826 lines
21 KiB
TypeScript
826 lines
21 KiB
TypeScript
import { VNode, VNodeChild, isVNode } from './vnode'
|
|
import {
|
|
ReactiveEffect,
|
|
pauseTracking,
|
|
resetTracking,
|
|
shallowReadonly,
|
|
proxyRefs
|
|
} from '@vue/reactivity'
|
|
import {
|
|
ComponentPublicInstance,
|
|
PublicInstanceProxyHandlers,
|
|
RuntimeCompiledPublicInstanceProxyHandlers,
|
|
createRenderContext,
|
|
exposePropsOnRenderContext,
|
|
exposeSetupStateOnRenderContext,
|
|
ComponentPublicInstanceConstructor
|
|
} from './componentPublicInstance'
|
|
import {
|
|
ComponentPropsOptions,
|
|
NormalizedPropsOptions,
|
|
initProps,
|
|
normalizePropsOptions
|
|
} from './componentProps'
|
|
import { Slots, initSlots, InternalSlots } from './componentSlots'
|
|
import { warn } from './warning'
|
|
import { ErrorCodes, callWithErrorHandling } from './errorHandling'
|
|
import { AppContext, createAppContext, AppConfig } from './apiCreateApp'
|
|
import { Directive, validateDirectiveName } from './directives'
|
|
import {
|
|
applyOptions,
|
|
ComponentOptions,
|
|
ComputedOptions,
|
|
MethodOptions
|
|
} from './componentOptions'
|
|
import {
|
|
EmitsOptions,
|
|
ObjectEmitsOptions,
|
|
EmitFn,
|
|
emit,
|
|
normalizeEmitsOptions
|
|
} from './componentEmits'
|
|
import {
|
|
EMPTY_OBJ,
|
|
isFunction,
|
|
NOOP,
|
|
isObject,
|
|
NO,
|
|
makeMap,
|
|
isPromise,
|
|
ShapeFlags
|
|
} from '@vue/shared'
|
|
import { SuspenseBoundary } from './components/Suspense'
|
|
import { CompilerOptions } from '@vue/compiler-core'
|
|
import {
|
|
currentRenderingInstance,
|
|
markAttrsAccessed
|
|
} from './componentRenderUtils'
|
|
import { startMeasure, endMeasure } from './profiling'
|
|
import { devtoolsComponentAdded } from './devtools'
|
|
|
|
export type Data = Record<string, unknown>
|
|
|
|
/**
|
|
* 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
|
|
*/
|
|
__props?: NormalizedPropsOptions
|
|
/**
|
|
* @internal
|
|
*/
|
|
__emits?: ObjectEmitsOptions | null
|
|
/**
|
|
* @internal
|
|
*/
|
|
__scopeId?: string
|
|
/**
|
|
* @internal
|
|
*/
|
|
__cssModules?: Data
|
|
/**
|
|
* @internal
|
|
*/
|
|
__hmrId?: string
|
|
/**
|
|
* This one should be exposed so that devtools can make use of it
|
|
*/
|
|
__file?: string
|
|
}
|
|
|
|
export interface FunctionalComponent<P = {}, E extends EmitsOptions = {}>
|
|
extends ComponentInternalOptions {
|
|
// use of any here is intentional so it can be a valid JSX Element constructor
|
|
(props: P, ctx: Omit<SetupContext<E, P>, 'expose'>): any
|
|
props?: ComponentPropsOptions<P>
|
|
emits?: E | (keyof E)[]
|
|
inheritAttrs?: boolean
|
|
displayName?: string
|
|
}
|
|
|
|
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
|
|
> =
|
|
| ComponentOptions<Props, RawBindings, D, C, M>
|
|
| FunctionalComponent<Props, any>
|
|
|
|
/**
|
|
* 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<Props, RawBindings, D, C, M>
|
|
| ComponentPublicInstanceConstructor<Props>
|
|
|
|
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 interface SetupContext<E = EmitsOptions, P = Data> {
|
|
props: P
|
|
attrs: Data
|
|
slots: Slots
|
|
emit: EmitFn<E>
|
|
expose: (exposed: Record<string, any>) => 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
|
|
}
|
|
|
|
/**
|
|
* 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
|
|
/**
|
|
* The reactive effect for rendering and patching the component. Callable.
|
|
*/
|
|
update: ReactiveEffect
|
|
/**
|
|
* The render function that returns vdom tree.
|
|
* @internal
|
|
*/
|
|
render: InternalRenderFunction | 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
|
|
*/
|
|
effects: ReactiveEffect[] | null
|
|
/**
|
|
* 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<string, ConcreteComponent> | null
|
|
/**
|
|
* Resolved directive registry, only for components with mixins or extends
|
|
* @internal
|
|
*/
|
|
directives: Record<string, Directive> | null
|
|
/**
|
|
* reoslved props options
|
|
* @internal
|
|
*/
|
|
propsOptions: NormalizedPropsOptions
|
|
/**
|
|
* resolved emits options
|
|
* @internal
|
|
*/
|
|
emitsOptions: ObjectEmitsOptions | null
|
|
|
|
// 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
|
|
|
|
/**
|
|
* 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<string, boolean> | null
|
|
|
|
/**
|
|
* 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<any> | 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
|
|
}
|
|
|
|
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
|
|
update: null!, // will be set synchronously right after creation
|
|
render: null,
|
|
proxy: null,
|
|
exposed: null,
|
|
withProxy: null,
|
|
effects: null,
|
|
provides: parent ? parent.provides : Object.create(appContext.provides),
|
|
accessCache: null!,
|
|
renderCache: [],
|
|
|
|
// local resovled assets
|
|
components: null,
|
|
directives: null,
|
|
|
|
// resolved props and emits options
|
|
propsOptions: normalizePropsOptions(type, appContext),
|
|
emitsOptions: normalizeEmitsOptions(type, appContext),
|
|
|
|
// emit
|
|
emit: null as any, // to be set immediately
|
|
emitted: null,
|
|
|
|
// 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
|
|
}
|
|
if (__DEV__) {
|
|
instance.ctx = createRenderContext(instance)
|
|
} else {
|
|
instance.ctx = { _: instance }
|
|
}
|
|
instance.root = parent ? parent.root : instance
|
|
instance.emit = emit.bind(null, instance)
|
|
|
|
if (__DEV__ || __FEATURE_PROD_DEVTOOLS__) {
|
|
devtoolsComponentAdded(instance)
|
|
}
|
|
|
|
return instance
|
|
}
|
|
|
|
export let currentInstance: ComponentInternalInstance | 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,
|
|
isSSR = false
|
|
) {
|
|
isInSSRComponentSetup = isSSR
|
|
|
|
const { props, children, shapeFlag } = instance.vnode
|
|
const isStateful = shapeFlag & ShapeFlags.STATEFUL_COMPONENT
|
|
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])
|
|
}
|
|
}
|
|
}
|
|
// 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 = 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)
|
|
|
|
currentInstance = instance
|
|
pauseTracking()
|
|
const setupResult = callWithErrorHandling(
|
|
setup,
|
|
instance,
|
|
ErrorCodes.SETUP_FUNCTION,
|
|
[__DEV__ ? shallowReadonly(instance.props) : instance.props, setupContext]
|
|
)
|
|
resetTracking()
|
|
currentInstance = null
|
|
|
|
if (isPromise(setupResult)) {
|
|
if (isSSR) {
|
|
// return the promise so server-renderer can wait on it
|
|
return setupResult.then((resolvedResult: unknown) => {
|
|
handleSetupResult(instance, resolvedResult, 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, isSSR)
|
|
}
|
|
} else {
|
|
finishComponentSetup(instance, isSSR)
|
|
}
|
|
}
|
|
|
|
export function handleSetupResult(
|
|
instance: ComponentInternalInstance,
|
|
setupResult: unknown,
|
|
isSSR: boolean
|
|
) {
|
|
if (isFunction(setupResult)) {
|
|
// setup returned an inline render function
|
|
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
|
|
|
|
/**
|
|
* 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
|
|
}
|
|
|
|
function finishComponentSetup(
|
|
instance: ComponentInternalInstance,
|
|
isSSR: boolean
|
|
) {
|
|
const Component = instance.type as ComponentOptions
|
|
|
|
// template / render function normalization
|
|
if (__NODE_JS__ && isSSR) {
|
|
if (Component.render) {
|
|
instance.render = Component.render as InternalRenderFunction
|
|
}
|
|
} else if (!instance.render) {
|
|
// could be set from setup()
|
|
if (compile && Component.template && !Component.render) {
|
|
if (__DEV__) {
|
|
startMeasure(instance, `compile`)
|
|
}
|
|
Component.render = compile(Component.template, {
|
|
isCustomElement: instance.appContext.config.isCustomElement,
|
|
delimiters: Component.delimiters
|
|
})
|
|
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 (instance.render._rc) {
|
|
instance.withProxy = new Proxy(
|
|
instance.ctx,
|
|
RuntimeCompiledPublicInstanceProxyHandlers
|
|
)
|
|
}
|
|
}
|
|
|
|
// support for 2.x options
|
|
if (__FEATURE_OPTIONS_API__) {
|
|
currentInstance = instance
|
|
applyOptions(instance, Component)
|
|
currentInstance = null
|
|
}
|
|
|
|
// warn missing template/render
|
|
if (__DEV__ && !Component.render && instance.render === NOOP) {
|
|
/* 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.`)
|
|
}
|
|
}
|
|
}
|
|
|
|
const attrHandlers: ProxyHandler<Data> = {
|
|
get: (target, key: string) => {
|
|
if (__DEV__) {
|
|
markAttrsAccessed()
|
|
}
|
|
return target[key]
|
|
},
|
|
set: () => {
|
|
warn(`setupContext.attrs is readonly.`)
|
|
return false
|
|
},
|
|
deleteProperty: () => {
|
|
warn(`setupContext.attrs is readonly.`)
|
|
return false
|
|
}
|
|
}
|
|
|
|
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 = proxyRefs(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 props() {
|
|
return instance.props
|
|
},
|
|
get attrs() {
|
|
return new Proxy(instance.attrs, attrHandlers)
|
|
},
|
|
get slots() {
|
|
return shallowReadonly(instance.slots)
|
|
},
|
|
get emit() {
|
|
return (event: string, ...args: any[]) => instance.emit(event, ...args)
|
|
},
|
|
expose
|
|
})
|
|
} else {
|
|
return {
|
|
props: instance.props,
|
|
attrs: instance.attrs,
|
|
slots: instance.slots,
|
|
emit: instance.emit,
|
|
expose
|
|
}
|
|
}
|
|
}
|
|
|
|
// 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)
|
|
}
|
|
}
|
|
|
|
const classifyRE = /(?:^|[-_])(\w)/g
|
|
const classify = (str: string): string =>
|
|
str.replace(classifyRE, c => c.toUpperCase()).replace(/[-_]/g, '')
|
|
|
|
/* istanbul ignore next */
|
|
export function formatComponentName(
|
|
instance: ComponentInternalInstance | null,
|
|
Component: ConcreteComponent,
|
|
isRoot = false
|
|
): string {
|
|
let name = isFunction(Component)
|
|
? Component.displayName || Component.name
|
|
: Component.name
|
|
if (!name && Component.__file) {
|
|
const match = Component.__file.match(/([^/\\]+)\.vue$/)
|
|
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
|
|
}
|