__vccOpts: ComponentOptions
}
+/**
+ * Type used where a function accepts both vdom and vapor components.
+ */
+export type GenericComponent = (
+ | {
+ name?: string
+ }
+ | ((() => any) & { displayName?: string })
+) &
+ ComponentInternalOptions
+
/**
* Concrete component type matches its actual value: it's either an options
* object, or a function. Use this where the code expects to work with actual
_compatWrapped?: boolean // is wrapped for v2 compat
}
+/**
+ * Base component instance interface that is shared between vdom mode and vapor
+ * mode, so that we can have a mixed instance tree and reuse core logic that
+ * operate on both.
+ */
+export interface GenericComponentInstance {
+ uid: number
+ type: GenericComponent
+ parent: GenericComponentInstance | null
+ appContext: AppContext
+ /**
+ * Object containing values this component provides for its descendants
+ * @internal
+ */
+ provides: Data
+ /**
+ * Tracking reactive effects (e.g. watchers) associated with this component
+ * so that they can be automatically stopped on component unmount
+ * @internal
+ */
+ scope: EffectScope
+ /**
+ * SSR render function
+ * (they are the same between vdom and vapor components.)
+ * @internal
+ */
+ ssrRender?: Function | null
+
+ // state
+ props: Data
+ attrs: Data
+ /**
+ * @internal
+ */
+ refs: Data
+ /**
+ * used for keeping track of .once event handlers on components
+ * @internal
+ */
+ emitted: Record<string, boolean> | null
+ /**
+ * used for caching the value returned from props default factory functions to
+ * avoid unnecessary watcher trigger
+ * @internal
+ */
+ propsDefaults: Data | null
+
+ // the following are for error handling logic only
+ proxy?: any
+ /**
+ * @internal
+ */
+ [LifecycleHooks.ERROR_CAPTURED]: LifecycleHook
+}
+
/**
* We expose a subset of properties on the internal instance as they are
* useful for advanced external libraries and tools.
*/
-export interface ComponentInternalInstance {
+export interface ComponentInternalInstance extends GenericComponentInstance {
uid: number
type: ConcreteComponent
parent: ComponentInternalInstance | null
* @internal
*/
render: InternalRenderFunction | null
- /**
- * SSR render function
- * @internal
- */
- ssrRender?: Function | null
- /**
- * Object containing values this component provides for its descendants
- * @internal
- */
- provides: Data
/**
* for tracking useId()
* first element is the current boundary prefix
* @internal
*/
ids: [string, number, number]
- /**
- * Tracking reactive effects (e.g. watchers) associated with this component
- * so that they can be automatically stopped on component unmount
- * @internal
- */
- scope: EffectScope
/**
* cache for proxy access type to avoid hasOwnProperty calls
* @internal
ceReload?: (newStyles?: string[]) => void
// the rest are only for stateful components ---------------------------------
+ /**
+ * @internal
+ */
+ setupContext: SetupContext | null
+ /**
+ * setup related
+ * @internal
+ */
+ setupState: Data | null
+ /**
+ * devtools access to additional info
+ * @internal
+ */
+ devtoolsRawSetupState?: any
// main proxy that serves as the public instance (`this`)
proxy: ComponentPublicInstance | null
+ data: Data // options API only
+ emit: EmitFn
+ slots: InternalSlots
// exposed properties via expose()
exposed: Record<string, any> | null
exposeProxy: Record<string, any> | null
* @internal
*/
ctx: Data
-
- // state
- data: Data
- props: Data
- attrs: Data
- slots: InternalSlots
- refs: Data
- emit: EmitFn
-
- /**
- * used for keeping track of .once event handlers on components
- * @internal
- */
- emitted: Record<string, boolean> | null
- /**
- * used for caching the value returned from props default factory functions to
- * avoid unnecessary watcher trigger
- * @internal
- */
- propsDefaults: Data | null
- /**
- * setup related
- * @internal
- */
- setupState: Data
- /**
- * devtools access to additional info
- * @internal
- */
- devtoolsRawSetupState?: any
- /**
- * @internal
- */
- setupContext: SetupContext | null
-
/**
* suspense related
* @internal
let uid = 0
+/**
+ * @internal for vapor
+ */
+export function nextUid(): number {
+ return uid++
+}
+
export function createComponentInstance(
vnode: VNode,
parent: ComponentInternalInstance | null,
str.replace(classifyRE, c => c.toUpperCase()).replace(/[-_]/g, '')
export function getComponentName(
- Component: ConcreteComponent,
+ Component: GenericComponent,
includeInferred = true,
): string | false | undefined {
return isFunction(Component)
}
export function formatComponentName(
- instance: ComponentInternalInstance | null,
- Component: ConcreteComponent,
+ instance: GenericComponentInstance | null,
+ Component: GenericComponent,
isRoot = false,
): string {
let name = getComponentName(Component)
}
name =
inferFromRegistry(
- instance.components ||
+ (instance as ComponentInternalInstance).components ||
(instance.parent.type as ComponentOptions).components,
) || inferFromRegistry(instance.appContext.components)
}
type ComponentInternalInstance,
type ComponentOptions,
type ConcreteComponent,
+ type GenericComponentInstance,
setCurrentInstance,
} from './component'
import { isEmitListener } from './componentEmits'
[BooleanFlags.shouldCastTrue]?: boolean
}
-// normalized value is a tuple of the actual normalized options
-// and an array of prop keys that need value casting (booleans and defaults)
+/**
+ * normalized value is a tuple of the actual normalized options
+ * and an array of prop keys that need value casting (booleans and defaults)
+ * @internal
+ */
export type NormalizedProps = Record<string, NormalizedProp>
+/**
+ * @internal
+ */
export type NormalizedPropsOptions = [NormalizedProps, string[]] | []
export function initProps(
return hasAttrsChanged
}
-/**
- * A type that allows both vdom and vapor instances
- */
-type CommonInstance = Pick<
- ComponentInternalInstance,
- 'props' | 'propsDefaults' | 'ce'
->
-
/**
* @internal for runtime-vapor
*/
-export function resolvePropValue<T extends CommonInstance>(
+export function resolvePropValue<
+ T extends GenericComponentInstance & Pick<ComponentInternalInstance, 'ce'>,
+>(
options: NormalizedProps,
key: string,
value: unknown,
import { pauseTracking, resetTracking } from '@vue/reactivity'
-import type { VNode } from './vnode'
-import type { ComponentInternalInstance } from './component'
+import type { GenericComponentInstance } from './component'
import { popWarningContext, pushWarningContext, warn } from './warning'
import { EMPTY_OBJ, isArray, isFunction, isPromise } from '@vue/shared'
import { LifecycleHooks } from './enums'
export function callWithErrorHandling(
fn: Function,
- instance: ComponentInternalInstance | null | undefined,
+ instance: GenericComponentInstance | null | undefined,
type: ErrorTypes,
args?: unknown[],
): any {
export function callWithAsyncErrorHandling(
fn: Function | Function[],
- instance: ComponentInternalInstance | null,
+ instance: GenericComponentInstance | null,
type: ErrorTypes,
args?: unknown[],
): any {
export function handleError(
err: unknown,
- instance: ComponentInternalInstance | null | undefined,
+ instance: GenericComponentInstance | null | undefined,
type: ErrorTypes,
throwInDev = true,
): void {
- const contextVNode = instance ? instance.vnode : null
const { errorHandler, throwUnhandledErrorInProduction } =
(instance && instance.appContext.config) || EMPTY_OBJ
if (instance) {
let cur = instance.parent
// the exposed instance is the render proxy to keep it consistent with 2.x
- const exposedInstance = instance.proxy
+ const exposedInstance = instance.proxy || instance
// in production the hook receives only the error code
const errorInfo = __DEV__
? ErrorTypeStrings[type]
return
}
}
- logError(err, type, contextVNode, throwInDev, throwUnhandledErrorInProduction)
+ logError(err, type, instance, throwInDev, throwUnhandledErrorInProduction)
}
function logError(
err: unknown,
type: ErrorTypes,
- contextVNode: VNode | null,
+ instance: GenericComponentInstance | null | undefined,
throwInDev = true,
throwInProd = false,
) {
if (__DEV__) {
const info = ErrorTypeStrings[type]
- if (contextVNode) {
- pushWarningContext(contextVNode)
+ if (instance) {
+ pushWarningContext(instance)
}
warn(`Unhandled error${info ? ` during execution of ${info}` : ``}`)
- if (contextVNode) {
+ if (instance) {
popWarningContext()
}
// crash in dev by default so it's more noticeable
ExtractPropTypes,
ExtractPublicPropTypes,
ExtractDefaultPropTypes,
- NormalizedPropsOptions,
} from './componentProps'
export type {
Directive,
// **IMPORTANT** These APIs are exposed solely for @vue/runtime-vapor and may
// change without notice between versions. User code should never rely on them.
-export { baseNormalizePropsOptions, resolvePropValue } from './componentProps'
+export {
+ type NormalizedPropsOptions,
+ baseNormalizePropsOptions,
+ resolvePropValue,
+} from './componentProps'
export { isEmitListener } from './componentEmits'
export { type SchedulerJob, queueJob } from './scheduler'
+export {
+ type ComponentInternalOptions,
+ type GenericComponentInstance,
+ type LifecycleHook,
+ nextUid,
+} from './component'
import { ErrorCodes, callWithErrorHandling, handleError } from './errorHandling'
import { NOOP, isArray } from '@vue/shared'
-import { type ComponentInternalInstance, getComponentName } from './component'
+import { type GenericComponentInstance, getComponentName } from './component'
export enum SchedulerJobFlags {
QUEUED = 1 << 0,
* Attached by renderer.ts when setting up a component's render effect
* Used to obtain component information when reporting max recursive updates.
*/
- i?: ComponentInternalInstance
+ i?: GenericComponentInstance
}
export type SchedulerJobs = SchedulerJob | SchedulerJob[]
}
export function flushPreFlushCbs(
- instance?: ComponentInternalInstance,
+ instance?: GenericComponentInstance,
seen?: CountMap,
// skip the current job
i: number = flushIndex + 1,
-import type { VNode } from './vnode'
import {
type ComponentInternalInstance,
- type ConcreteComponent,
+ type GenericComponentInstance,
formatComponentName,
} from './component'
import { isFunction, isString } from '@vue/shared'
import type { Data } from '@vue/runtime-shared'
import { isRef, pauseTracking, resetTracking, toRaw } from '@vue/reactivity'
import { ErrorCodes, callWithErrorHandling } from './errorHandling'
+import { type VNode, isVNode } from './vnode'
-type ComponentVNode = VNode & {
- type: ConcreteComponent
-}
-
-const stack: VNode[] = []
+const stack: (GenericComponentInstance | VNode)[] = []
type TraceEntry = {
- vnode: ComponentVNode
+ ctx: GenericComponentInstance | VNode
recurseCount: number
}
type ComponentTraceStack = TraceEntry[]
-export function pushWarningContext(vnode: VNode): void {
- stack.push(vnode)
+export function pushWarningContext(
+ ctx: GenericComponentInstance | VNode,
+): void {
+ stack.push(ctx)
}
export function popWarningContext(): void {
// during patch, leading to infinite recursion.
pauseTracking()
- const instance = stack.length ? stack[stack.length - 1].component : null
+ const entry = stack.length ? stack[stack.length - 1] : null
+ const instance = isVNode(entry) ? entry.component : entry
const appWarnHandler = instance && instance.appContext.config.warnHandler
const trace = getComponentTrace()
instance && instance.proxy,
trace
.map(
- ({ vnode }) => `at <${formatComponentName(instance, vnode.type)}>`,
+ ({ ctx }) =>
+ `at <${formatComponentName(instance, (ctx as any).type)}>`,
)
.join('\n'),
trace,
}
export function getComponentTrace(): ComponentTraceStack {
- let currentVNode: VNode | null = stack[stack.length - 1]
- if (!currentVNode) {
+ let currentCtx: TraceEntry['ctx'] | null = stack[stack.length - 1]
+ if (!currentCtx) {
return []
}
// instance parent pointers.
const normalizedStack: ComponentTraceStack = []
- while (currentVNode) {
+ while (currentCtx) {
const last = normalizedStack[0]
- if (last && last.vnode === currentVNode) {
+ if (last && last.ctx === currentCtx) {
last.recurseCount++
} else {
normalizedStack.push({
- vnode: currentVNode as ComponentVNode,
+ ctx: currentCtx,
recurseCount: 0,
})
}
- const parentInstance: ComponentInternalInstance | null =
- currentVNode.component && currentVNode.component.parent
- currentVNode = parentInstance && parentInstance.vnode
+ if (isVNode(currentCtx)) {
+ const parent: ComponentInternalInstance | null =
+ currentCtx.component && currentCtx.component.parent
+ currentCtx = parent && parent.vnode
+ } else {
+ currentCtx = currentCtx.parent
+ }
}
return normalizedStack
return logs
}
-function formatTraceEntry({ vnode, recurseCount }: TraceEntry): any[] {
+function formatTraceEntry({ ctx, recurseCount }: TraceEntry): any[] {
const postfix =
recurseCount > 0 ? `... (${recurseCount} recursive calls)` : ``
- const isRoot = vnode.component ? vnode.component.parent == null : false
- const open = ` at <${formatComponentName(
- vnode.component,
- vnode.type,
- isRoot,
- )}`
+ const instance = isVNode(ctx) ? ctx.component : ctx
+ const isRoot = instance ? instance.parent == null : false
+ const open = ` at <${formatComponentName(instance, (ctx as any).type, isRoot)}`
const close = `>` + postfix
- return vnode.props
- ? [open, ...formatProps(vnode.props), close]
- : [open + close]
+ return ctx.props ? [open, ...formatProps(ctx.props), close] : [open + close]
}
function formatProps(props: Data): any[] {
import { normalizeContainer } from '../apiRender'
import { insert } from '../dom/element'
-import { type Component, createComponent } from './component'
+import { type VaporComponent, createComponent } from './component'
-export function createVaporApp(comp: Component): any {
+export function createVaporApp(comp: VaporComponent): any {
return {
mount(container: string | ParentNode) {
container = normalizeContainer(container)
import {
+ type AppContext,
+ type ComponentInternalOptions,
type ComponentPropsOptions,
EffectScope,
type EmitsOptions,
+ type GenericComponentInstance,
+ type LifecycleHook,
type NormalizedPropsOptions,
type ObjectEmitsOptions,
+ nextUid,
} from '@vue/runtime-core'
import type { Block } from '../block'
import type { Data } from '@vue/runtime-shared'
import { pauseTracking, resetTracking } from '@vue/reactivity'
-import { isFunction } from '@vue/shared'
+import { EMPTY_OBJ, isFunction } from '@vue/shared'
import {
type RawProps,
getDynamicPropsHandlers,
import { setDynamicProp } from '../dom/prop'
import { renderEffect } from './renderEffect'
-export type Component = FunctionalComponent | ObjectComponent
+export type VaporComponent = FunctionalVaporComponent | ObjectVaporComponent
-export type SetupFn = (
+export type VaporSetupFn = (
props: any,
ctx: SetupContext,
) => Block | Data | undefined
-export type FunctionalComponent = SetupFn &
- Omit<ObjectComponent, 'setup'> & {
+export type FunctionalVaporComponent = VaporSetupFn &
+ Omit<ObjectVaporComponent, 'setup'> & {
displayName?: string
} & SharedInternalOptions
-export interface ObjectComponent
+export interface ObjectVaporComponent
extends ComponentInternalOptions,
SharedInternalOptions {
- setup?: SetupFn
+ setup?: VaporSetupFn
inheritAttrs?: boolean
props?: ComponentPropsOptions
emits?: EmitsOptions
}
interface SharedInternalOptions {
- __propsOptions?: NormalizedPropsOptions
- __propsHandlers?: [ProxyHandler<any>, ProxyHandler<any>]
- __emitsOptions?: ObjectEmitsOptions
-}
-
-// Note: can't mark this whole interface internal because some public interfaces
-// extend it.
-interface ComponentInternalOptions {
- /**
- * @internal
- */
- __scopeId?: string
- /**
- * @internal
- */
- __cssModules?: Data
/**
- * @internal
+ * Cached normalized props options.
+ * In vapor mode there are no mixins so normalized options can be cached
+ * directly on the component
*/
- __hmrId?: string
+ __propsOptions?: NormalizedPropsOptions
/**
- * This one should be exposed so that devtools can make use of it
+ * Cached normalized props proxy handlers.
*/
- __file?: string
+ __propsHandlers?: [ProxyHandler<any>, ProxyHandler<any>]
/**
- * name inferred from filename
+ * Cached normalized emits options.
*/
- __name?: string
+ __emitsOptions?: ObjectEmitsOptions
}
export function createComponent(
- component: Component,
+ component: VaporComponent,
rawProps?: RawProps,
isSingleRoot?: boolean,
-): ComponentInstance {
+): VaporComponentInstance {
// check if we are the single root of the parent
// if yes, inject parent attrs as dynamic props source
if (isSingleRoot && currentInstance && currentInstance.hasFallthrough) {
}
}
- const instance = new ComponentInstance(component, rawProps)
+ const instance = new VaporComponentInstance(component, rawProps)
pauseTracking()
let prevInstance = currentInstance
return instance
}
-let uid = 0
-export let currentInstance: ComponentInstance | null = null
+export let currentInstance: VaporComponentInstance | null = null
-export class ComponentInstance {
- type: Component
- uid: number = uid++
- scope: EffectScope = new EffectScope(true)
+export class VaporComponentInstance implements GenericComponentInstance {
+ uid: number
+ type: VaporComponent
+ parent: GenericComponentInstance | null
+ appContext: AppContext
+
+ block: Block
+ scope: EffectScope
props: Record<string, any>
- propsDefaults: Record<string, any> | null
attrs: Record<string, any>
- block: Block
exposed?: Record<string, any>
+
+ emitted: Record<string, boolean> | null
+ propsDefaults: Record<string, any> | null
+
+ // for useTemplateRef()
+ refs: Data
+ // for provide / inject
+ provides: Data
+
hasFallthrough: boolean
- constructor(comp: Component, rawProps?: RawProps) {
+ // LifecycleHooks.ERROR_CAPTURED
+ ec: LifecycleHook
+
+ constructor(comp: VaporComponent, rawProps?: RawProps) {
+ this.uid = nextUid()
this.type = comp
+ this.parent = currentInstance
+ this.appContext = currentInstance ? currentInstance.appContext : null! // TODO
+
this.block = null! // to be set
+ this.scope = new EffectScope(true)
+
+ this.provides = this.refs = EMPTY_OBJ
+ this.emitted = null
+ this.ec = null
// init props
this.propsDefaults = null
}
}
-export function isVaporComponent(value: unknown): value is ComponentInstance {
- return value instanceof ComponentInstance
+export function isVaporComponent(
+ value: unknown,
+): value is VaporComponentInstance {
+ return value instanceof VaporComponentInstance
}
export class SetupContext<E = EmitsOptions> {
// slots: Readonly<StaticSlots>
expose: (exposed?: Record<string, any>) => void
- constructor(instance: ComponentInstance) {
+ constructor(instance: VaporComponentInstance) {
this.attrs = instance.attrs
// this.emit = instance.emit as EmitFn<E>
// this.slots = instance.slots
-import type { ObjectEmitsOptions } from '@vue/runtime-core'
-import type { Component } from './component'
-import { isArray } from '@vue/shared'
+import type { EmitFn, ObjectEmitsOptions } from '@vue/runtime-core'
+import {
+ type VaporComponent,
+ type VaporComponentInstance,
+ currentInstance,
+} from './component'
+import { NOOP, isArray } from '@vue/shared'
/**
* The logic from core isn't too reusable so it's better to duplicate here
*/
export function normalizeEmitsOptions(
- comp: Component,
+ comp: VaporComponent,
): ObjectEmitsOptions | null {
const cached = comp.__emitsOptions
if (cached) return cached
return (comp.__emitsOptions = normalized)
}
+
+export function useEmit(): EmitFn {
+ if (!currentInstance) {
+ // TODO warn
+ return NOOP
+ } else {
+ return emit.bind(null, currentInstance)
+ }
+}
+
+export function emit(
+ instance: VaporComponentInstance,
+ event: string,
+ ...rawArgs: any[]
+): void {
+ // TODO extract reusable logic from core
+}
import { EMPTY_ARR, NO, camelize, hasOwn, isFunction } from '@vue/shared'
-import type { Component, ComponentInstance } from './component'
+import type { VaporComponent, VaporComponentInstance } from './component'
import {
type NormalizedPropsOptions,
baseNormalizePropsOptions,
type DynamicPropsSource = PropSource<Record<string, any>>
export function initStaticProps(
- comp: Component,
+ comp: VaporComponent,
rawProps: RawProps | undefined,
- instance: ComponentInstance,
+ instance: VaporComponentInstance,
): boolean {
let hasAttrs = false
const { props, attrs } = instance
function resolveDefault(
factory: (props: Record<string, any>) => unknown,
- instance: ComponentInstance,
+ instance: VaporComponentInstance,
) {
return factory.call(null, instance.props)
}
}
export function getDynamicPropsHandlers(
- comp: Component,
- instance: ComponentInstance,
+ comp: VaporComponent,
+ instance: VaporComponentInstance,
): [ProxyHandler<RawProps>, ProxyHandler<RawProps>] {
if (comp.__propsHandlers) {
return comp.__propsHandlers
return (comp.__propsHandlers = [propsHandlers, attrsHandlers])
}
-function normalizePropsOptions(comp: Component): NormalizedPropsOptions {
+function normalizePropsOptions(comp: VaporComponent): NormalizedPropsOptions {
const cached = comp.__propsOptions
if (cached) return cached
import { isArray } from '@vue/shared'
-import { type ComponentInstance, isVaporComponent } from './_new/component'
+import { type VaporComponentInstance, isVaporComponent } from './_new/component'
export const fragmentKey: unique symbol = Symbol(__DEV__ ? `fragmentKey` : ``)
-export type Block = Node | Fragment | ComponentInstance | Block[]
+export type Block = Node | Fragment | VaporComponentInstance | Block[]
export type Fragment = {
nodes: Block
anchor?: Node
}
export function findFirstRootElement(
- instance: ComponentInstance,
+ instance: VaporComponentInstance,
): Element | undefined {
const element = getFirstNode(instance.block)
return element instanceof Element ? element : undefined