import { VNode, VNodeChild, isVNode } from './vnode' import { pauseTracking, resetTracking, shallowReadonly, proxyRefs, EffectScope, markRaw, track, TrackOpTypes, ReactiveEffect } from '@vue/reactivity' import { ComponentPublicInstance, PublicInstanceProxyHandlers, createDevRenderContext, exposePropsOnRenderContext, exposeSetupStateOnRenderContext, ComponentPublicInstanceConstructor, publicPropertiesMap, RuntimeCompiledPublicInstanceProxyHandlers } from './componentPublicInstance' import { ComponentPropsOptions, NormalizedPropsOptions, initProps, normalizePropsOptions } from './componentProps' import { Slots, initSlots, InternalSlots } from './componentSlots' import { warn } from './warning' import { ErrorCodes, callWithErrorHandling, handleError } from './errorHandling' import { AppContext, createAppContext, AppConfig } from './apiCreateApp' import { Directive, validateDirectiveName } from './directives' import { applyOptions, ComponentOptions, ComputedOptions, MethodOptions, resolveMergedOptions } from './componentOptions' import { EmitsOptions, ObjectEmitsOptions, EmitFn, emit, normalizeEmitsOptions } from './componentEmits' import { EMPTY_OBJ, isFunction, NOOP, isObject, NO, makeMap, isPromise, ShapeFlags, extend } from '@vue/shared' import { SuspenseBoundary } from './components/Suspense' import { CompilerOptions } from '@vue/compiler-core' import { markAttrsAccessed } from './componentRenderUtils' import { currentRenderingInstance } from './componentRenderContext' import { startMeasure, endMeasure } from './profiling' import { convertLegacyRenderFn } from './compat/renderFn' import { CompatConfig, globalCompatConfig, validateCompatConfig } from './compat/compatConfig' import { SchedulerJob } from './scheduler' export type Data = Record /** * For extending allowed non-declared props on components in TSX */ export interface ComponentCustomProps {} /** * Default allowed non-declared props on component in TSX */ export interface AllowedComponentProps { class?: unknown style?: unknown } // Note: can't mark this whole interface internal because some public interfaces // extend it. export interface ComponentInternalOptions { /** * @internal */ __scopeId?: string /** * @internal */ __cssModules?: Data /** * @internal */ __hmrId?: string /** * Compat build only, for bailing out of certain compatibility behavior */ __isBuiltIn?: boolean /** * This one should be exposed so that devtools can make use of it */ __file?: string /** * name inferred from filename */ __name?: string } export interface FunctionalComponent

