component.ts 30 KB

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