| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556 |
- import {
- type ComponentInternalOptions,
- type ComponentPropsOptions,
- EffectScope,
- type EmitFn,
- type EmitsOptions,
- ErrorCodes,
- type GenericAppContext,
- type GenericComponentInstance,
- type LifecycleHook,
- type NormalizedPropsOptions,
- type ObjectEmitsOptions,
- type SuspenseBoundary,
- callWithErrorHandling,
- currentInstance,
- endMeasure,
- expose,
- nextUid,
- popWarningContext,
- pushWarningContext,
- queuePostFlushCb,
- registerHMR,
- simpleSetCurrentInstance,
- startMeasure,
- unregisterHMR,
- warn,
- } from '@vue/runtime-dom'
- import {
- type Block,
- insert,
- isBlock,
- parentsWithUnmountedChildren,
- remove,
- } from './block'
- import {
- type ShallowRef,
- markRaw,
- pauseTracking,
- proxyRefs,
- resetTracking,
- unref,
- } from '@vue/reactivity'
- import {
- EMPTY_ARR,
- EMPTY_OBJ,
- invokeArrayFns,
- isFunction,
- isString,
- remove as removeItem,
- } from '@vue/shared'
- import {
- type DynamicPropsSource,
- type RawProps,
- getKeysFromRawProps,
- getPropsProxyHandlers,
- hasFallthroughAttrs,
- normalizePropsOptions,
- resolveDynamicProps,
- setupPropsValidation,
- } from './componentProps'
- import { renderEffect } from './renderEffect'
- import { emit, normalizeEmitsOptions } from './componentEmits'
- import { setDynamicProps } from './dom/prop'
- import {
- type DynamicSlotSource,
- type RawSlots,
- type StaticSlots,
- type VaporSlot,
- dynamicSlotsProxyHandlers,
- getSlot,
- } from './componentSlots'
- import { hmrReload, hmrRerender } from './hmr'
- export { currentInstance } from '@vue/runtime-dom'
- export type VaporComponent = FunctionalVaporComponent | ObjectVaporComponent
- export type VaporSetupFn = (
- props: any,
- ctx: Pick<VaporComponentInstance, 'slots' | 'attrs' | 'emit' | 'expose'>,
- ) => Block | Record<string, any> | undefined
- export type FunctionalVaporComponent = VaporSetupFn &
- Omit<ObjectVaporComponent, 'setup'> & {
- displayName?: string
- } & SharedInternalOptions
- export interface ObjectVaporComponent
- extends ComponentInternalOptions,
- SharedInternalOptions {
- setup?: VaporSetupFn
- inheritAttrs?: boolean
- props?: ComponentPropsOptions
- emits?: EmitsOptions
- render?(
- ctx: any,
- props?: any,
- emit?: EmitFn,
- attrs?: any,
- slots?: Record<string, VaporSlot>,
- ): Block
- name?: string
- vapor?: boolean
- }
- interface SharedInternalOptions {
- /**
- * Cached normalized props options.
- * In vapor mode there are no mixins so normalized options can be cached
- * directly on the component
- */
- __propsOptions?: NormalizedPropsOptions
- /**
- * Cached normalized props proxy handlers.
- */
- __propsHandlers?: [ProxyHandler<any> | null, ProxyHandler<any>]
- /**
- * Cached normalized emits options.
- */
- __emitsOptions?: ObjectEmitsOptions
- }
- // In TypeScript, it is actually impossible to have a record type with only
- // specific properties that have a different type from the indexed type.
- // This makes our rawProps / rawSlots shape difficult to satisfy when calling
- // `createComponent` - luckily this is not user-facing, so we don't need to be
- // 100% strict. Here we use intentionally wider types to make `createComponent`
- // more ergonomic in tests and internal call sites, where we immediately cast
- // them into the stricter types.
- export type LooseRawProps = Record<
- string,
- (() => unknown) | DynamicPropsSource[]
- > & {
- $?: DynamicPropsSource[]
- }
- type LooseRawSlots = Record<string, VaporSlot | DynamicSlotSource[]> & {
- $?: DynamicSlotSource[]
- }
- export function createComponent(
- component: VaporComponent,
- rawProps?: LooseRawProps | null,
- rawSlots?: LooseRawSlots | null,
- isSingleRoot?: boolean,
- appContext?: GenericAppContext,
- ): VaporComponentInstance {
- // check if we are the single root of the parent
- // if yes, inject parent attrs as dynamic props source
- // TODO avoid child overwriting parent
- if (
- isSingleRoot &&
- component.inheritAttrs !== false &&
- isVaporComponent(currentInstance) &&
- currentInstance.hasFallthrough
- ) {
- const attrs = currentInstance.attrs
- if (rawProps) {
- ;((rawProps as RawProps).$ || ((rawProps as RawProps).$ = [])).push(
- () => attrs,
- )
- } else {
- rawProps = { $: [() => attrs] } as RawProps
- }
- }
- const instance = new VaporComponentInstance(
- component,
- rawProps as RawProps,
- rawSlots as RawSlots,
- appContext,
- )
- if (__DEV__) {
- pushWarningContext(instance)
- startMeasure(instance, `init`)
- }
- const prev = currentInstance
- simpleSetCurrentInstance(instance)
- pauseTracking()
- if (__DEV__) {
- setupPropsValidation(instance)
- }
- const setupFn = isFunction(component) ? component : component.setup
- const setupResult = setupFn
- ? callWithErrorHandling(setupFn, instance, ErrorCodes.SETUP_FUNCTION, [
- instance.props,
- instance,
- ]) || EMPTY_OBJ
- : EMPTY_OBJ
- if (__DEV__ && !isBlock(setupResult)) {
- if (isFunction(component)) {
- warn(`Functional vapor component must return a block directly.`)
- instance.block = []
- } else if (!component.render) {
- warn(
- `Vapor component setup() returned non-block value, and has no render function.`,
- )
- instance.block = []
- } else {
- instance.devtoolsRawSetupState = setupResult
- // TODO make the proxy warn non-existent property access during dev
- instance.setupState = proxyRefs(setupResult)
- devRender(instance)
- // HMR
- if (component.__hmrId) {
- registerHMR(instance)
- instance.isSingleRoot = isSingleRoot
- instance.hmrRerender = hmrRerender.bind(null, instance)
- instance.hmrReload = hmrReload.bind(null, instance)
- }
- }
- } else {
- // component has a render function but no setup function
- // (typically components with only a template and no state)
- if (!setupFn && component.render) {
- instance.block = callWithErrorHandling(
- component.render,
- instance,
- ErrorCodes.RENDER_FUNCTION,
- )
- } else {
- // in prod result can only be block
- instance.block = setupResult as Block
- }
- }
- // single root, inherit attrs
- if (
- instance.hasFallthrough &&
- component.inheritAttrs !== false &&
- instance.block instanceof Element &&
- Object.keys(instance.attrs).length
- ) {
- renderEffect(() => {
- isApplyingFallthroughProps = true
- setDynamicProps(instance.block as Element, [instance.attrs])
- isApplyingFallthroughProps = false
- })
- }
- resetTracking()
- simpleSetCurrentInstance(prev, instance)
- if (__DEV__) {
- popWarningContext()
- endMeasure(instance, 'init')
- }
- return instance
- }
- export let isApplyingFallthroughProps = false
- /**
- * dev only
- */
- export function devRender(instance: VaporComponentInstance): void {
- instance.block =
- callWithErrorHandling(
- instance.type.render!,
- instance,
- ErrorCodes.RENDER_FUNCTION,
- [
- instance.setupState,
- instance.props,
- instance.emit,
- instance.attrs,
- instance.slots,
- ],
- ) || []
- }
- const emptyContext: GenericAppContext = {
- app: null as any,
- config: {},
- provides: /*@__PURE__*/ Object.create(null),
- }
- export class VaporComponentInstance implements GenericComponentInstance {
- vapor: true
- uid: number
- type: VaporComponent
- parent: GenericComponentInstance | null
- children: VaporComponentInstance[] // TODO handle vdom children
- appContext: GenericAppContext
- block: Block
- scope: EffectScope
- rawProps: RawProps
- rawSlots: RawSlots
- props: Record<string, any>
- attrs: Record<string, any>
- propsDefaults: Record<string, any> | null
- rawPropsRef?: ShallowRef<any> // to hold vnode props in vdom interop mode
- slots: StaticSlots
- emit: EmitFn
- emitted: Record<string, boolean> | null
- expose: (exposed: Record<string, any>) => void
- exposed: Record<string, any> | null
- exposeProxy: Record<string, any> | null
- // for useTemplateRef()
- refs: Record<string, any>
- // for provide / inject
- provides: Record<string, any>
- // for useId
- ids: [string, number, number]
- // for suspense
- suspense: SuspenseBoundary | null
- hasFallthrough: boolean
- // lifecycle hooks
- isMounted: boolean
- isUnmounted: boolean
- isDeactivated: boolean
- isUpdating: boolean
- bc?: LifecycleHook // LifecycleHooks.BEFORE_CREATE
- c?: LifecycleHook // LifecycleHooks.CREATED
- bm?: LifecycleHook // LifecycleHooks.BEFORE_MOUNT
- m?: LifecycleHook // LifecycleHooks.MOUNTED
- bu?: LifecycleHook // LifecycleHooks.BEFORE_UPDATE
- u?: LifecycleHook // LifecycleHooks.UPDATED
- um?: LifecycleHook // LifecycleHooks.BEFORE_UNMOUNT
- bum?: LifecycleHook // LifecycleHooks.UNMOUNTED
- da?: LifecycleHook // LifecycleHooks.DEACTIVATED
- a?: LifecycleHook // LifecycleHooks.ACTIVATED
- rtg?: LifecycleHook // LifecycleHooks.RENDER_TRACKED
- rtc?: LifecycleHook // LifecycleHooks.RENDER_TRIGGERED
- ec?: LifecycleHook // LifecycleHooks.ERROR_CAPTURED
- sp?: LifecycleHook<() => Promise<unknown>> // LifecycleHooks.SERVER_PREFETCH
- // dev only
- setupState?: Record<string, any>
- devtoolsRawSetupState?: any
- hmrRerender?: () => void
- hmrReload?: (newComp: VaporComponent) => void
- propsOptions?: NormalizedPropsOptions
- emitsOptions?: ObjectEmitsOptions | null
- isSingleRoot?: boolean
- constructor(
- comp: VaporComponent,
- rawProps?: RawProps | null,
- rawSlots?: RawSlots | null,
- appContext?: GenericAppContext,
- ) {
- this.vapor = true
- this.uid = nextUid()
- this.type = comp
- this.parent = currentInstance // TODO proper parent source when inside vdom instance
- this.children = []
- if (currentInstance) {
- if (isVaporComponent(currentInstance)) {
- currentInstance.children.push(this)
- }
- this.appContext = currentInstance.appContext
- this.provides = currentInstance.provides
- this.ids = currentInstance.ids
- } else {
- this.appContext = appContext || emptyContext
- this.provides = Object.create(this.appContext.provides)
- this.ids = ['', 0, 0]
- }
- this.block = null! // to be set
- this.scope = new EffectScope(true)
- this.emit = emit.bind(null, this)
- this.expose = expose.bind(null, this)
- this.refs = EMPTY_OBJ
- this.emitted =
- this.exposed =
- this.exposeProxy =
- this.propsDefaults =
- this.suspense =
- null
- this.isMounted =
- this.isUnmounted =
- this.isUpdating =
- this.isDeactivated =
- false
- // init props
- this.rawProps = rawProps || EMPTY_OBJ
- this.hasFallthrough = hasFallthroughAttrs(comp, rawProps)
- if (rawProps || comp.props) {
- const [propsHandlers, attrsHandlers] = getPropsProxyHandlers(comp)
- this.attrs = new Proxy(this, attrsHandlers)
- this.props = comp.props
- ? new Proxy(this, propsHandlers!)
- : isFunction(comp)
- ? this.attrs
- : EMPTY_OBJ
- } else {
- this.props = this.attrs = EMPTY_OBJ
- }
- // init slots
- this.rawSlots = rawSlots || EMPTY_OBJ
- this.slots = rawSlots
- ? rawSlots.$
- ? new Proxy(rawSlots, dynamicSlotsProxyHandlers)
- : rawSlots
- : EMPTY_OBJ
- if (__DEV__) {
- // cache normalized options for dev only emit check
- this.propsOptions = normalizePropsOptions(comp)
- this.emitsOptions = normalizeEmitsOptions(comp)
- }
- }
- /**
- * Expose `getKeysFromRawProps` on the instance so it can be used in code
- * paths where it's needed, e.g. `useModel`
- */
- rawKeys(): string[] {
- return getKeysFromRawProps(this.rawProps)
- }
- }
- export function isVaporComponent(
- value: unknown,
- ): value is VaporComponentInstance {
- return value instanceof VaporComponentInstance
- }
- /**
- * Used when a component cannot be resolved at compile time
- * and needs rely on runtime resolution - where it might fallback to a plain
- * element if the resolution fails.
- */
- export function createComponentWithFallback(
- comp: VaporComponent | string,
- rawProps?: RawProps | null,
- rawSlots?: RawSlots | null,
- isSingleRoot?: boolean,
- ): HTMLElement | VaporComponentInstance {
- if (!isString(comp)) {
- return createComponent(comp, rawProps, rawSlots, isSingleRoot)
- }
- const el = document.createElement(comp)
- // mark single root
- ;(el as any).$root = isSingleRoot
- if (rawProps) {
- renderEffect(() => {
- setDynamicProps(el, [resolveDynamicProps(rawProps)])
- })
- }
- if (rawSlots) {
- if (rawSlots.$) {
- // TODO dynamic slot fragment
- } else {
- insert(getSlot(rawSlots, 'default')!(), el)
- }
- }
- return el
- }
- export function mountComponent(
- instance: VaporComponentInstance,
- parent: ParentNode,
- anchor?: Node | null | 0,
- ): void {
- if (__DEV__) {
- startMeasure(instance, `mount`)
- }
- if (!instance.isMounted) {
- if (instance.bm) invokeArrayFns(instance.bm)
- insert(instance.block, parent, anchor)
- if (instance.m) queuePostFlushCb(() => invokeArrayFns(instance.m!))
- instance.isMounted = true
- } else {
- insert(instance.block, parent, anchor)
- }
- if (__DEV__) {
- endMeasure(instance, `mount`)
- }
- }
- export function unmountComponent(
- instance: VaporComponentInstance,
- parentNode?: ParentNode,
- ): void {
- if (instance.isMounted && !instance.isUnmounted) {
- if (__DEV__ && instance.type.__hmrId) {
- unregisterHMR(instance)
- }
- if (instance.bum) {
- invokeArrayFns(instance.bum)
- }
- instance.scope.stop()
- for (const c of instance.children) {
- unmountComponent(c)
- }
- instance.children = EMPTY_ARR as any
- if (parentNode) {
- // root remove: need to both remove this instance's DOM nodes
- // and also remove it from the parent's children list.
- remove(instance.block, parentNode)
- const parentInstance = instance.parent
- instance.parent = null
- if (isVaporComponent(parentInstance)) {
- if (parentsWithUnmountedChildren) {
- // for optimize children removal
- parentsWithUnmountedChildren.add(parentInstance)
- } else {
- removeItem(parentInstance.children, instance)
- }
- }
- }
- if (instance.um) {
- queuePostFlushCb(() => invokeArrayFns(instance.um!))
- }
- instance.isUnmounted = true
- } else if (parentNode) {
- remove(instance.block, parentNode)
- }
- }
- export function getExposed(
- instance: GenericComponentInstance,
- ): Record<string, any> | undefined {
- if (instance.exposed) {
- return (
- instance.exposeProxy ||
- (instance.exposeProxy = new Proxy(markRaw(instance.exposed), {
- get: (target, key) => unref(target[key as any]),
- }))
- )
- }
- }
|