| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531 |
- import { VNode, VNodeChild, isVNode } from './vnode'
- import {
- reactive,
- ReactiveEffect,
- shallowReadonly,
- pauseTracking,
- resetTracking
- } from '@vue/reactivity'
- import {
- PublicInstanceProxyHandlers,
- ComponentPublicInstance,
- runtimeCompiledRenderProxyHandlers
- } from './componentProxy'
- import { ComponentPropsOptions, resolveProps } from './componentProps'
- import { Slots, resolveSlots } from './componentSlots'
- import { warn } from './warning'
- import {
- ErrorCodes,
- callWithErrorHandling,
- callWithAsyncErrorHandling
- } from './errorHandling'
- import { AppContext, createAppContext, AppConfig } from './apiCreateApp'
- import { Directive, validateDirectiveName } from './directives'
- import { applyOptions, ComponentOptions } from './apiOptions'
- import {
- EMPTY_OBJ,
- isFunction,
- capitalize,
- NOOP,
- isObject,
- NO,
- makeMap,
- isPromise,
- isArray,
- hyphenate,
- ShapeFlags
- } from '@vue/shared'
- import { SuspenseBoundary } from './components/Suspense'
- import { CompilerOptions } from '@vue/compiler-core'
- import {
- currentRenderingInstance,
- markAttrsAccessed
- } from './componentRenderUtils'
- export type Data = { [key: string]: unknown }
- export interface SFCInternalOptions {
- __scopeId?: string
- __cssModules?: Data
- __hmrId?: string
- __hmrUpdated?: boolean
- }
- export interface FunctionalComponent<P = {}> extends SFCInternalOptions {
- (props: P, ctx: SetupContext): VNodeChild
- props?: ComponentPropsOptions<P>
- inheritAttrs?: boolean
- displayName?: string
- }
- export interface ClassComponent {
- new (...args: any[]): ComponentPublicInstance<any, any, any, any, any>
- __vccOpts: ComponentOptions
- }
- export type Component = 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 PublicAPIComponent =
- | Component
- | { new (...args: any[]): ComponentPublicInstance<any, any, any, any, any> }
- export { ComponentOptions }
- type LifecycleHook = Function[] | 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'
- }
- export type Emit = (event: string, ...args: unknown[]) => any[]
- export interface SetupContext {
- attrs: Data
- slots: Slots
- emit: Emit
- }
- export type RenderFunction = {
- (): VNodeChild
- _rc?: boolean // isRuntimeCompiled
- }
- export interface ComponentInternalInstance {
- type: FunctionalComponent | ComponentOptions
- parent: ComponentInternalInstance | null
- appContext: AppContext
- root: ComponentInternalInstance
- vnode: VNode
- next: VNode | null
- subTree: VNode
- update: ReactiveEffect
- render: RenderFunction | null
- effects: ReactiveEffect[] | null
- provides: Data
- // cache for proxy access type to avoid hasOwnProperty calls
- accessCache: Data | null
- // cache for render function values that rely on _ctx but won't need updates
- // after initialized (e.g. inline handlers)
- renderCache: (Function | VNode)[]
- // assets for fast resolution
- components: Record<string, Component>
- directives: Record<string, Directive>
- // the rest are only for stateful components
- renderContext: Data
- data: Data
- props: Data
- attrs: Data
- vnodeHooks: Data
- slots: Slots
- proxy: ComponentPublicInstance | null
- // alternative proxy used only for runtime-compiled render functions using
- // `with` block
- withProxy: ComponentPublicInstance | null
- propsProxy: Data | null
- setupContext: SetupContext | null
- refs: Data
- emit: Emit
- // suspense related
- asyncDep: Promise<any> | null
- asyncResult: unknown
- asyncResolved: boolean
- // storage for any extra properties
- sink: { [key: string]: any }
- // lifecycle
- isMounted: boolean
- isUnmounted: boolean
- isDeactivated: boolean
- [LifecycleHooks.BEFORE_CREATE]: LifecycleHook
- [LifecycleHooks.CREATED]: LifecycleHook
- [LifecycleHooks.BEFORE_MOUNT]: LifecycleHook
- [LifecycleHooks.MOUNTED]: LifecycleHook
- [LifecycleHooks.BEFORE_UPDATE]: LifecycleHook
- [LifecycleHooks.UPDATED]: LifecycleHook
- [LifecycleHooks.BEFORE_UNMOUNT]: LifecycleHook
- [LifecycleHooks.UNMOUNTED]: LifecycleHook
- [LifecycleHooks.RENDER_TRACKED]: LifecycleHook
- [LifecycleHooks.RENDER_TRIGGERED]: LifecycleHook
- [LifecycleHooks.ACTIVATED]: LifecycleHook
- [LifecycleHooks.DEACTIVATED]: LifecycleHook
- [LifecycleHooks.ERROR_CAPTURED]: LifecycleHook
- // hmr marker (dev only)
- renderUpdated?: boolean
- }
- const emptyAppContext = createAppContext()
- export function createComponentInstance(
- vnode: VNode,
- parent: ComponentInternalInstance | null
- ) {
- // inherit parent app context - or - if root, adopt from root vnode
- const appContext =
- (parent ? parent.appContext : vnode.appContext) || emptyAppContext
- const instance: ComponentInternalInstance = {
- vnode,
- parent,
- appContext,
- type: vnode.type as Component,
- root: null!, // set later so it can point to itself
- next: null,
- subTree: null!, // will be set synchronously right after creation
- update: null!, // will be set synchronously right after creation
- render: null,
- proxy: null,
- withProxy: null,
- propsProxy: null,
- setupContext: null,
- effects: null,
- provides: parent ? parent.provides : Object.create(appContext.provides),
- accessCache: null!,
- renderCache: [],
- // setup context properties
- renderContext: EMPTY_OBJ,
- data: EMPTY_OBJ,
- props: EMPTY_OBJ,
- attrs: EMPTY_OBJ,
- vnodeHooks: EMPTY_OBJ,
- slots: EMPTY_OBJ,
- refs: EMPTY_OBJ,
- // per-instance asset storage (mutable during options resolution)
- components: Object.create(appContext.components),
- directives: Object.create(appContext.directives),
- // async dependency management
- asyncDep: null,
- asyncResult: null,
- asyncResolved: false,
- // user namespace for storing whatever the user assigns to `this`
- // can also be used as a wildcard storage for ad-hoc injections internally
- sink: {},
- // 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,
- emit: (event, ...args): any[] => {
- const props = instance.vnode.props || EMPTY_OBJ
- let handler = props[`on${event}`] || props[`on${capitalize(event)}`]
- if (!handler && event.indexOf('update:') === 0) {
- event = hyphenate(event)
- handler = props[`on${event}`] || props[`on${capitalize(event)}`]
- }
- if (handler) {
- const res = callWithAsyncErrorHandling(
- handler,
- instance,
- ErrorCodes.COMPONENT_EVENT_HANDLER,
- args
- )
- return isArray(res) ? res : [res]
- } else {
- return []
- }
- }
- }
- instance.root = parent ? parent.root : instance
- return instance
- }
- export let currentInstance: ComponentInternalInstance | null = null
- export let currentSuspense: SuspenseBoundary | null = null
- export const getCurrentInstance: () => ComponentInternalInstance | null = () =>
- currentInstance || currentRenderingInstance
- export const setCurrentInstance = (
- instance: ComponentInternalInstance | null
- ) => {
- currentInstance = instance
- }
- 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 let isInSSRComponentSetup = false
- export function setupComponent(
- instance: ComponentInternalInstance,
- parentSuspense: SuspenseBoundary | null,
- isSSR = false
- ) {
- isInSSRComponentSetup = isSSR
- const propsOptions = instance.type.props
- const { props, children, shapeFlag } = instance.vnode
- resolveProps(instance, props, propsOptions)
- resolveSlots(instance, children)
- // setup stateful logic
- let setupResult
- if (shapeFlag & ShapeFlags.STATEFUL_COMPONENT) {
- setupResult = setupStatefulComponent(instance, parentSuspense, isSSR)
- }
- isInSSRComponentSetup = false
- return setupResult
- }
- function setupStatefulComponent(
- instance: ComponentInternalInstance,
- parentSuspense: SuspenseBoundary | null,
- 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])
- }
- }
- }
- // 0. create render proxy property access cache
- instance.accessCache = {}
- // 1. create public instance / render proxy
- instance.proxy = new Proxy(instance, PublicInstanceProxyHandlers)
- // 2. create props proxy
- // the propsProxy is a reactive AND readonly proxy to the actual props.
- // it will be updated in resolveProps() on updates before render
- const propsProxy = (instance.propsProxy = isSSR
- ? instance.props
- : shallowReadonly(instance.props))
- // 3. call setup()
- const { setup } = Component
- if (setup) {
- const setupContext = (instance.setupContext =
- setup.length > 1 ? createSetupContext(instance) : null)
- currentInstance = instance
- currentSuspense = parentSuspense
- pauseTracking()
- const setupResult = callWithErrorHandling(
- setup,
- instance,
- ErrorCodes.SETUP_FUNCTION,
- [propsProxy, setupContext]
- )
- resetTracking()
- currentInstance = null
- currentSuspense = null
- if (isPromise(setupResult)) {
- if (isSSR) {
- // return the promise so server-renderer can wait on it
- return setupResult.then(resolvedResult => {
- handleSetupResult(instance, resolvedResult, parentSuspense, isSSR)
- })
- } else if (__FEATURE_SUSPENSE__) {
- // async setup returned Promise.
- // bail here and wait for re-entry.
- instance.asyncDep = setupResult
- } 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, parentSuspense, isSSR)
- }
- } else {
- finishComponentSetup(instance, parentSuspense, isSSR)
- }
- }
- export function handleSetupResult(
- instance: ComponentInternalInstance,
- setupResult: unknown,
- parentSuspense: SuspenseBoundary | null,
- isSSR: boolean
- ) {
- if (isFunction(setupResult)) {
- // setup returned an inline render function
- instance.render = setupResult as RenderFunction
- } 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.
- instance.renderContext = reactive(setupResult)
- } else if (__DEV__ && setupResult !== undefined) {
- warn(
- `setup() should return an object. Received: ${
- setupResult === null ? 'null' : typeof setupResult
- }`
- )
- }
- finishComponentSetup(instance, parentSuspense, isSSR)
- }
- type CompileFunction = (
- template: string | object,
- options?: CompilerOptions
- ) => RenderFunction
- let compile: CompileFunction | undefined
- // exported method uses any to avoid d.ts relying on the compiler types.
- export function registerRuntimeCompiler(_compile: any) {
- compile = _compile
- }
- function finishComponentSetup(
- instance: ComponentInternalInstance,
- parentSuspense: SuspenseBoundary | null,
- isSSR: boolean
- ) {
- const Component = instance.type as ComponentOptions
- // template / render function normalization
- if (__NODE_JS__ && isSSR) {
- if (Component.render) {
- instance.render = Component.render as RenderFunction
- }
- } else if (!instance.render) {
- if (compile && Component.template && !Component.render) {
- Component.render = compile(Component.template, {
- isCustomElement: instance.appContext.config.isCustomElement || NO
- })
- // mark the function as runtime compiled
- ;(Component.render as RenderFunction)._rc = true
- }
- if (__DEV__ && !Component.render) {
- /* istanbul ignore if */
- if (!compile && Component.template) {
- warn(
- `Component provides template but the build of Vue you are running ` +
- `does not support runtime template compilation. Either use the ` +
- `full build or pre-compile the template using Vue CLI.`
- )
- } else {
- warn(`Component is missing template or render function.`)
- }
- }
- instance.render = (Component.render || NOOP) as RenderFunction
- // 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 (instance.render._rc) {
- instance.withProxy = new Proxy(
- instance,
- runtimeCompiledRenderProxyHandlers
- )
- }
- }
- // support for 2.x options
- if (__FEATURE_OPTIONS__) {
- currentInstance = instance
- currentSuspense = parentSuspense
- applyOptions(instance, Component)
- currentInstance = null
- currentSuspense = null
- }
- }
- // used to identify a setup context proxy
- export const SetupProxySymbol = Symbol()
- const SetupProxyHandlers: { [key: string]: ProxyHandler<any> } = {}
- ;['attrs', 'slots'].forEach((type: string) => {
- SetupProxyHandlers[type] = {
- get: (instance, key) => {
- if (__DEV__) {
- markAttrsAccessed()
- }
- return instance[type][key]
- },
- has: (instance, key) => key === SetupProxySymbol || key in instance[type],
- ownKeys: instance => Reflect.ownKeys(instance[type]),
- // this is necessary for ownKeys to work properly
- getOwnPropertyDescriptor: (instance, key) =>
- Reflect.getOwnPropertyDescriptor(instance[type], key),
- set: () => false,
- deleteProperty: () => false
- }
- })
- function createSetupContext(instance: ComponentInternalInstance): SetupContext {
- const context = {
- // attrs & slots are non-reactive, but they need to always expose
- // the latest values (instance.xxx may get replaced during updates) so we
- // need to expose them through a proxy
- attrs: new Proxy(instance, SetupProxyHandlers.attrs),
- slots: new Proxy(instance, SetupProxyHandlers.slots),
- get emit() {
- return instance.emit
- }
- }
- return __DEV__ ? Object.freeze(context) : context
- }
- // record effects created during a component's setup() so that they can be
- // stopped when the component unmounts
- export function recordInstanceBoundEffect(effect: ReactiveEffect) {
- if (currentInstance) {
- ;(currentInstance.effects || (currentInstance.effects = [])).push(effect)
- }
- }
|