From 5b933f582c4a0450f980af3668a9605c3248a88d Mon Sep 17 00:00:00 2001 From: daiwei Date: Wed, 26 Mar 2025 21:40:13 +0800 Subject: [PATCH] wip: extract VaporFragment into a separate file to resolve the circular dependency caused by TeleportFragment --- .../vapor-e2e-test/__tests__/teleport.spec.ts | 61 ++++++++-------- .../runtime-vapor/__tests__/block.spec.ts | 9 +-- .../src/apiCreateDynamicComponent.ts | 2 +- packages/runtime-vapor/src/apiCreateFor.ts | 8 +- packages/runtime-vapor/src/apiCreateIf.ts | 3 +- packages/runtime-vapor/src/block.ts | 73 ++----------------- packages/runtime-vapor/src/componentSlots.ts | 3 +- .../runtime-vapor/src/components/Teleport.ts | 13 +--- .../runtime-vapor/src/directives/vShow.ts | 3 +- packages/runtime-vapor/src/fragment.ts | 69 ++++++++++++++++++ packages/runtime-vapor/src/index.ts | 4 +- packages/runtime-vapor/src/vdomInterop.ts | 3 +- 12 files changed, 125 insertions(+), 126 deletions(-) create mode 100644 packages/runtime-vapor/src/fragment.ts diff --git a/packages-private/vapor-e2e-test/__tests__/teleport.spec.ts b/packages-private/vapor-e2e-test/__tests__/teleport.spec.ts index dc3efa13a4..ce383dadf1 100644 --- a/packages-private/vapor-e2e-test/__tests__/teleport.spec.ts +++ b/packages-private/vapor-e2e-test/__tests__/teleport.spec.ts @@ -9,7 +9,7 @@ import { nextTick } from 'vue' import { ports } from '../utils' const { page, click, html } = setupPuppeteer() -describe('vdom / vapor interop', () => { +describe('vapor teleport', () => { let server: any const port = ports.teleport beforeAll(() => { @@ -29,33 +29,34 @@ describe('vdom / vapor interop', () => { await page().waitForSelector('#app') }) - describe('vapor teleport', () => { - test( - 'render vdom component', - async () => { - const targetSelector = '.target' - const testSelector = '.interop-render-vdom-comp' - const containerSelector = `${testSelector} > div` - const btnSelector = `${testSelector} > button` - - // teleport is disabled - expect(await html(containerSelector)).toBe('

vdom comp

') - expect(await html(targetSelector)).toBe('') - - // enable teleport - await click(btnSelector) - await nextTick() - - expect(await html(containerSelector)).toBe('') - expect(await html(targetSelector)).toBe('

vdom comp

') - - // disable teleport - await click(btnSelector) - await nextTick() - expect(await html(containerSelector)).toBe('

vdom comp

') - expect(await html(targetSelector)).toBe('') - }, - E2E_TIMEOUT, - ) - }) + test( + 'render vdom component', + async () => { + const targetSelector = '.target' + const testSelector = '.interop-render-vdom-comp' + const containerSelector = `${testSelector} > div` + const btnSelector = `${testSelector} > button` + + const tt = await html('#app') + console.log(tt) + + // teleport is disabled + expect(await html(containerSelector)).toBe('

vdom comp

') + expect(await html(targetSelector)).toBe('') + + // enable teleport + await click(btnSelector) + await nextTick() + + expect(await html(containerSelector)).toBe('') + expect(await html(targetSelector)).toBe('

vdom comp

') + + // disable teleport + await click(btnSelector) + await nextTick() + expect(await html(containerSelector)).toBe('

vdom comp

') + expect(await html(targetSelector)).toBe('') + }, + E2E_TIMEOUT, + ) }) diff --git a/packages/runtime-vapor/__tests__/block.spec.ts b/packages/runtime-vapor/__tests__/block.spec.ts index 9f76c7f033..f0144dee3d 100644 --- a/packages/runtime-vapor/__tests__/block.spec.ts +++ b/packages/runtime-vapor/__tests__/block.spec.ts @@ -1,10 +1,5 @@ -import { - VaporFragment, - insert, - normalizeBlock, - prepend, - remove, -} from '../src/block' +import { insert, normalizeBlock, prepend, remove } from '../src/block' +import { VaporFragment } from '../src/fragment' const node1 = document.createTextNode('node1') const node2 = document.createTextNode('node2') diff --git a/packages/runtime-vapor/src/apiCreateDynamicComponent.ts b/packages/runtime-vapor/src/apiCreateDynamicComponent.ts index 2126611d71..abdc1a1cf2 100644 --- a/packages/runtime-vapor/src/apiCreateDynamicComponent.ts +++ b/packages/runtime-vapor/src/apiCreateDynamicComponent.ts @@ -1,9 +1,9 @@ import { resolveDynamicComponent } from '@vue/runtime-dom' -import { DynamicFragment, type VaporFragment } from './block' import { createComponentWithFallback } from './component' import { renderEffect } from './renderEffect' import type { RawProps } from './componentProps' import type { RawSlots } from './componentSlots' +import { DynamicFragment, type VaporFragment } from './fragment' export function createDynamicComponent( getter: () => any, diff --git a/packages/runtime-vapor/src/apiCreateFor.ts b/packages/runtime-vapor/src/apiCreateFor.ts index 0cd8317532..ca976aa8cc 100644 --- a/packages/runtime-vapor/src/apiCreateFor.ts +++ b/packages/runtime-vapor/src/apiCreateFor.ts @@ -11,12 +11,7 @@ import { } from '@vue/reactivity' import { getSequence, isArray, isObject, isString } from '@vue/shared' import { createComment, createTextNode } from './dom/node' -import { - type Block, - VaporFragment, - insert, - remove as removeBlock, -} from './block' +import { type Block, insert, remove as removeBlock } from './block' import { warn } from '@vue/runtime-dom' import { currentInstance, isVaporComponent } from './component' import type { DynamicSlot } from './componentSlots' @@ -24,6 +19,7 @@ import { renderEffect } from './renderEffect' import { VaporVForFlags } from '../../shared/src/vaporFlags' import { isHydrating, locateHydrationNode } from './dom/hydration' import { insertionAnchor, insertionParent } from './insertionState' +import { VaporFragment } from './fragment' class ForBlock extends VaporFragment { scope: EffectScope | undefined diff --git a/packages/runtime-vapor/src/apiCreateIf.ts b/packages/runtime-vapor/src/apiCreateIf.ts index 71bfa32d5d..e83b251d06 100644 --- a/packages/runtime-vapor/src/apiCreateIf.ts +++ b/packages/runtime-vapor/src/apiCreateIf.ts @@ -1,7 +1,8 @@ -import { type Block, type BlockFn, DynamicFragment, insert } from './block' +import { type Block, type BlockFn, insert } from './block' import { isHydrating, locateHydrationNode } from './dom/hydration' import { insertionAnchor, insertionParent } from './insertionState' import { renderEffect } from './renderEffect' +import { DynamicFragment } from './fragment' export function createIf( condition: () => any, diff --git a/packages/runtime-vapor/src/block.ts b/packages/runtime-vapor/src/block.ts index 332595aac4..f1791904ce 100644 --- a/packages/runtime-vapor/src/block.ts +++ b/packages/runtime-vapor/src/block.ts @@ -5,9 +5,12 @@ import { mountComponent, unmountComponent, } from './component' -import { createComment, createTextNode } from './dom/node' -import { EffectScope, pauseTracking, resetTracking } from '@vue/reactivity' import { isHydrating } from './dom/hydration' +import { + type DynamicFragment, + type VaporFragment, + isFragment, +} from './fragment' export type Block = | Node @@ -18,72 +21,6 @@ export type Block = export type BlockFn = (...args: any[]) => Block -export class VaporFragment { - nodes: Block - target?: ParentNode | null - targetAnchor?: Node | null - anchor?: Node - insert?: (parent: ParentNode, anchor: Node | null) => void - remove?: (parent?: ParentNode) => void - getNodes?: () => Block - - constructor(nodes: Block) { - this.nodes = nodes - } -} - -export class DynamicFragment extends VaporFragment { - anchor: Node - scope: EffectScope | undefined - current?: BlockFn - fallback?: BlockFn - - constructor(anchorLabel?: string) { - super([]) - this.anchor = - __DEV__ && anchorLabel ? createComment(anchorLabel) : createTextNode() - } - - update(render?: BlockFn, key: any = render): void { - if (key === this.current) { - return - } - this.current = key - - pauseTracking() - const parent = this.anchor.parentNode - - // teardown previous branch - if (this.scope) { - this.scope.stop() - parent && remove(this.nodes, parent) - } - - if (render) { - this.scope = new EffectScope() - this.nodes = this.scope.run(render) || [] - if (parent) insert(this.nodes, parent, this.anchor) - } else { - this.scope = undefined - this.nodes = [] - } - - if (this.fallback && !isValidBlock(this.nodes)) { - parent && remove(this.nodes, parent) - this.nodes = - (this.scope || (this.scope = new EffectScope())).run(this.fallback) || - [] - parent && insert(this.nodes, parent, this.anchor) - } - - resetTracking() - } -} - -export function isFragment(val: NonNullable): val is VaporFragment { - return val instanceof VaporFragment -} - export function isBlock(val: NonNullable): val is Block { return ( val instanceof Node || diff --git a/packages/runtime-vapor/src/componentSlots.ts b/packages/runtime-vapor/src/componentSlots.ts index 74296e0946..3d17e5c0a5 100644 --- a/packages/runtime-vapor/src/componentSlots.ts +++ b/packages/runtime-vapor/src/componentSlots.ts @@ -1,11 +1,12 @@ import { EMPTY_OBJ, NO, hasOwn, isArray, isFunction } from '@vue/shared' -import { type Block, type BlockFn, DynamicFragment, insert } from './block' +import { type Block, type BlockFn, insert } from './block' import { rawPropsProxyHandlers } from './componentProps' import { currentInstance, isRef } from '@vue/runtime-dom' import type { LooseRawProps, VaporComponentInstance } from './component' import { renderEffect } from './renderEffect' import { insertionAnchor, insertionParent } from './insertionState' import { isHydrating, locateHydrationNode } from './dom/hydration' +import { DynamicFragment } from './fragment' export type RawSlots = Record & { $?: DynamicSlotSource[] diff --git a/packages/runtime-vapor/src/components/Teleport.ts b/packages/runtime-vapor/src/components/Teleport.ts index 040914ef98..65fdc53e0a 100644 --- a/packages/runtime-vapor/src/components/Teleport.ts +++ b/packages/runtime-vapor/src/components/Teleport.ts @@ -7,13 +7,7 @@ import { resolveTarget, warn, } from '@vue/runtime-dom' -import { - type Block, - type BlockFn, - VaporFragment, - insert, - remove, -} from '../block' +import { type Block, type BlockFn, insert, remove } from '../block' import { createComment, createTextNode, querySelector } from '../dom/node' import type { LooseRawProps, @@ -24,6 +18,7 @@ import { rawPropsProxyHandlers } from '../componentProps' import { renderEffect } from '../renderEffect' import { extend, isArray } from '@vue/shared' import { EffectScope, pauseTracking, resetTracking } from '@vue/reactivity' +import { VaporFragment } from '../fragment' export const teleportStack: TeleportFragment[] = __DEV__ ? ([] as TeleportFragment[]) @@ -70,11 +65,11 @@ export const VaporTeleportImpl = { const scope = (frag.scope = new EffectScope()) scope!.run(() => { renderEffect(() => { - teleportStack.push(frag) + __DEV__ && teleportStack.push(frag) frag.updateChildren( (frag.children = slots.default && (slots.default as BlockFn)()), ) - teleportStack.pop() + __DEV__ && teleportStack.pop() }) renderEffect(() => { diff --git a/packages/runtime-vapor/src/directives/vShow.ts b/packages/runtime-vapor/src/directives/vShow.ts index ac4c066b71..b0fc22c14c 100644 --- a/packages/runtime-vapor/src/directives/vShow.ts +++ b/packages/runtime-vapor/src/directives/vShow.ts @@ -6,8 +6,9 @@ import { } from '@vue/runtime-dom' import { renderEffect } from '../renderEffect' import { isVaporComponent } from '../component' -import { type Block, DynamicFragment } from '../block' +import type { Block } from '../block' import { isArray } from '@vue/shared' +import { DynamicFragment } from '../fragment' export function applyVShow(target: Block, source: () => any): void { if (isVaporComponent(target)) { diff --git a/packages/runtime-vapor/src/fragment.ts b/packages/runtime-vapor/src/fragment.ts new file mode 100644 index 0000000000..3e4fcb221c --- /dev/null +++ b/packages/runtime-vapor/src/fragment.ts @@ -0,0 +1,69 @@ +import { EffectScope, pauseTracking, resetTracking } from '@vue/reactivity' +import { createComment, createTextNode } from './dom/node' +import { type Block, type BlockFn, insert, isValidBlock, remove } from './block' + +export class VaporFragment { + nodes: Block + target?: ParentNode | null + targetAnchor?: Node | null + anchor?: Node + insert?: (parent: ParentNode, anchor: Node | null) => void + remove?: (parent?: ParentNode) => void + getNodes?: () => Block + + constructor(nodes: Block) { + this.nodes = nodes + } +} + +export class DynamicFragment extends VaporFragment { + anchor: Node + scope: EffectScope | undefined + current?: BlockFn + fallback?: BlockFn + + constructor(anchorLabel?: string) { + super([]) + this.anchor = + __DEV__ && anchorLabel ? createComment(anchorLabel) : createTextNode() + } + + update(render?: BlockFn, key: any = render): void { + if (key === this.current) { + return + } + this.current = key + + pauseTracking() + const parent = this.anchor.parentNode + + // teardown previous branch + if (this.scope) { + this.scope.stop() + parent && remove(this.nodes, parent) + } + + if (render) { + this.scope = new EffectScope() + this.nodes = this.scope.run(render) || [] + if (parent) insert(this.nodes, parent, this.anchor) + } else { + this.scope = undefined + this.nodes = [] + } + + if (this.fallback && !isValidBlock(this.nodes)) { + parent && remove(this.nodes, parent) + this.nodes = + (this.scope || (this.scope = new EffectScope())).run(this.fallback) || + [] + parent && insert(this.nodes, parent, this.anchor) + } + + resetTracking() + } +} + +export function isFragment(val: NonNullable): val is VaporFragment { + return val instanceof VaporFragment +} diff --git a/packages/runtime-vapor/src/index.ts b/packages/runtime-vapor/src/index.ts index 2edceb5491..c2716059df 100644 --- a/packages/runtime-vapor/src/index.ts +++ b/packages/runtime-vapor/src/index.ts @@ -6,7 +6,7 @@ export type { VaporDirective } from './directives/custom' export { VaporTeleport } from './components/Teleport' // compiler-use only -export { insert, prepend, remove, isFragment, VaporFragment } from './block' +export { insert, prepend, remove } from './block' export { setInsertionState } from './insertionState' export { createComponent, createComponentWithFallback } from './component' export { renderEffect } from './renderEffect' @@ -43,3 +43,5 @@ export { applyDynamicModel, } from './directives/vModel' export { withVaporDirectives } from './directives/custom' +export { isFragment } from './fragment' +export { VaporFragment } from './fragment' diff --git a/packages/runtime-vapor/src/vdomInterop.ts b/packages/runtime-vapor/src/vdomInterop.ts index 77228fd72a..2249bbb2fd 100644 --- a/packages/runtime-vapor/src/vdomInterop.ts +++ b/packages/runtime-vapor/src/vdomInterop.ts @@ -26,13 +26,14 @@ import { mountComponent, unmountComponent, } from './component' -import { type Block, VaporFragment, insert, remove } from './block' +import { type Block, insert, remove } from './block' import { EMPTY_OBJ, extend, isFunction } from '@vue/shared' import { type RawProps, rawPropsProxyHandlers } from './componentProps' import type { RawSlots, VaporSlot } from './componentSlots' import { renderEffect } from './renderEffect' import { createTextNode } from './dom/node' import { optimizePropertyLookup } from './dom/prop' +import { VaporFragment } from './fragment' // mounting vapor components and slots in vdom const vaporInteropImpl: Omit< -- 2.47.3