component.ts 29 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090
  1. import {
  2. type AsyncComponentInternalOptions,
  3. type ComponentInternalOptions,
  4. type ComponentObjectPropsOptions,
  5. type ComponentPropsOptions,
  6. EffectScope,
  7. type EmitFn,
  8. type EmitsOptions,
  9. type EmitsToProps,
  10. ErrorCodes,
  11. type ExtractPropTypes,
  12. type GenericAppContext,
  13. type GenericComponentInstance,
  14. type LifecycleHook,
  15. NULL_DYNAMIC_COMPONENT,
  16. type NormalizedPropsOptions,
  17. type ObjectEmitsOptions,
  18. type ShallowUnwrapRef,
  19. type SuspenseBoundary,
  20. callWithErrorHandling,
  21. currentInstance,
  22. endMeasure,
  23. expose,
  24. getComponentName,
  25. getFunctionalFallthrough,
  26. isAsyncWrapper,
  27. isKeepAlive,
  28. markAsyncBoundary,
  29. nextUid,
  30. popWarningContext,
  31. pushWarningContext,
  32. queuePostFlushCb,
  33. registerHMR,
  34. setCurrentInstance,
  35. startMeasure,
  36. unregisterHMR,
  37. warn,
  38. warnExtraneousAttributes,
  39. } from '@vue/runtime-dom'
  40. import {
  41. type Block,
  42. insert,
  43. isBlock,
  44. remove,
  45. setComponentScopeId,
  46. setScopeId,
  47. } from './block'
  48. import {
  49. type ShallowRef,
  50. markRaw,
  51. onScopeDispose,
  52. proxyRefs,
  53. setActiveSub,
  54. toRaw,
  55. unref,
  56. } from '@vue/reactivity'
  57. import {
  58. EMPTY_OBJ,
  59. type Prettify,
  60. ShapeFlags,
  61. hasOwn,
  62. invokeArrayFns,
  63. isArray,
  64. isFunction,
  65. isPromise,
  66. isString,
  67. } from '@vue/shared'
  68. import {
  69. type DynamicPropsSource,
  70. type RawProps,
  71. getKeysFromRawProps,
  72. getPropsProxyHandlers,
  73. hasFallthroughAttrs,
  74. normalizePropsOptions,
  75. resolveDynamicProps,
  76. setupPropsValidation,
  77. } from './componentProps'
  78. import { type RenderEffect, renderEffect } from './renderEffect'
  79. import { emit, normalizeEmitsOptions } from './componentEmits'
  80. import { setDynamicProps } from './dom/prop'
  81. import {
  82. type DynamicSlotSource,
  83. type RawSlots,
  84. type StaticSlots,
  85. type VaporSlot,
  86. dynamicSlotsProxyHandlers,
  87. getScopeOwner,
  88. getSlot,
  89. setCurrentSlotOwner,
  90. } from './componentSlots'
  91. import { hmrReload, hmrRerender } from './hmr'
  92. import {
  93. adoptTemplate,
  94. advanceHydrationNode,
  95. currentHydrationNode,
  96. isHydrating,
  97. locateHydrationNode,
  98. locateNextNode,
  99. setCurrentHydrationNode,
  100. } from './dom/hydration'
  101. import { createComment, createElement, createTextNode } from './dom/node'
  102. import {
  103. type TeleportFragment,
  104. isTeleportFragment,
  105. isVaporTeleport,
  106. } from './components/Teleport'
  107. import type { KeepAliveInstance } from './components/KeepAlive'
  108. import {
  109. currentKeepAliveCtx,
  110. setCurrentKeepAliveCtx,
  111. } from './components/KeepAlive'
  112. import {
  113. insertionAnchor,
  114. insertionParent,
  115. isLastInsertion,
  116. resetInsertionState,
  117. } from './insertionState'
  118. import type {
  119. DefineVaporComponent,
  120. VaporRenderResult,
  121. } from './apiDefineComponent'
  122. import { DynamicFragment, isFragment } from './fragment'
  123. import type { VaporElement } from './apiDefineCustomElement'
  124. import { parentSuspense, setParentSuspense } from './components/Suspense'
  125. export { currentInstance } from '@vue/runtime-dom'
  126. export type VaporComponent =
  127. | FunctionalVaporComponent
  128. | ObjectVaporComponent
  129. | DefineVaporComponent
  130. export type FunctionalVaporComponent<
  131. Props = {},
  132. Emits extends EmitsOptions = {},
  133. Slots extends StaticSlots = StaticSlots,
  134. Exposed extends Record<string, any> = Record<string, any>,
  135. > = ((
  136. props: Readonly<Props & EmitsToProps<Emits>>,
  137. ctx: {
  138. emit: EmitFn<Emits>
  139. slots: Slots
  140. attrs: Record<string, any>
  141. expose: <T extends Record<string, any> = Exposed>(exposed: T) => void
  142. },
  143. ) => VaporRenderResult) &
  144. Omit<
  145. ObjectVaporComponent<ComponentPropsOptions<Props>, Emits, string, Slots>,
  146. 'setup'
  147. > & {
  148. displayName?: string
  149. } & SharedInternalOptions
  150. export interface ObjectVaporComponent<
  151. Props = {},
  152. Emits extends EmitsOptions = {},
  153. RuntimeEmitsKeys extends string = string,
  154. Slots extends StaticSlots = StaticSlots,
  155. Exposed extends Record<string, any> = Record<string, any>,
  156. TypeBlock extends Block = Block,
  157. InferredProps = ComponentObjectPropsOptions extends Props
  158. ? {}
  159. : ExtractPropTypes<Props>,
  160. >
  161. extends
  162. ComponentInternalOptions,
  163. AsyncComponentInternalOptions<ObjectVaporComponent, VaporComponentInstance>,
  164. SharedInternalOptions {
  165. inheritAttrs?: boolean
  166. props?: Props
  167. emits?: Emits | RuntimeEmitsKeys[]
  168. slots?: Slots
  169. setup?: (
  170. props: Readonly<InferredProps>,
  171. ctx: {
  172. emit: EmitFn<Emits>
  173. slots: Slots
  174. attrs: Record<string, any>
  175. expose: <T extends Record<string, any> = Exposed>(exposed: T) => void
  176. },
  177. ) => TypeBlock | Exposed | Promise<Exposed> | void
  178. render?(
  179. ctx: Exposed extends Block ? undefined : ShallowUnwrapRef<Exposed>,
  180. props: Readonly<InferredProps>,
  181. emit: EmitFn<Emits>,
  182. attrs: any,
  183. slots: Slots,
  184. ): VaporRenderResult<TypeBlock> | void
  185. name?: string
  186. vapor?: boolean
  187. components?: Record<string, VaporComponent>
  188. /**
  189. * @internal custom element interception hook
  190. */
  191. ce?: (instance: VaporComponentInstance) => void
  192. }
  193. interface SharedInternalOptions {
  194. /**
  195. * Cached normalized props options.
  196. * In vapor mode there are no mixins so normalized options can be cached
  197. * directly on the component
  198. */
  199. __propsOptions?: NormalizedPropsOptions
  200. /**
  201. * Cached normalized props proxy handlers.
  202. */
  203. __propsHandlers?: [ProxyHandler<any> | null, ProxyHandler<any>]
  204. /**
  205. * Cached normalized emits options.
  206. */
  207. __emitsOptions?: ObjectEmitsOptions
  208. }
  209. // In TypeScript, it is actually impossible to have a record type with only
  210. // specific properties that have a different type from the indexed type.
  211. // This makes our rawProps / rawSlots shape difficult to satisfy when calling
  212. // `createComponent` - luckily this is not user-facing, so we don't need to be
  213. // 100% strict. Here we use intentionally wider types to make `createComponent`
  214. // more ergonomic in tests and internal call sites, where we immediately cast
  215. // them into the stricter types.
  216. export type LooseRawProps = Record<
  217. string,
  218. (() => unknown) | DynamicPropsSource[]
  219. > & {
  220. $?: DynamicPropsSource[]
  221. }
  222. export type LooseRawSlots = Record<string, VaporSlot | DynamicSlotSource[]> & {
  223. $?: DynamicSlotSource[]
  224. }
  225. export function createComponent(
  226. component: VaporComponent,
  227. rawProps?: LooseRawProps | null,
  228. rawSlots?: LooseRawSlots | null,
  229. isSingleRoot?: boolean,
  230. once?: boolean,
  231. appContext: GenericAppContext = (currentInstance &&
  232. currentInstance.appContext) ||
  233. emptyContext,
  234. ): VaporComponentInstance {
  235. const _insertionParent = insertionParent
  236. const _insertionAnchor = insertionAnchor
  237. const _isLastInsertion = isLastInsertion
  238. if (isHydrating) {
  239. locateHydrationNode()
  240. } else {
  241. resetInsertionState()
  242. }
  243. let prevSuspense: SuspenseBoundary | null = null
  244. if (__FEATURE_SUSPENSE__ && currentInstance && currentInstance.suspense) {
  245. prevSuspense = setParentSuspense(currentInstance.suspense)
  246. }
  247. if (
  248. (isSingleRoot ||
  249. // transition has attrs fallthrough
  250. (currentInstance && isVaporTransition(currentInstance!.type))) &&
  251. component.inheritAttrs !== false &&
  252. isVaporComponent(currentInstance) &&
  253. currentInstance.hasFallthrough
  254. ) {
  255. // check if we are the single root of the parent
  256. // if yes, inject parent attrs as dynamic props source
  257. const attrs = currentInstance.attrs
  258. if (rawProps && rawProps !== EMPTY_OBJ) {
  259. ;((rawProps as RawProps).$ || ((rawProps as RawProps).$ = [])).push(
  260. () => attrs,
  261. )
  262. } else {
  263. rawProps = { $: [() => attrs] } as RawProps
  264. }
  265. }
  266. // keep-alive
  267. if (
  268. currentInstance &&
  269. currentInstance.vapor &&
  270. isKeepAlive(currentInstance)
  271. ) {
  272. const cached = (
  273. currentInstance as KeepAliveInstance
  274. ).ctx.getCachedComponent(component)
  275. // @ts-expect-error
  276. if (cached) return cached
  277. }
  278. // vdom interop enabled and component is not an explicit vapor component
  279. if (appContext.vapor && !component.__vapor) {
  280. const frag = appContext.vapor.vdomMount(
  281. component as any,
  282. currentInstance as any,
  283. rawProps,
  284. rawSlots,
  285. isSingleRoot,
  286. )
  287. if (!isHydrating) {
  288. if (_insertionParent) insert(frag, _insertionParent, _insertionAnchor)
  289. } else {
  290. frag.hydrate()
  291. if (_isLastInsertion) {
  292. advanceHydrationNode(_insertionParent!)
  293. }
  294. }
  295. return frag
  296. }
  297. // teleport
  298. if (isVaporTeleport(component)) {
  299. const frag = component.process(rawProps!, rawSlots!)
  300. if (!isHydrating) {
  301. if (_insertionParent) insert(frag, _insertionParent, _insertionAnchor)
  302. } else {
  303. frag.hydrate()
  304. if (_isLastInsertion) {
  305. advanceHydrationNode(_insertionParent!)
  306. }
  307. }
  308. return frag as any
  309. }
  310. const instance = new VaporComponentInstance(
  311. component,
  312. rawProps as RawProps,
  313. rawSlots as RawSlots,
  314. appContext,
  315. once,
  316. )
  317. // handle currentKeepAliveCtx for component boundary isolation
  318. // AsyncWrapper should NOT clear currentKeepAliveCtx so its internal
  319. // DynamicFragment can capture it
  320. if (currentKeepAliveCtx && !isAsyncWrapper(instance)) {
  321. currentKeepAliveCtx.processShapeFlag(instance)
  322. // clear currentKeepAliveCtx so child components don't associate
  323. // with parent's KeepAlive
  324. setCurrentKeepAliveCtx(null)
  325. }
  326. // reset currentSlotOwner to null to avoid affecting the child components
  327. const prevSlotOwner = setCurrentSlotOwner(null)
  328. // HMR
  329. if (__DEV__) {
  330. registerHMR(instance)
  331. instance.isSingleRoot = isSingleRoot
  332. instance.hmrRerender = hmrRerender.bind(null, instance)
  333. instance.hmrReload = hmrReload.bind(null, instance)
  334. pushWarningContext(instance)
  335. startMeasure(instance, `init`)
  336. // cache normalized options for dev only emit check
  337. instance.propsOptions = normalizePropsOptions(component)
  338. instance.emitsOptions = normalizeEmitsOptions(component)
  339. }
  340. // hydrating async component
  341. if (
  342. isHydrating &&
  343. isAsyncWrapper(instance) &&
  344. component.__asyncHydrate &&
  345. !component.__asyncResolved
  346. ) {
  347. component.__asyncHydrate(currentHydrationNode as Element, instance, () =>
  348. setupComponent(instance, component),
  349. )
  350. } else {
  351. setupComponent(instance, component)
  352. }
  353. if (__DEV__) {
  354. popWarningContext()
  355. endMeasure(instance, 'init')
  356. }
  357. if (__FEATURE_SUSPENSE__ && currentInstance && currentInstance.suspense) {
  358. setParentSuspense(prevSuspense)
  359. }
  360. // restore currentSlotOwner to previous value after setupFn is called
  361. setCurrentSlotOwner(prevSlotOwner)
  362. onScopeDispose(() => unmountComponent(instance), true)
  363. if (_insertionParent || isHydrating) {
  364. mountComponent(instance, _insertionParent!, _insertionAnchor)
  365. }
  366. if (isHydrating && _insertionAnchor !== undefined) {
  367. advanceHydrationNode(_insertionParent!)
  368. }
  369. return instance
  370. }
  371. export function setupComponent(
  372. instance: VaporComponentInstance,
  373. component: VaporComponent,
  374. ): void {
  375. const prevInstance = setCurrentInstance(instance)
  376. const prevSub = setActiveSub()
  377. if (__DEV__) {
  378. setupPropsValidation(instance)
  379. }
  380. const setupFn = isFunction(component) ? component : component.setup
  381. const setupResult = setupFn
  382. ? callWithErrorHandling(setupFn, instance, ErrorCodes.SETUP_FUNCTION, [
  383. instance.props,
  384. instance,
  385. ]) || EMPTY_OBJ
  386. : EMPTY_OBJ
  387. const isAsyncSetup = isPromise(setupResult)
  388. if ((isAsyncSetup || instance.sp) && !isAsyncWrapper(instance)) {
  389. // async setup / serverPrefetch, mark as async boundary for useId()
  390. markAsyncBoundary(instance)
  391. }
  392. if (isAsyncSetup) {
  393. if (__FEATURE_SUSPENSE__) {
  394. // async setup returned Promise.
  395. // bail here and wait for re-entry.
  396. instance.asyncDep = setupResult
  397. if (__DEV__ && !instance.suspense) {
  398. const name = getComponentName(component) ?? 'Anonymous'
  399. warn(
  400. `Component <${name}>: setup function returned a promise, but no ` +
  401. `<Suspense> boundary was found in the parent component tree. ` +
  402. `A component with async setup() must be nested in a <Suspense> ` +
  403. `in order to be rendered.`,
  404. )
  405. }
  406. } else if (__DEV__) {
  407. warn(
  408. `setup() returned a Promise, but the version of Vue you are using ` +
  409. `does not support it yet.`,
  410. )
  411. }
  412. } else {
  413. handleSetupResult(setupResult, component, instance)
  414. }
  415. setActiveSub(prevSub)
  416. setCurrentInstance(...prevInstance)
  417. }
  418. export let isApplyingFallthroughProps = false
  419. export function applyFallthroughProps(
  420. el: Element,
  421. attrs: Record<string, any>,
  422. ): void {
  423. isApplyingFallthroughProps = true
  424. setDynamicProps(el, [attrs])
  425. isApplyingFallthroughProps = false
  426. }
  427. /**
  428. * dev only
  429. */
  430. function createDevSetupStateProxy(
  431. instance: VaporComponentInstance,
  432. ): Record<string, any> {
  433. const { setupState } = instance
  434. return new Proxy(setupState!, {
  435. get(target, key: string | symbol, receiver) {
  436. if (
  437. isString(key) &&
  438. !key.startsWith('__v') &&
  439. !hasOwn(toRaw(setupState)!, key)
  440. ) {
  441. warn(
  442. `Property ${JSON.stringify(key)} was accessed during render ` +
  443. `but is not defined on instance.`,
  444. )
  445. }
  446. return Reflect.get(target, key, receiver)
  447. },
  448. })
  449. }
  450. /**
  451. * dev only
  452. */
  453. export function devRender(instance: VaporComponentInstance): void {
  454. instance.block =
  455. (instance.type.render
  456. ? callWithErrorHandling(
  457. instance.type.render,
  458. instance,
  459. ErrorCodes.RENDER_FUNCTION,
  460. [
  461. instance.setupState,
  462. instance.props,
  463. instance.emit,
  464. instance.attrs,
  465. instance.slots,
  466. ],
  467. )
  468. : callWithErrorHandling(
  469. isFunction(instance.type) ? instance.type : instance.type.setup!,
  470. instance,
  471. ErrorCodes.SETUP_FUNCTION,
  472. [
  473. instance.props,
  474. {
  475. slots: instance.slots,
  476. attrs: instance.attrs,
  477. emit: instance.emit,
  478. expose: instance.expose,
  479. },
  480. ],
  481. )) || []
  482. }
  483. export const emptyContext: GenericAppContext = {
  484. app: null as any,
  485. config: {},
  486. provides: /*@__PURE__*/ Object.create(null),
  487. }
  488. export class VaporComponentInstance<
  489. Props extends Record<string, any> = {},
  490. Emits extends EmitsOptions = {},
  491. Slots extends StaticSlots = StaticSlots,
  492. Exposed extends Record<string, any> = Record<string, any>,
  493. TypeBlock extends Block = Block,
  494. TypeRefs extends Record<string, any> = Record<string, any>,
  495. > implements GenericComponentInstance {
  496. vapor: true
  497. uid: number
  498. type: VaporComponent
  499. root: GenericComponentInstance | null
  500. parent: GenericComponentInstance | null
  501. appContext: GenericAppContext
  502. block: TypeBlock
  503. scope: EffectScope
  504. rawProps: RawProps
  505. rawSlots: RawSlots
  506. props: Readonly<Props>
  507. attrs: Record<string, any>
  508. propsDefaults: Record<string, any> | null
  509. slots: Slots
  510. scopeId?: string | null
  511. // to hold vnode props / slots in vdom interop mode
  512. rawPropsRef?: ShallowRef<any>
  513. rawSlotsRef?: ShallowRef<any>
  514. emit: EmitFn<Emits>
  515. emitted: Record<string, boolean> | null
  516. expose: (<T extends Record<string, any> = Exposed>(exposed: T) => void) &
  517. // compatible with vdom components
  518. string[]
  519. exposed: Exposed | null
  520. exposeProxy: Prettify<ShallowUnwrapRef<Exposed>> | null
  521. // for useTemplateRef()
  522. refs: TypeRefs
  523. // for provide / inject
  524. provides: Record<string, any>
  525. // for useId
  526. ids: [string, number, number]
  527. // for suspense
  528. suspense: SuspenseBoundary | null
  529. suspenseId: number
  530. asyncDep: Promise<any> | null
  531. asyncResolved: boolean
  532. // for vapor custom element
  533. renderEffects?: RenderEffect[]
  534. hasFallthrough: boolean
  535. // for keep-alive
  536. shapeFlag?: number
  537. // for v-once: caches props/attrs values to ensure they remain frozen
  538. // even when the component re-renders due to local state changes
  539. oncePropsCache?: Record<string | symbol, any>
  540. // lifecycle hooks
  541. isMounted: boolean
  542. isUnmounted: boolean
  543. isDeactivated: boolean
  544. isUpdating: boolean
  545. bc?: LifecycleHook // LifecycleHooks.BEFORE_CREATE
  546. c?: LifecycleHook // LifecycleHooks.CREATED
  547. bm?: LifecycleHook // LifecycleHooks.BEFORE_MOUNT
  548. m?: LifecycleHook // LifecycleHooks.MOUNTED
  549. bu?: LifecycleHook // LifecycleHooks.BEFORE_UPDATE
  550. u?: LifecycleHook // LifecycleHooks.UPDATED
  551. um?: LifecycleHook // LifecycleHooks.BEFORE_UNMOUNT
  552. bum?: LifecycleHook // LifecycleHooks.UNMOUNTED
  553. da?: LifecycleHook // LifecycleHooks.DEACTIVATED
  554. a?: LifecycleHook // LifecycleHooks.ACTIVATED
  555. rtg?: LifecycleHook // LifecycleHooks.RENDER_TRACKED
  556. rtc?: LifecycleHook // LifecycleHooks.RENDER_TRIGGERED
  557. ec?: LifecycleHook // LifecycleHooks.ERROR_CAPTURED
  558. sp?: LifecycleHook<() => Promise<unknown>> // LifecycleHooks.SERVER_PREFETCH
  559. // dev only
  560. setupState?: Exposed extends Block ? undefined : ShallowUnwrapRef<Exposed>
  561. devtoolsRawSetupState?: any
  562. hmrRerender?: () => void
  563. hmrReload?: (newComp: VaporComponent) => void
  564. parentTeleport?: TeleportFragment | null
  565. propsOptions?: NormalizedPropsOptions
  566. emitsOptions?: ObjectEmitsOptions | null
  567. isSingleRoot?: boolean
  568. /**
  569. * dev only flag to track whether $attrs was used during render.
  570. * If $attrs was used during render then the warning for failed attrs
  571. * fallthrough can be suppressed.
  572. */
  573. accessedAttrs: boolean = false
  574. constructor(
  575. comp: VaporComponent,
  576. rawProps?: RawProps | null,
  577. rawSlots?: RawSlots | null,
  578. appContext?: GenericAppContext,
  579. once?: boolean,
  580. ) {
  581. this.vapor = true
  582. this.uid = nextUid()
  583. this.type = comp
  584. this.parent = currentInstance
  585. if (currentInstance) {
  586. this.root = currentInstance.root
  587. this.appContext = currentInstance.appContext
  588. this.provides = currentInstance.provides
  589. this.ids = currentInstance.ids
  590. } else {
  591. this.root = this
  592. this.appContext = appContext || emptyContext
  593. this.provides = Object.create(this.appContext.provides)
  594. this.ids = ['', 0, 0]
  595. }
  596. this.block = null! // to be set
  597. this.scope = new EffectScope(true)
  598. this.emit = emit.bind(null, this) as EmitFn<Emits>
  599. this.expose = expose.bind(null, this) as any
  600. this.refs = EMPTY_OBJ as TypeRefs
  601. this.emitted = this.exposed = this.exposeProxy = this.propsDefaults = null
  602. // suspense related
  603. this.suspense = parentSuspense
  604. this.suspenseId = parentSuspense ? parentSuspense.pendingId : 0
  605. this.asyncDep = null
  606. this.asyncResolved = false
  607. this.isMounted =
  608. this.isUnmounted =
  609. this.isUpdating =
  610. this.isDeactivated =
  611. false
  612. // init props
  613. this.rawProps = rawProps || EMPTY_OBJ
  614. this.hasFallthrough = hasFallthroughAttrs(comp, rawProps)
  615. if (rawProps || comp.props) {
  616. const [propsHandlers, attrsHandlers] = getPropsProxyHandlers(comp, once)
  617. this.attrs = new Proxy(this, attrsHandlers)
  618. this.props = (
  619. comp.props
  620. ? new Proxy(this, propsHandlers!)
  621. : isFunction(comp)
  622. ? this.attrs
  623. : EMPTY_OBJ
  624. ) as Props
  625. } else {
  626. this.props = this.attrs = EMPTY_OBJ as Props
  627. }
  628. // init slots
  629. this.rawSlots = rawSlots || EMPTY_OBJ
  630. this.slots = (
  631. rawSlots
  632. ? rawSlots.$
  633. ? new Proxy(rawSlots, dynamicSlotsProxyHandlers)
  634. : rawSlots
  635. : EMPTY_OBJ
  636. ) as Slots
  637. this.scopeId = getCurrentScopeId()
  638. // apply custom element special handling
  639. if (comp.ce) {
  640. comp.ce(this)
  641. }
  642. if (__DEV__) {
  643. // in dev, mark attrs accessed if optional props (attrs === props)
  644. if (this.props === this.attrs) {
  645. this.accessedAttrs = true
  646. } else {
  647. const attrs = this.attrs
  648. const instance = this
  649. this.attrs = new Proxy(attrs, {
  650. get(target, key, receiver) {
  651. instance.accessedAttrs = true
  652. return Reflect.get(target, key, receiver)
  653. },
  654. })
  655. }
  656. }
  657. }
  658. /**
  659. * Expose `getKeysFromRawProps` on the instance so it can be used in code
  660. * paths where it's needed, e.g. `useModel`
  661. */
  662. rawKeys(): string[] {
  663. return getKeysFromRawProps(this.rawProps)
  664. }
  665. }
  666. export function isVaporComponent(
  667. value: unknown,
  668. ): value is VaporComponentInstance {
  669. return value instanceof VaporComponentInstance
  670. }
  671. /**
  672. * Used when a component cannot be resolved at compile time
  673. * and needs rely on runtime resolution - where it might fallback to a plain
  674. * element if the resolution fails.
  675. */
  676. export function createComponentWithFallback(
  677. comp: VaporComponent | typeof NULL_DYNAMIC_COMPONENT | string,
  678. rawProps?: LooseRawProps | null,
  679. rawSlots?: LooseRawSlots | null,
  680. isSingleRoot?: boolean,
  681. once?: boolean,
  682. appContext?: GenericAppContext,
  683. ): HTMLElement | VaporComponentInstance {
  684. if (comp === NULL_DYNAMIC_COMPONENT) {
  685. return (__DEV__
  686. ? createComment('ndc')
  687. : createTextNode('')) as any as HTMLElement
  688. }
  689. if (!isString(comp)) {
  690. return createComponent(
  691. comp,
  692. rawProps,
  693. rawSlots,
  694. isSingleRoot,
  695. once,
  696. appContext,
  697. )
  698. }
  699. return createPlainElement(comp, rawProps, rawSlots, isSingleRoot, once)
  700. }
  701. export function createPlainElement(
  702. comp: string,
  703. rawProps?: LooseRawProps | null,
  704. rawSlots?: LooseRawSlots | null,
  705. isSingleRoot?: boolean,
  706. once?: boolean,
  707. ): HTMLElement {
  708. const _insertionParent = insertionParent
  709. const _insertionAnchor = insertionAnchor
  710. const _isLastInsertion = isLastInsertion
  711. if (isHydrating) {
  712. locateHydrationNode()
  713. } else {
  714. resetInsertionState()
  715. }
  716. const el = isHydrating
  717. ? (adoptTemplate(currentHydrationNode!, `<${comp}/>`) as HTMLElement)
  718. : createElement(comp)
  719. // mark single root
  720. ;(el as any).$root = isSingleRoot
  721. if (!isHydrating) {
  722. const scopeId = getCurrentScopeId()
  723. if (scopeId) setScopeId(el, [scopeId])
  724. }
  725. if (rawProps) {
  726. const setFn = () =>
  727. setDynamicProps(el, [resolveDynamicProps(rawProps as RawProps)])
  728. if (once) setFn()
  729. else renderEffect(setFn)
  730. }
  731. if (rawSlots) {
  732. let nextNode: Node | null = null
  733. if (isHydrating) {
  734. nextNode = locateNextNode(el)
  735. setCurrentHydrationNode(el.firstChild)
  736. }
  737. if (rawSlots.$) {
  738. // ssr output does not contain the slot anchor, use an empty string
  739. // as the anchor label to avoid slot anchor search errors
  740. const frag = new DynamicFragment(
  741. isHydrating ? '' : __DEV__ ? 'slot' : undefined,
  742. )
  743. renderEffect(() => frag.update(getSlot(rawSlots as RawSlots, 'default')))
  744. if (!isHydrating) insert(frag, el)
  745. } else {
  746. const block = getSlot(rawSlots as RawSlots, 'default')!()
  747. if (!isHydrating) insert(block, el)
  748. }
  749. if (isHydrating) {
  750. setCurrentHydrationNode(nextNode)
  751. }
  752. }
  753. if (!isHydrating) {
  754. if (_insertionParent) insert(el, _insertionParent, _insertionAnchor)
  755. } else {
  756. if (_isLastInsertion) {
  757. advanceHydrationNode(_insertionParent!)
  758. }
  759. }
  760. return el
  761. }
  762. export function mountComponent(
  763. instance: VaporComponentInstance,
  764. parent: ParentNode,
  765. anchor?: Node | null | 0,
  766. ): void {
  767. if (
  768. __FEATURE_SUSPENSE__ &&
  769. instance.suspense &&
  770. instance.asyncDep &&
  771. !instance.asyncResolved
  772. ) {
  773. const component = instance.type
  774. instance.suspense.registerDep(instance, setupResult => {
  775. handleSetupResult(setupResult, component, instance)
  776. mountComponent(instance, parent, anchor)
  777. })
  778. return
  779. }
  780. if (instance.shapeFlag! & ShapeFlags.COMPONENT_KEPT_ALIVE) {
  781. ;(instance.parent as KeepAliveInstance)!.ctx.activate(
  782. instance,
  783. parent,
  784. anchor,
  785. )
  786. return
  787. }
  788. // custom element style injection
  789. const { root, type } = instance as GenericComponentInstance
  790. if (
  791. root &&
  792. root.ce &&
  793. // @ts-expect-error _def is private
  794. (root.ce as VaporElement)._def.shadowRoot !== false
  795. ) {
  796. root.ce!._injectChildStyle(type)
  797. }
  798. if (__DEV__) {
  799. startMeasure(instance, `mount`)
  800. }
  801. if (instance.bm) invokeArrayFns(instance.bm)
  802. if (!isHydrating) {
  803. insert(instance.block, parent, anchor)
  804. setComponentScopeId(instance)
  805. }
  806. if (instance.m) queuePostFlushCb(instance.m!)
  807. if (
  808. instance.shapeFlag! & ShapeFlags.COMPONENT_SHOULD_KEEP_ALIVE &&
  809. instance.a
  810. ) {
  811. queuePostFlushCb(instance.a!)
  812. }
  813. instance.isMounted = true
  814. if (__DEV__) {
  815. endMeasure(instance, `mount`)
  816. }
  817. }
  818. export function unmountComponent(
  819. instance: VaporComponentInstance,
  820. parentNode?: ParentNode,
  821. ): void {
  822. // Skip unmount for kept-alive components - deactivate if called from remove()
  823. if (
  824. instance.shapeFlag! & ShapeFlags.COMPONENT_SHOULD_KEEP_ALIVE &&
  825. instance.parent &&
  826. instance.parent.vapor
  827. ) {
  828. if (parentNode) {
  829. ;(instance.parent as KeepAliveInstance)!.ctx.deactivate(instance)
  830. }
  831. return
  832. }
  833. if (instance.isMounted && !instance.isUnmounted) {
  834. if (__DEV__) {
  835. unregisterHMR(instance)
  836. }
  837. if (instance.bum) {
  838. invokeArrayFns(instance.bum)
  839. }
  840. instance.scope.stop()
  841. if (instance.um) {
  842. queuePostFlushCb(instance.um!)
  843. }
  844. instance.isUnmounted = true
  845. }
  846. if (parentNode) {
  847. remove(instance.block, parentNode)
  848. }
  849. }
  850. export function getExposed(
  851. instance: GenericComponentInstance,
  852. ): Record<string, any> | undefined {
  853. if (instance.exposed) {
  854. return (
  855. instance.exposeProxy ||
  856. (instance.exposeProxy = new Proxy(markRaw(instance.exposed), {
  857. get: (target, key) => unref(target[key as any]),
  858. }))
  859. )
  860. }
  861. }
  862. export function getRootElement(
  863. block: Block,
  864. onDynamicFragment?: (frag: DynamicFragment) => void,
  865. recurse: boolean = true,
  866. ): Element | undefined {
  867. if (block instanceof Element) {
  868. return block
  869. }
  870. if (recurse && isVaporComponent(block)) {
  871. return getRootElement(block.block, onDynamicFragment, recurse)
  872. }
  873. if (isFragment(block) && !isTeleportFragment(block)) {
  874. if (block instanceof DynamicFragment && onDynamicFragment) {
  875. onDynamicFragment(block)
  876. }
  877. const { nodes } = block
  878. if (nodes instanceof Element && (nodes as any).$root) {
  879. return nodes
  880. }
  881. return getRootElement(nodes, onDynamicFragment, recurse)
  882. }
  883. // The root node contains comments. It is necessary to filter out
  884. // the comment nodes and return a single root node.
  885. // align with vdom behavior
  886. if (isArray(block)) {
  887. let singleRoot: Element | undefined
  888. let hasComment = false
  889. for (const b of block) {
  890. if (b instanceof Comment) {
  891. hasComment = true
  892. continue
  893. }
  894. const thisRoot = getRootElement(b, onDynamicFragment, recurse)
  895. // only return root if there is exactly one eligible root in the array
  896. if (!thisRoot || singleRoot) {
  897. return
  898. }
  899. singleRoot = thisRoot
  900. }
  901. return hasComment ? singleRoot : undefined
  902. }
  903. }
  904. function isVaporTransition(component: VaporComponent): boolean {
  905. return getComponentName(component) === 'VaporTransition'
  906. }
  907. function handleSetupResult(
  908. setupResult: any,
  909. component: VaporComponent,
  910. instance: VaporComponentInstance,
  911. ) {
  912. if (__DEV__) {
  913. pushWarningContext(instance)
  914. }
  915. if (__DEV__ && !isBlock(setupResult)) {
  916. if (isFunction(component)) {
  917. warn(`Functional vapor component must return a block directly.`)
  918. instance.block = []
  919. } else if (!component.render) {
  920. warn(
  921. `Vapor component setup() returned non-block value, and has no render function.`,
  922. )
  923. instance.block = []
  924. } else {
  925. if (__DEV__ || __FEATURE_PROD_DEVTOOLS__) {
  926. instance.devtoolsRawSetupState = setupResult
  927. }
  928. instance.setupState = proxyRefs(setupResult)
  929. if (__DEV__) {
  930. instance.setupState = createDevSetupStateProxy(instance)
  931. }
  932. devRender(instance)
  933. }
  934. } else {
  935. // component has a render function but no setup function
  936. // (typically components with only a template and no state)
  937. if (setupResult === EMPTY_OBJ && component.render) {
  938. instance.block = callWithErrorHandling(
  939. component.render,
  940. instance,
  941. ErrorCodes.RENDER_FUNCTION,
  942. )
  943. } else {
  944. // in prod result can only be block
  945. instance.block = setupResult as Block
  946. }
  947. }
  948. // single root, inherit attrs
  949. if (
  950. instance.hasFallthrough &&
  951. component.inheritAttrs !== false &&
  952. Object.keys(instance.attrs).length
  953. ) {
  954. const root = getRootElement(
  955. instance.block,
  956. // attach attrs to root dynamic fragments for applying during each update
  957. frag => (frag.attrs = instance.attrs),
  958. false,
  959. )
  960. if (root) {
  961. renderEffect(() => {
  962. const attrs =
  963. isFunction(component) && !isVaporTransition(component)
  964. ? getFunctionalFallthrough(instance.attrs)
  965. : instance.attrs
  966. if (attrs) applyFallthroughProps(root, attrs)
  967. })
  968. } else if (
  969. __DEV__ &&
  970. ((!instance.accessedAttrs &&
  971. isArray(instance.block) &&
  972. instance.block.length) ||
  973. // preventing attrs fallthrough on Teleport
  974. // consistent with VDOM Teleport behavior
  975. isTeleportFragment(instance.block))
  976. ) {
  977. warnExtraneousAttributes(instance.attrs)
  978. }
  979. }
  980. if (__DEV__) {
  981. popWarningContext()
  982. }
  983. }
  984. export function getCurrentScopeId(): string | undefined {
  985. const scopeOwner = getScopeOwner()
  986. return scopeOwner ? scopeOwner.type.__scopeId : undefined
  987. }