extends ComponentInternalOptions { // use of any here is intentional so it can be a valid JSX Element constructor (props: P, ctx: Omit, 'expose'>): any props?: ComponentPropsOptions

emits?: E | (keyof E)[] inheritAttrs?: boolean displayName?: string compatConfig?: CompatConfig } export interface ClassComponent { new (...args: any[]): ComponentPublicInstance __vccOpts: ComponentOptions } /** * 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 * values, e.g. checking if its a function or not. This is mostly for internal * implementation code. */ export type ConcreteComponent< Props = {}, RawBindings = any, D = any, C extends ComputedOptions = ComputedOptions, M extends MethodOptions = MethodOptions > = | ComponentOptions | FunctionalComponent /** * A type used in public APIs where a component type is expected. * The constructor type is an artificial type returned by defineComponent(). */ export type Component< Props = any, RawBindings = any, D = any, C extends ComputedOptions = ComputedOptions, M extends MethodOptions = MethodOptions > = | ConcreteComponent | ComponentPublicInstanceConstructor export { ComponentOptions } type LifecycleHook = TFn[] | null export const enum LifecycleHooks { BEFORE_CREATE = 'bc', CREATED = 'c', BEFORE_MOUNT = 'bm', MOUNTED = 'm', BEFORE_UPDATE = 'bu', UPDATED = 'u', BEFORE_UNMOUNT = 'bum', UNMOUNTED = 'um', DEACTIVATED = 'da', ACTIVATED = 'a', RENDER_TRIGGERED = 'rtg', RENDER_TRACKED = 'rtc', ERROR_CAPTURED = 'ec', SERVER_PREFETCH = 'sp' } export interface SetupContext { attrs: Data slots: Slots emit: EmitFn expose: (exposed?: Record) => void } /** * @internal */ export type InternalRenderFunction = { ( ctx: ComponentPublicInstance, cache: ComponentInternalInstance['renderCache'], // for compiler-optimized bindings $props: ComponentInternalInstance['props'], $setup: ComponentInternalInstance['setupState'], $data: ComponentInternalInstance['data'], $options: ComponentInternalInstance['ctx'] ): VNodeChild _rc?: boolean // isRuntimeCompiled // __COMPAT__ only _compatChecked?: boolean // v3 and already checked for v2 compat _compatWrapped?: boolean // is wrapped for v2 compat } /** * We expose a subset of properties on the internal instance as they are * useful for advanced external libraries and tools. */ export interface ComponentInternalInstance { uid: number type: ConcreteComponent parent: ComponentInternalInstance | null root: ComponentInternalInstance appContext: AppContext /** * Vnode representing this component in its parent's vdom tree */ vnode: VNode /** * The pending new vnode from parent updates * @internal */ next: VNode | null /** * Root vnode of this component's own vdom tree */ subTree: VNode /** * Render effect instance */ effect: ReactiveEffect /** * Bound effect runner to be passed to schedulers */ update: SchedulerJob /** * The render function that returns vdom tree. * @internal */ render: InternalRenderFunction | null /** * SSR render function * @internal */ ssrRender?: Function | null /** * Object containing values this component provides for its descendents * @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 /** * cache for proxy access type to avoid hasOwnProperty calls * @internal */ accessCache: Data | null /** * cache for render function values that rely on _ctx but won't need updates * after initialized (e.g. inline handlers) * @internal */ renderCache: (Function | VNode)[] /** * Resolved component registry, only for components with mixins or extends * @internal */ components: Record | null /** * Resolved directive registry, only for components with mixins or extends * @internal */ directives: Record | null /** * Resolved filters registry, v2 compat only * @internal */ filters?: Record /** * resolved props options * @internal */ propsOptions: NormalizedPropsOptions /** * resolved emits options * @internal */ emitsOptions: ObjectEmitsOptions | null /** * resolved inheritAttrs options * @internal */ inheritAttrs?: boolean /** * is custom element? */ isCE?: boolean /** * custom element specific HMR method */ ceReload?: (newStyles?: string[]) => void // the rest are only for stateful components --------------------------------- // main proxy that serves as the public instance (`this`) proxy: ComponentPublicInstance | null // exposed properties via expose() exposed: Record | null exposeProxy: Record | null /** * alternative proxy used only for runtime-compiled render functions using * `with` block * @internal */ withProxy: ComponentPublicInstance | null /** * This is the target for the public instance proxy. It also holds properties * injected by user options (computed, methods etc.) and user-attached * custom properties (via `this.x = ...`) * @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 | null /** * used for caching the value returned from props default factory functions to * avoid unnecessary watcher trigger * @internal */ propsDefaults: Data /** * setup related * @internal */ setupState: Data /** * devtools access to additional info * @internal */ devtoolsRawSetupState?: any /** * @internal */ setupContext: SetupContext | null /** * suspense related * @internal */ suspense: SuspenseBoundary | null /** * suspense pending batch id * @internal */ suspenseId: number /** * @internal */ asyncDep: Promise | null /** * @internal */ asyncResolved: boolean // lifecycle isMounted: boolean isUnmounted: boolean isDeactivated: boolean /** * @internal */ [LifecycleHooks.BEFORE_CREATE]: LifecycleHook /** * @internal */ [LifecycleHooks.CREATED]: LifecycleHook /** * @internal */ [LifecycleHooks.BEFORE_MOUNT]: LifecycleHook /** * @internal */ [LifecycleHooks.MOUNTED]: LifecycleHook /** * @internal */ [LifecycleHooks.BEFORE_UPDATE]: LifecycleHook /** * @internal */ [LifecycleHooks.UPDATED]: LifecycleHook /** * @internal */ [LifecycleHooks.BEFORE_UNMOUNT]: LifecycleHook /** * @internal */ [LifecycleHooks.UNMOUNTED]: LifecycleHook /** * @internal */ [LifecycleHooks.RENDER_TRACKED]: LifecycleHook /** * @internal */ [LifecycleHooks.RENDER_TRIGGERED]: LifecycleHook /** * @internal */ [LifecycleHooks.ACTIVATED]: LifecycleHook /** * @internal */ [LifecycleHooks.DEACTIVATED]: LifecycleHook /** * @internal */ [LifecycleHooks.ERROR_CAPTURED]: LifecycleHook /** * @internal */ [LifecycleHooks.SERVER_PREFETCH]: LifecycleHook<() => Promise> /** * For caching bound $forceUpdate on public proxy access */ f?: () => void /** * For caching bound $nextTick on public proxy access */ n?: () => Promise } const emptyAppContext = createAppContext() let uid = 0 export function createComponentInstance( vnode: VNode, parent: ComponentInternalInstance | null, suspense: SuspenseBoundary | null ) { const type = vnode.type as ConcreteComponent // inherit parent app context - or - if root, adopt from root vnode const appContext = (parent ? parent.appContext : vnode.appContext) || emptyAppContext const instance: ComponentInternalInstance = { uid: uid++, vnode, type, parent, appContext, root: null!, // to be immediately set next: null, subTree: null!, // will be set synchronously right after creation effect: null!, update: null!, // will be set synchronously right after creation scope: new EffectScope(true /* detached */), render: null, proxy: null, exposed: null, exposeProxy: null, withProxy: null, provides: parent ? parent.provides : Object.create(appContext.provides), accessCache: null!, renderCache: [], // local resolved assets components: null, directives: null, // resolved props and emits options propsOptions: normalizePropsOptions(type, appContext), emitsOptions: normalizeEmitsOptions(type, appContext), // emit emit: null!, // to be set immediately emitted: null, // props default value propsDefaults: EMPTY_OBJ, // inheritAttrs inheritAttrs: type.inheritAttrs, // state ctx: EMPTY_OBJ, data: EMPTY_OBJ, props: EMPTY_OBJ, attrs: EMPTY_OBJ, slots: EMPTY_OBJ, refs: EMPTY_OBJ, setupState: EMPTY_OBJ, setupContext: null, // suspense related suspense, suspenseId: suspense ? suspense.pendingId : 0, asyncDep: null, asyncResolved: false, // lifecycle hooks // not using enums here because it results in computed properties isMounted: false, isUnmounted: false, isDeactivated: false, bc: null, c: null, bm: null, m: null, bu: null, u: null, um: null, bum: null, da: null, a: null, rtg: null, rtc: null, ec: null, sp: null } if (__DEV__) { instance.ctx = createDevRenderContext(instance) } else { instance.ctx = { _: instance } } instance.root = parent ? parent.root : instance instance.emit = emit.bind(null, instance) // apply custom element special handling if (vnode.ce) { vnode.ce(instance) } return instance } export let currentInstance: ComponentInternalInstance | null = null export const getCurrentInstance: () => ComponentInternalInstance | null = () => currentInstance || currentRenderingInstance export const setCurrentInstance = (instance: ComponentInternalInstance) => { currentInstance = instance instance.scope.on() } export const unsetCurrentInstance = () => { currentInstance && currentInstance.scope.off() currentInstance = null } const isBuiltInTag = /*#__PURE__*/ makeMap('slot,component') export function validateComponentName(name: string, config: AppConfig) { const appIsNativeTag = config.isNativeTag || NO if (isBuiltInTag(name) || appIsNativeTag(name)) { warn( 'Do not use built-in or reserved HTML elements as component id: ' + name ) } } export function isStatefulComponent(instance: ComponentInternalInstance) { return instance.vnode.shapeFlag & ShapeFlags.STATEFUL_COMPONENT } export let isInSSRComponentSetup = false export function setupComponent( instance: ComponentInternalInstance, isSSR = false ) { isInSSRComponentSetup = isSSR const { props, children } = instance.vnode const isStateful = isStatefulComponent(instance) initProps(instance, props, isStateful, isSSR) initSlots(instance, children) const setupResult = isStateful ? setupStatefulComponent(instance, isSSR) : undefined isInSSRComponentSetup = false return setupResult } function setupStatefulComponent( instance: ComponentInternalInstance, isSSR: boolean ) { const Component = instance.type as ComponentOptions if (__DEV__) { if (Component.name) { validateComponentName(Component.name, instance.appContext.config) } if (Component.components) { const names = Object.keys(Component.components) for (let i = 0; i < names.length; i++) { validateComponentName(names[i], instance.appContext.config) } } if (Component.directives) { const names = Object.keys(Component.directives) for (let i = 0; i < names.length; i++) { validateDirectiveName(names[i]) } } if (Component.compilerOptions && isRuntimeOnly()) { warn( `"compilerOptions" is only supported when using a build of Vue that ` + `includes the runtime compiler. Since you are using a runtime-only ` + `build, the options should be passed via your build tool config instead.` ) } } // 0. create render proxy property access cache instance.accessCache = Object.create(null) // 1. create public instance / render proxy // also mark it raw so it's never observed instance.proxy = markRaw(new Proxy(instance.ctx, PublicInstanceProxyHandlers)) if (__DEV__) { exposePropsOnRenderContext(instance) } // 2. call setup() const { setup } = Component if (setup) { const setupContext = (instance.setupContext = setup.length > 1 ? createSetupContext(instance) : null) setCurrentInstance(instance) pauseTracking() const setupResult = callWithErrorHandling( setup, instance, ErrorCodes.SETUP_FUNCTION, [__DEV__ ? shallowReadonly(instance.props) : instance.props, setupContext] ) resetTracking() unsetCurrentInstance() if (isPromise(setupResult)) { setupResult.then(unsetCurrentInstance, unsetCurrentInstance) if (isSSR) { // return the promise so server-renderer can wait on it return setupResult .then((resolvedResult: unknown) => { handleSetupResult(instance, resolvedResult, isSSR) }) .catch(e => { handleError(e, instance, ErrorCodes.SETUP_FUNCTION) }) } else if (__FEATURE_SUSPENSE__) { // async setup returned Promise. // bail here and wait for re-entry. instance.asyncDep = setupResult if (__DEV__ && !instance.suspense) { const name = Component.name ?? 'Anonymous' warn( `Component <${name}>: setup function returned a promise, but no ` + ` boundary was found in the parent component tree. ` + `A component with async setup() must be nested in a ` + `in order to be rendered.` ) } } else if (__DEV__) { warn( `setup() returned a Promise, but the version of Vue you are using ` + `does not support it yet.` ) } } else { handleSetupResult(instance, setupResult, isSSR) } } else { finishComponentSetup(instance, isSSR) } } export function handleSetupResult( instance: ComponentInternalInstance, setupResult: unknown, isSSR: boolean ) { if (isFunction(setupResult)) { // setup returned an inline render function if (__SSR__ && (instance.type as ComponentOptions).__ssrInlineRender) { // when the function's name is `ssrRender` (compiled by SFC inline mode), // set it as ssrRender instead. instance.ssrRender = setupResult } else { instance.render = setupResult as InternalRenderFunction } } else if (isObject(setupResult)) { if (__DEV__ && isVNode(setupResult)) { warn( `setup() should not return VNodes directly - ` + `return a render function instead.` ) } // setup returned bindings. // assuming a render function compiled from template is present. if (__DEV__ || __FEATURE_PROD_DEVTOOLS__) { instance.devtoolsRawSetupState = setupResult } instance.setupState = proxyRefs(setupResult) if (__DEV__) { exposeSetupStateOnRenderContext(instance) } } else if (__DEV__ && setupResult !== undefined) { warn( `setup() should return an object. Received: ${ setupResult === null ? 'null' : typeof setupResult }` ) } finishComponentSetup(instance, isSSR) } type CompileFunction = ( template: string | object, options?: CompilerOptions ) => InternalRenderFunction let compile: CompileFunction | undefined let installWithProxy: (i: ComponentInternalInstance) => void /** * For runtime-dom to register the compiler. * Note the exported method uses any to avoid d.ts relying on the compiler types. */ export function registerRuntimeCompiler(_compile: any) { compile = _compile installWithProxy = i => { if (i.render!._rc) { i.withProxy = new Proxy(i.ctx, RuntimeCompiledPublicInstanceProxyHandlers) } } } // dev only export const isRuntimeOnly = () => !compile export function finishComponentSetup( instance: ComponentInternalInstance, isSSR: boolean, skipOptions?: boolean ) { const Component = instance.type as ComponentOptions if (__COMPAT__) { convertLegacyRenderFn(instance) if (__DEV__ && Component.compatConfig) { validateCompatConfig(Component.compatConfig) } } // template / render function normalization // could be already set when returned from setup() if (!instance.render) { // only do on-the-fly compile if not in SSR - SSR on-the-fly compilation // is done by server-renderer if (!isSSR && compile && !Component.render) { const template = (__COMPAT__ && instance.vnode.props && instance.vnode.props['inline-template']) || Component.template || resolveMergedOptions(instance).template if (template) { if (__DEV__) { startMeasure(instance, `compile`) } const { isCustomElement, compilerOptions } = instance.appContext.config const { delimiters, compilerOptions: componentCompilerOptions } = Component const finalCompilerOptions: CompilerOptions = extend( extend( { isCustomElement, delimiters }, compilerOptions ), componentCompilerOptions ) if (__COMPAT__) { // pass runtime compat config into the compiler finalCompilerOptions.compatConfig = Object.create(globalCompatConfig) if (Component.compatConfig) { // @ts-expect-error types are not compatible extend(finalCompilerOptions.compatConfig, Component.compatConfig) } } Component.render = compile(template, finalCompilerOptions) if (__DEV__) { endMeasure(instance, `compile`) } } } instance.render = (Component.render || NOOP) as InternalRenderFunction // for runtime-compiled render functions using `with` blocks, the render // proxy used needs a different `has` handler which is more performant and // also only allows a whitelist of globals to fallthrough. if (installWithProxy) { installWithProxy(instance) } } // support for 2.x options if (__FEATURE_OPTIONS_API__ && !(__COMPAT__ && skipOptions)) { setCurrentInstance(instance) pauseTracking() applyOptions(instance) resetTracking() unsetCurrentInstance() } // warn missing template/render // the runtime compilation of template in SSR is done by server-render if (__DEV__ && !Component.render && instance.render === NOOP && !isSSR) { /* istanbul ignore if */ if (!compile && Component.template) { warn( `Component provided template option but ` + `runtime compilation is not supported in this build of Vue.` + (__ESM_BUNDLER__ ? ` Configure your bundler to alias "vue" to "vue/dist/vue.esm-bundler.js".` : __ESM_BROWSER__ ? ` Use "vue.esm-browser.js" instead.` : __GLOBAL__ ? ` Use "vue.global.js" instead.` : ``) /* should not happen */ ) } else { warn(`Component is missing template or render function.`) } } } function createAttrsProxy(instance: ComponentInternalInstance): Data { return new Proxy( instance.attrs, __DEV__ ? { get(target, key: string) { markAttrsAccessed() track(instance, TrackOpTypes.GET, '$attrs') return target[key] }, set() { warn(`setupContext.attrs is readonly.`) return false }, deleteProperty() { warn(`setupContext.attrs is readonly.`) return false } } : { get(target, key: string) { track(instance, TrackOpTypes.GET, '$attrs') return target[key] } } ) } export function createSetupContext( instance: ComponentInternalInstance ): SetupContext { const expose: SetupContext['expose'] = exposed => { if (__DEV__ && instance.exposed) { warn(`expose() should be called only once per setup().`) } instance.exposed = exposed || {} } let attrs: Data if (__DEV__) { // We use getters in dev in case libs like test-utils overwrite instance // properties (overwrites should not be done in prod) return Object.freeze({ get attrs() { return attrs || (attrs = createAttrsProxy(instance)) }, get slots() { return shallowReadonly(instance.slots) }, get emit() { return (event: string, ...args: any[]) => instance.emit(event, ...args) }, expose }) } else { return { get attrs() { return attrs || (attrs = createAttrsProxy(instance)) }, slots: instance.slots, emit: instance.emit, expose } } } export function getExposeProxy(instance: ComponentInternalInstance) { if (instance.exposed) { return ( instance.exposeProxy || (instance.exposeProxy = new Proxy(proxyRefs(markRaw(instance.exposed)), { get(target, key: string) { if (key in target) { return target[key] } else if (key in publicPropertiesMap) { return publicPropertiesMap[key](instance) } } })) ) } } const classifyRE = /(?:^|[-_])(\w)/g const classify = (str: string): string => str.replace(classifyRE, c => c.toUpperCase()).replace(/[-_]/g, '') export function getComponentName( Component: ConcreteComponent, includeInferred = true ): string | false | undefined { return isFunction(Component) ? Component.displayName || Component.name : Component.name || (includeInferred && Component.__name) } /* istanbul ignore next */ export function formatComponentName( instance: ComponentInternalInstance | null, Component: ConcreteComponent, isRoot = false ): string { let name = getComponentName(Component) if (!name && Component.__file) { const match = Component.__file.match(/([^/\\]+)\.\w+$/) if (match) { name = match[1] } } if (!name && instance && instance.parent) { // try to infer the name based on reverse resolution const inferFromRegistry = (registry: Record | undefined) => { for (const key in registry) { if (registry[key] === Component) { return key } } } name = inferFromRegistry( instance.components || (instance.parent.type as ComponentOptions).components ) || inferFromRegistry(instance.appContext.components) } return name ? classify(name) : isRoot ? `App` : `Anonymous` } export function isClassComponent(value: unknown): value is ClassComponent { return isFunction(value) && '__vccOpts' in value }