vue3-core/packages/runtime-core/src/component.ts

1145 lines
30 KiB
TypeScript
Raw Normal View History

import { VNode, VNodeChild, isVNode } from './vnode'
import {
isRef,
pauseTracking,
resetTracking,
shallowReadonly,
proxyRefs,
EffectScope,
markRaw,
track,
TrackOpTypes,
ReactiveEffect
} from '@vue/reactivity'
2019-09-07 00:58:31 +08:00
import {
ComponentPublicInstance,
PublicInstanceProxyHandlers,
createDevRenderContext,
exposePropsOnRenderContext,
exposeSetupStateOnRenderContext,
ComponentPublicInstanceConstructor,
2021-07-21 23:11:40 +08:00
publicPropertiesMap,
RuntimeCompiledPublicInstanceProxyHandlers
} from './componentPublicInstance'
import {
ComponentPropsOptions,
NormalizedPropsOptions,
initProps,
normalizePropsOptions
} from './componentProps'
import {
initSlots,
InternalSlots,
Slots,
SlotsType,
UnwrapSlotsType
} 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,
EmitsToProps,
ShortEmitsToObject
} from './componentEmits'
import {
EMPTY_OBJ,
isArray,
isFunction,
NOOP,
isObject,
NO,
makeMap,
isPromise,
ShapeFlags,
extend,
getGlobalThis,
IfAny
} 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'
2021-04-11 23:15:40 +08:00
import { convertLegacyRenderFn } from './compat/renderFn'
import {
CompatConfig,
globalCompatConfig,
validateCompatConfig
} from './compat/compatConfig'
import { SchedulerJob } from './scheduler'
import { LifecycleHooks } from './enums'
2019-05-28 13:27:31 +08:00
export type Data = Record<string, unknown>
2019-05-29 10:43:27 +08:00
/**
* Public utility type for extracting the instance type of a component.
* Works with all valid component definition types. This is intended to replace
* the usage of `InstanceType<typeof Comp>` which only works for
* constructor-based component definition types.
*
* Exmaple:
* ```ts
* const MyComp = { ... }
* declare const instance: ComponentInstance<typeof MyComp>
* ```
*/
export type ComponentInstance<T> = T extends { new (): ComponentPublicInstance }
? InstanceType<T>
: T extends FunctionalComponent<infer Props, infer Emits>
2023-12-08 22:34:47 +08:00
? ComponentPublicInstance<Props, {}, {}, {}, {}, ShortEmitsToObject<Emits>>
: T extends Component<
infer Props,
infer RawBindings,
infer D,
infer C,
infer M
>
? // NOTE we override Props/RawBindings/D to make sure is not `unknown`
ComponentPublicInstance<
unknown extends Props ? {} : Props,
unknown extends RawBindings ? {} : RawBindings,
unknown extends D ? {} : D,
C,
M
>
: never // not a vue Component
/**
* 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
*/
2019-12-18 10:28:24 +08:00
__cssModules?: Data
/**
* @internal
*/
__hmrId?: string
2021-04-11 23:15:40 +08:00
/**
* 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<
P = {},
E extends EmitsOptions | Record<string, any[]> = {},
S extends Record<string, any> = any,
EE extends EmitsOptions = ShortEmitsToObject<E>
> extends ComponentInternalOptions {
// use of any here is intentional so it can be a valid JSX Element constructor
(
props: P & EmitsToProps<EE>,
ctx: Omit<SetupContext<EE, IfAny<S, {}, SlotsType<S>>>, 'expose'>
): any
2019-05-28 18:06:00 +08:00
props?: ComponentPropsOptions<P>
emits?: EE | (keyof EE)[]
slots?: IfAny<S, Slots, SlotsType<S>>
inheritAttrs?: boolean
2019-05-28 18:06:00 +08:00
displayName?: string
compatConfig?: CompatConfig
2019-05-28 18:06:00 +08:00
}
export interface ClassComponent {
new (...args: any[]): ComponentPublicInstance<any, any, any, any, any>
__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,
E extends EmitsOptions | Record<string, any[]> = {},
S extends Record<string, any> = any
> =
| ComponentOptions<Props, RawBindings, D, C, M>
| FunctionalComponent<Props, E, S>
/**
* 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,
E extends EmitsOptions | Record<string, any[]> = {},
S extends Record<string, any> = any
> =
| ConcreteComponent<Props, RawBindings, D, C, M, E, S>
| ComponentPublicInstanceConstructor<Props>
export type { ComponentOptions }
2019-09-03 04:09:34 +08:00
type LifecycleHook<TFn = Function> = TFn[] | null
2019-05-28 19:36:15 +08:00
// use `E extends any` to force evaluating type to fix #2362
export type SetupContext<
E = EmitsOptions,
S extends SlotsType = {}
> = E extends any
? {
attrs: Data
slots: UnwrapSlotsType<S>
emit: EmitFn<E>
expose: (exposed?: Record<string, any>) => void
}
: never
2019-06-19 16:43:34 +08:00
/**
* @internal
*/
export type InternalRenderFunction = {
(
ctx: ComponentPublicInstance,
2020-07-11 10:12:25 +08:00
cache: ComponentInternalInstance['renderCache'],
// for compiler-optimized bindings
$props: ComponentInternalInstance['props'],
$setup: ComponentInternalInstance['setupState'],
$data: ComponentInternalInstance['data'],
$options: ComponentInternalInstance['ctx']
): VNodeChild
_rc?: boolean // isRuntimeCompiled
2021-04-11 23:15:40 +08:00
// __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.
*/
2019-09-07 00:58:31 +08:00
export interface ComponentInternalInstance {
uid: number
type: ConcreteComponent
2019-09-07 00:58:31 +08:00
parent: ComponentInternalInstance | null
root: ComponentInternalInstance
appContext: AppContext
/**
* Vnode representing this component in its parent's vdom tree
*/
2019-05-29 10:43:27 +08:00
vnode: VNode
/**
* The pending new vnode from parent updates
* @internal
*/
2019-05-28 17:19:47 +08:00
next: VNode | null
/**
* Root vnode of this component's own vdom tree
*/
2019-05-29 10:43:27 +08:00
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
/**
2023-06-10 17:17:41 +08:00
* Object containing values this component provides for its descendants
* @internal
*/
2019-06-19 22:48:22 +08:00
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)[]
2019-06-19 16:43:34 +08:00
/**
* Resolved component registry, only for components with mixins or extends
* @internal
*/
components: Record<string, ConcreteComponent> | null
/**
* Resolved directive registry, only for components with mixins or extends
* @internal
*/
directives: Record<string, Directive> | null
2021-04-20 00:08:26 +08:00
/**
* Resolved filters registry, v2 compat only
* @internal
*/
filters?: Record<string, Function>
/**
2021-01-06 17:46:51 +08:00
* resolved props options
* @internal
*/
propsOptions: NormalizedPropsOptions
/**
* resolved emits options
* @internal
*/
emitsOptions: ObjectEmitsOptions | null
/**
* resolved inheritAttrs options
* @internal
*/
inheritAttrs?: boolean
2021-07-13 03:32:38 +08:00
/**
* is custom element?
* @internal
2021-07-13 03:32:38 +08:00
*/
isCE?: boolean
/**
* custom element specific HMR method
* @internal
*/
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<string, any> | null
exposeProxy: Record<string, any> | 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
2020-04-16 23:50:33 +08:00
refs: Data
emit: EmitFn
attrsProxy: Data | null
slotsProxy: Slots | null
/**
* 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
/**
* setup related
* @internal
*/
2020-04-16 23:50:33 +08:00
setupState: Data
/**
* devtools access to additional info
* @internal
*/
devtoolsRawSetupState?: any
/**
* @internal
*/
2020-04-16 23:50:33 +08:00
setupContext: SetupContext | null
/**
* suspense related
* @internal
*/
suspense: SuspenseBoundary | null
/**
* suspense pending batch id
* @internal
*/
suspenseId: number
/**
* @internal
*/
2019-10-30 10:28:38 +08:00
asyncDep: Promise<any> | null
/**
* @internal
*/
2019-10-30 10:28:38 +08:00
asyncResolved: boolean
// lifecycle
2019-11-23 12:32:53 +08:00
isMounted: boolean
isUnmounted: boolean
2019-10-30 10:28:38 +08:00
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<unknown>>
/**
* For caching bound $forceUpdate on public proxy access
* @internal
*/
f?: () => void
/**
* For caching bound $nextTick on public proxy access
* @internal
*/
n?: () => Promise<void>
/**
* `updateTeleportCssVars`
* For updating css vars on contained teleports
* @internal
*/
ut?: (vars?: Record<string, string>) => void
}
2019-05-28 19:36:15 +08:00
2019-09-03 04:09:34 +08:00
const emptyAppContext = createAppContext()
let uid = 0
export function createComponentInstance(
2019-08-29 00:13:36 +08:00
vnode: VNode,
parent: ComponentInternalInstance | null,
suspense: SuspenseBoundary | null
) {
const type = vnode.type as ConcreteComponent
2019-09-04 06:11:04 +08:00
// inherit parent app context - or - if root, adopt from root vnode
const appContext =
(parent ? parent.appContext : vnode.appContext) || emptyAppContext
const instance: ComponentInternalInstance = {
uid: uid++,
2019-08-29 00:13:36 +08:00
vnode,
type,
2019-06-03 09:43:28 +08:00
parent,
2019-09-04 06:11:04 +08:00
appContext,
root: null!, // to be immediately set
2019-05-28 19:36:15 +08:00
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,
2019-09-04 06:11:04 +08:00
provides: parent ? parent.provides : Object.create(appContext.provides),
accessCache: null!,
renderCache: [],
2019-05-28 19:36:15 +08:00
2022-05-10 10:51:51 +08:00
// 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,
2020-04-16 23:50:33 +08:00
// state
ctx: EMPTY_OBJ,
data: EMPTY_OBJ,
props: EMPTY_OBJ,
attrs: EMPTY_OBJ,
slots: EMPTY_OBJ,
refs: EMPTY_OBJ,
2020-04-16 23:50:33 +08:00
setupState: EMPTY_OBJ,
setupContext: null,
attrsProxy: null,
slotsProxy: null,
// suspense related
suspense,
suspenseId: suspense ? suspense.pendingId : 0,
2019-09-10 04:00:50 +08:00
asyncDep: null,
asyncResolved: false,
// lifecycle hooks
// not using enums here because it results in computed properties
2019-11-23 12:32:53 +08:00
isMounted: false,
isUnmounted: false,
2019-10-30 10:28:38 +08:00
isDeactivated: false,
bc: null,
c: null,
2019-05-28 19:36:15 +08:00
bm: null,
m: null,
bu: null,
u: null,
um: null,
bum: null,
da: null,
a: null,
rtg: null,
rtc: null,
ec: null,
sp: null
2019-05-28 19:36:15 +08:00
}
if (__DEV__) {
instance.ctx = createDevRenderContext(instance)
} else {
instance.ctx = { _: instance }
}
2019-06-03 09:43:28 +08:00
instance.root = parent ? parent.root : instance
instance.emit = emit.bind(null, instance)
2020-07-17 06:18:52 +08:00
2021-07-13 03:32:38 +08:00
// apply custom element special handling
if (vnode.ce) {
vnode.ce(instance)
}
2019-06-03 09:43:28 +08:00
return instance
2019-05-28 19:36:15 +08:00
}
2019-09-07 00:58:31 +08:00
export let currentInstance: ComponentInternalInstance | null = null
2019-05-28 19:36:15 +08:00
2019-09-07 00:58:31 +08:00
export const getCurrentInstance: () => ComponentInternalInstance | null = () =>
2019-11-23 12:32:53 +08:00
currentInstance || currentRenderingInstance
2019-06-20 15:25:10 +08:00
type GlobalInstanceSetter = ((
instance: ComponentInternalInstance | null
) => void) & { version?: string }
let internalSetCurrentInstance: GlobalInstanceSetter
let globalCurrentInstanceSetters: GlobalInstanceSetter[]
let settersKey = '__VUE_INSTANCE_SETTERS__'
/**
* The following makes getCurrentInstance() usage across multiple copies of Vue
* work. Some cases of how this can happen are summarized in #7590. In principle
* the duplication should be avoided, but in practice there are often cases
* where the user is unable to resolve on their own, especially in complicated
* SSR setups.
*
* Note this fix is technically incomplete, as we still rely on other singletons
* for effectScope and global reactive dependency maps. However, it does make
* some of the most common cases work. It also warns if the duplication is
* found during browser execution.
*/
if (__SSR__) {
if (!(globalCurrentInstanceSetters = getGlobalThis()[settersKey])) {
globalCurrentInstanceSetters = getGlobalThis()[settersKey] = []
}
globalCurrentInstanceSetters.push(i => (currentInstance = i))
internalSetCurrentInstance = instance => {
if (globalCurrentInstanceSetters.length > 1) {
globalCurrentInstanceSetters.forEach(s => s(instance))
} else {
globalCurrentInstanceSetters[0](instance)
}
}
} else {
internalSetCurrentInstance = i => {
currentInstance = i
}
}
export const setCurrentInstance = (instance: ComponentInternalInstance) => {
internalSetCurrentInstance(instance)
instance.scope.on()
}
export const unsetCurrentInstance = () => {
currentInstance && currentInstance.scope.off()
internalSetCurrentInstance(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
2020-01-24 11:23:10 +08:00
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)
2020-01-24 11:23:10 +08:00
const setupResult = isStateful
? setupStatefulComponent(instance, isSSR)
: undefined
isInSSRComponentSetup = false
return setupResult
2020-01-24 11:23:10 +08:00
}
function setupStatefulComponent(
instance: ComponentInternalInstance,
isSSR: boolean
2020-01-24 11:23:10 +08:00
) {
2019-05-29 10:43:27 +08:00
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()
2019-05-30 23:16:15 +08:00
const { setup } = Component
if (setup) {
2019-06-19 16:43:34 +08:00
const setupContext = (instance.setupContext =
setup.length > 1 ? createSetupContext(instance) : null)
2019-09-10 04:00:50 +08:00
setCurrentInstance(instance)
pauseTracking()
const setupResult = callWithErrorHandling(
setup,
instance,
2019-09-07 00:58:31 +08:00
ErrorCodes.SETUP_FUNCTION,
[__DEV__ ? shallowReadonly(instance.props) : instance.props, setupContext]
)
resetTracking()
unsetCurrentInstance()
2019-06-19 22:48:22 +08:00
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__) {
2019-09-10 04:28:32 +08:00
// async setup returned Promise.
// bail here and wait for re-entry.
2019-10-05 22:09:34 +08:00
instance.asyncDep = setupResult
if (__DEV__ && !instance.suspense) {
const name = Component.name ?? 'Anonymous'
warn(
`Component <${name}>: setup function returned a promise, but no ` +
`<Suspense> boundary was found in the parent component tree. ` +
`A component with async setup() must be nested in a <Suspense> ` +
`in order to be rendered.`
)
}
2019-09-10 04:28:32 +08:00
} 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)
}
2019-08-22 05:05:14 +08:00
} else {
finishComponentSetup(instance, isSSR)
2019-09-10 04:00:50 +08:00
}
}
export function handleSetupResult(
instance: ComponentInternalInstance,
setupResult: unknown,
isSSR: boolean
2019-09-10 04:00:50 +08:00
) {
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
}
2019-09-10 04:00:50 +08:00
} else if (isObject(setupResult)) {
if (__DEV__ && isVNode(setupResult)) {
warn(
`setup() should not return VNodes directly - ` +
`return a render function instead.`
)
}
2019-09-10 04:00:50 +08:00
// 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)
}
2019-09-10 04:00:50 +08:00
} else if (__DEV__ && setupResult !== undefined) {
warn(
`setup() should return an object. Received: ${
setupResult === null ? 'null' : typeof setupResult
}`
)
}
finishComponentSetup(instance, isSSR)
2019-09-10 04:00:50 +08:00
}
type CompileFunction = (
template: string | object,
options?: CompilerOptions
) => InternalRenderFunction
let compile: CompileFunction | undefined
2021-07-21 23:11:40 +08:00
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) {
2019-09-20 12:24:16 +08:00
compile = _compile
2021-07-21 23:11:40 +08:00
installWithProxy = i => {
if (i.render!._rc) {
i.withProxy = new Proxy(i.ctx, RuntimeCompiledPublicInstanceProxyHandlers)
}
}
2019-09-20 12:24:16 +08:00
}
2021-07-21 23:11:40 +08:00
// dev only
export const isRuntimeOnly = () => !compile
2021-04-05 23:54:35 +08:00
export function finishComponentSetup(
instance: ComponentInternalInstance,
2021-04-05 23:54:35 +08:00
isSSR: boolean,
skipOptions?: boolean
) {
2019-09-10 04:00:50 +08:00
const Component = instance.type as ComponentOptions
2021-04-11 23:15:40 +08:00
if (__COMPAT__) {
convertLegacyRenderFn(instance)
if (__DEV__ && Component.compatConfig) {
validateCompatConfig(Component.compatConfig)
}
2021-04-10 06:52:14 +08:00
}
// 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) {
2021-04-18 11:19:40 +08:00
const template =
(__COMPAT__ &&
instance.vnode.props &&
instance.vnode.props['inline-template']) ||
Component.template ||
resolveMergedOptions(instance).template
2021-04-18 11:19:40 +08:00
if (template) {
if (__DEV__) {
startMeasure(instance, `compile`)
}
const { isCustomElement, compilerOptions } = instance.appContext.config
2021-07-20 06:24:18 +08:00
const { delimiters, compilerOptions: componentCompilerOptions } =
Component
const finalCompilerOptions: CompilerOptions = extend(
extend(
{
isCustomElement,
delimiters
},
compilerOptions
),
componentCompilerOptions
)
2021-04-18 11:19:40 +08:00
if (__COMPAT__) {
// pass runtime compat config into the compiler
finalCompilerOptions.compatConfig = Object.create(globalCompatConfig)
2021-04-18 11:19:40 +08:00
if (Component.compatConfig) {
// @ts-expect-error types are not compatible
extend(finalCompilerOptions.compatConfig, Component.compatConfig)
2021-04-18 11:19:40 +08:00
}
}
Component.render = compile(template, finalCompilerOptions)
2021-04-18 11:19:40 +08:00
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.
2021-07-21 23:11:40 +08:00
if (installWithProxy) {
installWithProxy(instance)
}
2019-05-28 19:36:15 +08:00
}
2019-09-10 04:00:50 +08:00
2019-09-04 10:25:38 +08:00
// support for 2.x options
2021-04-05 23:54:35 +08:00
if (__FEATURE_OPTIONS_API__ && !(__COMPAT__ && skipOptions)) {
setCurrentInstance(instance)
pauseTracking()
try {
applyOptions(instance)
} finally {
resetTracking()
unsetCurrentInstance()
}
2019-09-04 10:25:38 +08:00
}
// 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.`)
}
}
2019-05-28 19:36:15 +08:00
}
2019-05-28 17:19:47 +08:00
function getAttrsProxy(instance: ComponentInternalInstance): Data {
return (
instance.attrsProxy ||
(instance.attrsProxy = 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]
}
}
))
)
}
/**
* Dev-only
*/
function getSlotsProxy(instance: ComponentInternalInstance): Slots {
return (
instance.slotsProxy ||
(instance.slotsProxy = new Proxy(instance.slots, {
get(target, key: string) {
track(instance, TrackOpTypes.GET, '$slots')
return target[key]
}
}))
)
}
export function createSetupContext(
instance: ComponentInternalInstance
): SetupContext {
const expose: SetupContext['expose'] = exposed => {
if (__DEV__) {
if (instance.exposed) {
warn(`expose() should be called only once per setup().`)
}
if (exposed != null) {
let exposedType: string = typeof exposed
if (exposedType === 'object') {
if (isArray(exposed)) {
exposedType = 'array'
} else if (isRef(exposed)) {
exposedType = 'ref'
}
}
if (exposedType !== 'object') {
warn(
`expose() should be passed a plain object, received ${exposedType}.`
)
}
}
}
instance.exposed = exposed || {}
}
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 getAttrsProxy(instance)
},
get slots() {
return getSlotsProxy(instance)
},
get emit() {
return (event: string, ...args: any[]) => instance.emit(event, ...args)
},
expose
})
} else {
return {
get attrs() {
return getAttrsProxy(instance)
},
slots: instance.slots,
emit: instance.emit,
expose
}
2019-10-05 22:09:34 +08:00
}
2019-06-19 16:43:34 +08:00
}
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)
}
},
has(target, key: string) {
return key in target || key in publicPropertiesMap
}
}))
)
}
}
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)
}
2020-07-15 22:38:45 +08:00
/* 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<string, any> | 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
}