2024-09-19 01:15:17 +08:00
|
|
|
import { EffectScope, isRef } from '@vue/reactivity'
|
2024-12-01 16:41:51 +08:00
|
|
|
import { EMPTY_OBJ, isArray, isBuiltInTag, isFunction } from '@vue/shared'
|
2024-11-16 02:24:42 +08:00
|
|
|
import type { Block } from './block'
|
2023-12-10 01:33:18 +08:00
|
|
|
import {
|
|
|
|
type ComponentPropsOptions,
|
|
|
|
type NormalizedPropsOptions,
|
2024-03-16 18:54:36 +08:00
|
|
|
type NormalizedRawProps,
|
|
|
|
type RawProps,
|
2024-03-14 16:19:45 +08:00
|
|
|
initProps,
|
2023-12-10 01:33:18 +08:00
|
|
|
normalizePropsOptions,
|
|
|
|
} from './componentProps'
|
2024-02-04 21:18:57 +08:00
|
|
|
import {
|
|
|
|
type EmitFn,
|
|
|
|
type EmitsOptions,
|
|
|
|
type ObjectEmitsOptions,
|
|
|
|
emit,
|
|
|
|
normalizeEmitsOptions,
|
|
|
|
} from './componentEmits'
|
2024-06-19 01:09:17 +08:00
|
|
|
import { type RawSlots, type StaticSlots, initSlots } from './componentSlots'
|
2024-06-20 14:33:16 +08:00
|
|
|
import { VaporLifecycleHooks } from './enums'
|
2024-03-22 23:28:18 +08:00
|
|
|
import { warn } from './warning'
|
2024-05-29 01:43:47 +08:00
|
|
|
import {
|
|
|
|
type AppConfig,
|
|
|
|
type AppContext,
|
|
|
|
createAppContext,
|
|
|
|
} from './apiCreateVaporApp'
|
2024-05-11 23:14:26 +08:00
|
|
|
import type { Data } from '@vue/runtime-shared'
|
2023-11-30 02:11:21 +08:00
|
|
|
|
2023-12-10 01:33:18 +08:00
|
|
|
export type Component = FunctionalComponent | ObjectComponent
|
|
|
|
|
2024-12-02 09:36:49 +08:00
|
|
|
export type SetupFn = (
|
|
|
|
props: any,
|
|
|
|
ctx: SetupContext,
|
|
|
|
) => Block | Data | undefined
|
2024-04-28 18:43:16 +08:00
|
|
|
export type FunctionalComponent = SetupFn &
|
|
|
|
Omit<ObjectComponent, 'setup'> & {
|
|
|
|
displayName?: string
|
|
|
|
}
|
2024-02-14 14:43:18 +08:00
|
|
|
|
2024-12-01 17:00:38 +08:00
|
|
|
export class SetupContext<E = EmitsOptions> {
|
|
|
|
attrs: Data
|
|
|
|
emit: EmitFn<E>
|
|
|
|
slots: Readonly<StaticSlots>
|
|
|
|
expose: (exposed?: Record<string, any>) => void
|
|
|
|
|
|
|
|
constructor(instance: ComponentInternalInstance) {
|
|
|
|
this.attrs = instance.attrs
|
|
|
|
this.emit = instance.emit as EmitFn<E>
|
|
|
|
this.slots = instance.slots
|
|
|
|
this.expose = (exposed = {}) => {
|
|
|
|
instance.exposed = exposed
|
2024-03-22 23:28:18 +08:00
|
|
|
}
|
2024-12-01 17:00:38 +08:00
|
|
|
}
|
|
|
|
}
|
2024-03-22 23:28:18 +08:00
|
|
|
|
|
|
|
export function createSetupContext(
|
|
|
|
instance: ComponentInternalInstance,
|
|
|
|
): SetupContext {
|
|
|
|
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)
|
|
|
|
},
|
2024-03-24 20:29:00 +08:00
|
|
|
get slots() {
|
|
|
|
return getSlotsProxy(instance)
|
|
|
|
},
|
2024-03-22 23:28:18 +08:00
|
|
|
get emit() {
|
|
|
|
return (event: string, ...args: any[]) => instance.emit(event, ...args)
|
|
|
|
},
|
2024-12-01 17:00:38 +08:00
|
|
|
expose: (exposed?: Record<string, any>) => {
|
|
|
|
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 || {}
|
|
|
|
},
|
|
|
|
}) as SetupContext
|
2024-03-22 23:28:18 +08:00
|
|
|
} else {
|
2024-12-01 17:00:38 +08:00
|
|
|
return new SetupContext(instance)
|
2024-03-22 23:28:18 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-04-28 18:43:16 +08:00
|
|
|
export interface ObjectComponent extends ComponentInternalOptions {
|
|
|
|
setup?: SetupFn
|
2024-03-19 00:24:58 +08:00
|
|
|
inheritAttrs?: boolean
|
2024-04-28 18:43:16 +08:00
|
|
|
props?: ComponentPropsOptions
|
2024-02-14 14:43:18 +08:00
|
|
|
emits?: EmitsOptions
|
|
|
|
render?(ctx: any): Block
|
2024-04-28 18:43:16 +08:00
|
|
|
|
|
|
|
name?: string
|
2024-02-14 14:43:18 +08:00
|
|
|
vapor?: boolean
|
2023-12-06 14:59:11 +08:00
|
|
|
}
|
|
|
|
|
2024-04-28 18:43:16 +08:00
|
|
|
// 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
|
|
|
|
}
|
|
|
|
|
2023-12-15 01:47:56 +08:00
|
|
|
type LifecycleHook<TFn = Function> = TFn[] | null
|
|
|
|
|
2024-12-01 16:41:51 +08:00
|
|
|
export let currentInstance: ComponentInternalInstance | null = null
|
|
|
|
|
|
|
|
export const getCurrentInstance: () => ComponentInternalInstance | null = () =>
|
|
|
|
currentInstance
|
|
|
|
|
|
|
|
export const setCurrentInstance = (instance: ComponentInternalInstance) => {
|
|
|
|
const prev = currentInstance
|
|
|
|
currentInstance = instance
|
|
|
|
return (): void => {
|
|
|
|
currentInstance = prev
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
export const unsetCurrentInstance = (): void => {
|
|
|
|
currentInstance && currentInstance.scope.off()
|
|
|
|
currentInstance = null
|
|
|
|
}
|
|
|
|
|
|
|
|
const emptyAppContext = createAppContext()
|
|
|
|
|
|
|
|
let uid = 0
|
|
|
|
export class ComponentInternalInstance {
|
|
|
|
vapor = true
|
2024-03-19 00:24:58 +08:00
|
|
|
|
2023-11-30 02:11:21 +08:00
|
|
|
uid: number
|
2024-03-22 23:41:16 +08:00
|
|
|
appContext: AppContext
|
2024-03-16 18:54:36 +08:00
|
|
|
|
2024-06-21 14:03:11 +08:00
|
|
|
type: Component
|
2023-11-30 02:11:21 +08:00
|
|
|
block: Block | null
|
2024-03-16 18:54:36 +08:00
|
|
|
container: ParentNode
|
|
|
|
parent: ComponentInternalInstance | null
|
2024-06-21 14:03:11 +08:00
|
|
|
root: ComponentInternalInstance
|
2024-03-16 18:54:36 +08:00
|
|
|
|
2024-03-22 23:41:16 +08:00
|
|
|
provides: Data
|
2024-09-19 01:15:17 +08:00
|
|
|
scope: EffectScope
|
2024-03-16 18:54:36 +08:00
|
|
|
comps: Set<ComponentInternalInstance>
|
2024-11-15 01:21:30 +08:00
|
|
|
scopeIds: string[]
|
2024-02-04 21:18:57 +08:00
|
|
|
|
2024-03-16 18:54:36 +08:00
|
|
|
rawProps: NormalizedRawProps
|
2023-12-10 01:33:18 +08:00
|
|
|
propsOptions: NormalizedPropsOptions
|
2024-02-04 21:18:57 +08:00
|
|
|
emitsOptions: ObjectEmitsOptions | null
|
2023-12-10 01:33:18 +08:00
|
|
|
|
|
|
|
// state
|
|
|
|
setupState: Data
|
2024-03-22 23:28:18 +08:00
|
|
|
setupContext: SetupContext | null
|
2024-03-16 18:54:36 +08:00
|
|
|
props: Data
|
2024-02-04 21:18:57 +08:00
|
|
|
emit: EmitFn
|
|
|
|
emitted: Record<string, boolean> | null
|
2024-03-16 18:54:36 +08:00
|
|
|
attrs: Data
|
2024-11-14 20:01:10 +08:00
|
|
|
/**
|
|
|
|
* - `undefined` : no props
|
|
|
|
* - `false` : all props are static
|
|
|
|
* - `string[]` : list of props are dynamic
|
|
|
|
* - `true` : all props as dynamic
|
|
|
|
*/
|
|
|
|
dynamicAttrs?: string[] | boolean
|
2024-06-19 01:09:17 +08:00
|
|
|
slots: StaticSlots
|
2024-01-21 13:59:56 +08:00
|
|
|
refs: Data
|
2024-04-20 22:17:30 +08:00
|
|
|
// exposed properties via expose()
|
|
|
|
exposed?: Record<string, any>
|
2023-11-30 02:11:21 +08:00
|
|
|
|
2024-03-24 20:29:00 +08:00
|
|
|
attrsProxy?: Data
|
2024-06-19 01:09:17 +08:00
|
|
|
slotsProxy?: StaticSlots
|
2024-03-22 23:28:18 +08:00
|
|
|
|
2023-12-10 01:33:18 +08:00
|
|
|
// lifecycle
|
2024-01-20 20:46:41 +08:00
|
|
|
isMounted: boolean
|
|
|
|
isUnmounted: boolean
|
2024-01-13 03:25:57 +08:00
|
|
|
isUpdating: boolean
|
2023-12-23 15:17:18 +08:00
|
|
|
// TODO: registory of provides, lifecycles, ...
|
2023-12-15 01:47:56 +08:00
|
|
|
/**
|
|
|
|
* @internal
|
|
|
|
*/
|
2024-12-01 16:41:51 +08:00
|
|
|
// [VaporLifecycleHooks.BEFORE_MOUNT]: LifecycleHook;
|
|
|
|
bm: LifecycleHook
|
2023-12-15 01:47:56 +08:00
|
|
|
/**
|
|
|
|
* @internal
|
|
|
|
*/
|
2024-12-01 16:41:51 +08:00
|
|
|
// [VaporLifecycleHooks.MOUNTED]: LifecycleHook;
|
|
|
|
m: LifecycleHook
|
2023-12-15 01:47:56 +08:00
|
|
|
/**
|
|
|
|
* @internal
|
|
|
|
*/
|
2024-12-01 16:41:51 +08:00
|
|
|
// [VaporLifecycleHooks.BEFORE_UPDATE]: LifecycleHook;
|
|
|
|
bu: LifecycleHook
|
2023-12-15 01:47:56 +08:00
|
|
|
/**
|
|
|
|
* @internal
|
|
|
|
*/
|
2024-12-01 16:41:51 +08:00
|
|
|
// [VaporLifecycleHooks.UPDATED]: LifecycleHook;
|
|
|
|
u: LifecycleHook
|
2023-12-15 01:47:56 +08:00
|
|
|
/**
|
|
|
|
* @internal
|
|
|
|
*/
|
2024-12-01 16:41:51 +08:00
|
|
|
// [VaporLifecycleHooks.BEFORE_UNMOUNT]: LifecycleHook;
|
|
|
|
bum: LifecycleHook
|
2023-12-15 01:47:56 +08:00
|
|
|
/**
|
|
|
|
* @internal
|
|
|
|
*/
|
2024-12-01 16:41:51 +08:00
|
|
|
// [VaporLifecycleHooks.UNMOUNTED]: LifecycleHook;
|
|
|
|
um: LifecycleHook
|
2023-12-15 01:47:56 +08:00
|
|
|
/**
|
|
|
|
* @internal
|
|
|
|
*/
|
2024-12-01 16:41:51 +08:00
|
|
|
// [VaporLifecycleHooks.RENDER_TRACKED]: LifecycleHook;
|
|
|
|
rtc: LifecycleHook
|
2023-12-15 01:47:56 +08:00
|
|
|
/**
|
|
|
|
* @internal
|
|
|
|
*/
|
2024-12-01 16:41:51 +08:00
|
|
|
// [VaporLifecycleHooks.RENDER_TRIGGERED]: LifecycleHook;
|
|
|
|
rtg: LifecycleHook
|
2023-12-15 01:47:56 +08:00
|
|
|
/**
|
|
|
|
* @internal
|
|
|
|
*/
|
2024-12-01 16:41:51 +08:00
|
|
|
// [VaporLifecycleHooks.ACTIVATED]: LifecycleHook;
|
|
|
|
a: LifecycleHook
|
2023-12-15 01:47:56 +08:00
|
|
|
/**
|
|
|
|
* @internal
|
|
|
|
*/
|
2024-12-01 16:41:51 +08:00
|
|
|
// [VaporLifecycleHooks.DEACTIVATED]: LifecycleHook;
|
|
|
|
da: LifecycleHook
|
2023-12-15 01:47:56 +08:00
|
|
|
/**
|
|
|
|
* @internal
|
|
|
|
*/
|
2024-12-01 16:41:51 +08:00
|
|
|
// [VaporLifecycleHooks.ERROR_CAPTURED]: LifecycleHook
|
|
|
|
ec: LifecycleHook
|
|
|
|
|
|
|
|
constructor(
|
|
|
|
component: Component,
|
|
|
|
rawProps: RawProps | null,
|
|
|
|
slots: RawSlots | null,
|
|
|
|
once: boolean = false,
|
|
|
|
// application root node only
|
|
|
|
appContext?: AppContext,
|
|
|
|
) {
|
|
|
|
this.uid = uid++
|
|
|
|
const parent = (this.parent = currentInstance)
|
|
|
|
this.root = parent ? parent.root : this
|
|
|
|
const _appContext = (this.appContext =
|
|
|
|
(parent ? parent.appContext : appContext) || emptyAppContext)
|
|
|
|
this.block = null
|
|
|
|
this.container = null!
|
|
|
|
this.root = null!
|
|
|
|
this.scope = new EffectScope(true)
|
|
|
|
this.provides = parent
|
|
|
|
? parent.provides
|
|
|
|
: Object.create(_appContext.provides)
|
|
|
|
this.type = component
|
|
|
|
this.comps = new Set()
|
|
|
|
this.scopeIds = []
|
|
|
|
this.rawProps = null!
|
|
|
|
this.propsOptions = normalizePropsOptions(component)
|
|
|
|
this.emitsOptions = normalizeEmitsOptions(component)
|
2024-02-04 21:18:57 +08:00
|
|
|
|
2023-12-10 01:33:18 +08:00
|
|
|
// state
|
2024-12-01 16:41:51 +08:00
|
|
|
this.setupState = EMPTY_OBJ
|
|
|
|
this.setupContext = null
|
|
|
|
this.props = EMPTY_OBJ
|
|
|
|
this.emit = emit.bind(null, this)
|
|
|
|
this.emitted = null
|
|
|
|
this.attrs = EMPTY_OBJ
|
|
|
|
this.slots = EMPTY_OBJ
|
|
|
|
this.refs = EMPTY_OBJ
|
2023-12-10 01:33:18 +08:00
|
|
|
|
|
|
|
// lifecycle
|
2024-12-01 16:41:51 +08:00
|
|
|
this.isMounted = false
|
|
|
|
this.isUnmounted = false
|
|
|
|
this.isUpdating = false
|
|
|
|
this[VaporLifecycleHooks.BEFORE_MOUNT] = null
|
|
|
|
this[VaporLifecycleHooks.MOUNTED] = null
|
|
|
|
this[VaporLifecycleHooks.BEFORE_UPDATE] = null
|
|
|
|
this[VaporLifecycleHooks.UPDATED] = null
|
|
|
|
this[VaporLifecycleHooks.BEFORE_UNMOUNT] = null
|
|
|
|
this[VaporLifecycleHooks.UNMOUNTED] = null
|
|
|
|
this[VaporLifecycleHooks.RENDER_TRACKED] = null
|
|
|
|
this[VaporLifecycleHooks.RENDER_TRIGGERED] = null
|
|
|
|
this[VaporLifecycleHooks.ACTIVATED] = null
|
|
|
|
this[VaporLifecycleHooks.DEACTIVATED] = null
|
|
|
|
this[VaporLifecycleHooks.ERROR_CAPTURED] = null
|
|
|
|
|
|
|
|
initProps(this, rawProps, !isFunction(component), once)
|
|
|
|
initSlots(this, slots)
|
2023-11-30 02:11:21 +08:00
|
|
|
}
|
|
|
|
}
|
2024-03-22 23:28:18 +08:00
|
|
|
|
2024-04-25 04:57:45 +08:00
|
|
|
export function isVaporComponent(
|
|
|
|
val: unknown,
|
|
|
|
): val is ComponentInternalInstance {
|
2024-12-01 16:41:51 +08:00
|
|
|
return val instanceof ComponentInternalInstance
|
2024-04-25 04:57:45 +08:00
|
|
|
}
|
|
|
|
|
2024-05-29 01:43:47 +08:00
|
|
|
export function validateComponentName(
|
|
|
|
name: string,
|
|
|
|
{ isNativeTag }: AppConfig,
|
2024-08-09 16:56:59 +08:00
|
|
|
): void {
|
2024-05-29 01:43:47 +08:00
|
|
|
if (isBuiltInTag(name) || isNativeTag(name)) {
|
|
|
|
warn(
|
|
|
|
'Do not use built-in or reserved HTML elements as component id: ' + name,
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-11-16 06:58:26 +08:00
|
|
|
/**
|
|
|
|
* Dev-only
|
|
|
|
*/
|
|
|
|
export function getAttrsProxy(instance: ComponentInternalInstance): Data {
|
2024-03-22 23:28:18 +08:00
|
|
|
return (
|
|
|
|
instance.attrsProxy ||
|
2024-11-16 06:58:26 +08:00
|
|
|
(instance.attrsProxy = new Proxy(instance.attrs, {
|
|
|
|
get(target, key: string) {
|
|
|
|
return target[key]
|
|
|
|
},
|
|
|
|
set() {
|
|
|
|
warn(`setupContext.attrs is readonly.`)
|
|
|
|
return false
|
|
|
|
},
|
|
|
|
deleteProperty() {
|
|
|
|
warn(`setupContext.attrs is readonly.`)
|
|
|
|
return false
|
|
|
|
},
|
|
|
|
}))
|
2024-03-22 23:28:18 +08:00
|
|
|
)
|
|
|
|
}
|
2024-03-24 20:29:00 +08:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Dev-only
|
|
|
|
*/
|
2024-11-16 06:58:26 +08:00
|
|
|
export function getSlotsProxy(
|
|
|
|
instance: ComponentInternalInstance,
|
|
|
|
): StaticSlots {
|
2024-03-24 20:29:00 +08:00
|
|
|
return (
|
|
|
|
instance.slotsProxy ||
|
|
|
|
(instance.slotsProxy = new Proxy(instance.slots, {
|
|
|
|
get(target, key: string) {
|
|
|
|
return target[key]
|
|
|
|
},
|
|
|
|
}))
|
|
|
|
)
|
|
|
|
}
|
2024-06-16 16:50:36 +08:00
|
|
|
|
|
|
|
export function getComponentName(
|
|
|
|
Component: Component,
|
|
|
|
): string | false | undefined {
|
|
|
|
return isFunction(Component)
|
|
|
|
? Component.displayName || Component.name
|
2024-11-13 10:57:22 +08:00
|
|
|
: Component.name || Component.__name
|
2024-06-16 16:50:36 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
export function formatComponentName(
|
|
|
|
instance: ComponentInternalInstance | null,
|
|
|
|
Component: Component,
|
|
|
|
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.appContext.components)
|
|
|
|
}
|
|
|
|
|
|
|
|
return name ? classify(name) : isRoot ? `App` : `Anonymous`
|
|
|
|
}
|
|
|
|
|
|
|
|
const classifyRE = /(?:^|[-_])(\w)/g
|
|
|
|
const classify = (str: string): string =>
|
|
|
|
str.replace(classifyRE, c => c.toUpperCase()).replace(/[-_]/g, '')
|