From: daiwei Date: Mon, 28 Apr 2025 07:32:23 +0000 (+0800) Subject: wip: update X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=4253b0ce3ea1669f3c1df2a5f35ce03bae0ab400;p=thirdparty%2Fvuejs%2Fcore.git wip: update --- diff --git a/packages/runtime-vapor/__tests__/hydration.spec.ts b/packages/runtime-vapor/__tests__/hydration.spec.ts index b0878e1391..879257e4d1 100644 --- a/packages/runtime-vapor/__tests__/hydration.spec.ts +++ b/packages/runtime-vapor/__tests__/hydration.spec.ts @@ -1963,8 +1963,56 @@ describe('Vapor Mode hydration', () => { data, ) - expect(container.innerHTML).toMatchInlineSnapshot( - `"
foohi
"`, + expect(container.innerHTML).toBe( + `
` + + `foo` + + `hi` + + `
`, + ) + + data.msg = 'bar' + await nextTick() + expect(container.innerHTML).toBe( + `
` + + `foo` + + `bar` + + `
`, + ) + }) + + test('mixed root slot and text node', async () => { + const data = reactive({ + text: 'foo', + msg: 'hi', + }) + const { container } = await testHydration( + ``, + { + Child: ``, + }, + data, + ) + + expect(container.innerHTML).toBe( + `` + + `foo` + + `foo` + + `hi` + + ``, + ) + + data.msg = 'bar' + await nextTick() + expect(container.innerHTML).toBe( + `` + + `foo` + + `foo` + + `bar` + + ``, ) }) @@ -1985,8 +2033,20 @@ describe('Vapor Mode hydration', () => { data, ) - expect(container.innerHTML).toMatchInlineSnapshot( - `"
foo
hi
"`, + expect(container.innerHTML).toBe( + `
` + + `foo` + + `
hi
` + + `
`, + ) + + data.msg = 'bar' + await nextTick() + expect(container.innerHTML).toBe( + `
` + + `foo` + + `
bar
` + + `
`, ) }) @@ -2024,6 +2084,7 @@ describe('Vapor Mode hydration', () => { `
bar
` + ``, ) + data.msg2 = 'hello' await nextTick() expect(container.innerHTML).toBe( diff --git a/packages/runtime-vapor/src/apiCreateDynamicComponent.ts b/packages/runtime-vapor/src/apiCreateDynamicComponent.ts index e272f0d225..7ae689a0ae 100644 --- a/packages/runtime-vapor/src/apiCreateDynamicComponent.ts +++ b/packages/runtime-vapor/src/apiCreateDynamicComponent.ts @@ -22,7 +22,10 @@ export function createDynamicComponent( const _insertionAnchor = insertionAnchor if (!isHydrating) resetInsertionState() - const frag = new DynamicFragment(DYNAMIC_COMPONENT_ANCHOR_LABEL) + const frag = + isHydrating || __DEV__ + ? new DynamicFragment(DYNAMIC_COMPONENT_ANCHOR_LABEL) + : new DynamicFragment() renderEffect(() => { const value = getter() frag.update( diff --git a/packages/runtime-vapor/src/apiCreateFor.ts b/packages/runtime-vapor/src/apiCreateFor.ts index 704b01973d..06a2bf752e 100644 --- a/packages/runtime-vapor/src/apiCreateFor.ts +++ b/packages/runtime-vapor/src/apiCreateFor.ts @@ -16,11 +16,7 @@ import { isObject, isString, } from '@vue/shared' -import { - createComment, - createTextNode, - findVaporFragmentAnchor, -} from './dom/node' +import { createComment, createTextNode } from './dom/node' import { type Block, VaporFragment, @@ -34,6 +30,7 @@ import { renderEffect } from './renderEffect' import { VaporVForFlags } from '../../shared/src/vaporFlags' import { currentHydrationNode, + findVaporFragmentAnchor, isHydrating, locateHydrationNode, } from './dom/hydration' diff --git a/packages/runtime-vapor/src/apiCreateIf.ts b/packages/runtime-vapor/src/apiCreateIf.ts index bec4a3504d..3e370592b3 100644 --- a/packages/runtime-vapor/src/apiCreateIf.ts +++ b/packages/runtime-vapor/src/apiCreateIf.ts @@ -22,7 +22,10 @@ export function createIf( if (once) { frag = condition() ? b1() : b2 ? b2() : [] } else { - frag = new DynamicFragment(IF_ANCHOR_LABEL) + frag = + isHydrating || __DEV__ + ? new DynamicFragment(IF_ANCHOR_LABEL) + : new DynamicFragment() renderEffect(() => (frag as DynamicFragment).update(condition() ? b1 : b2)) } diff --git a/packages/runtime-vapor/src/block.ts b/packages/runtime-vapor/src/block.ts index 9879496462..bf77b9b094 100644 --- a/packages/runtime-vapor/src/block.ts +++ b/packages/runtime-vapor/src/block.ts @@ -5,14 +5,11 @@ import { mountComponent, unmountComponent, } from './component' -import { - createComment, - createTextNode, - findVaporFragmentAnchor, -} from './dom/node' +import { createComment, createTextNode } from './dom/node' import { EffectScope, pauseTracking, resetTracking } from '@vue/reactivity' import { currentHydrationNode, + findVaporFragmentAnchor, isComment, isHydrating, locateHydrationNode, @@ -92,13 +89,11 @@ export class DynamicFragment extends VaporFragment { } hydrate(label: string): void { - // for v-if="false" the hydrationNode will be a empty comment node - // use it as anchor. - // otherwise, use the next sibling comment node as anchor + // for `v-if="false"` the node will be an empty comment node use it as the anchor. + // otherwise, find next sibling vapor fragment anchor if (isComment(currentHydrationNode!, '')) { this.anchor = currentHydrationNode } else { - // find next sibling dynamic fragment end anchor const anchor = findVaporFragmentAnchor(currentHydrationNode!, label)! if (anchor) { this.anchor = anchor diff --git a/packages/runtime-vapor/src/componentSlots.ts b/packages/runtime-vapor/src/componentSlots.ts index 093ed7fb08..2b2de60729 100644 --- a/packages/runtime-vapor/src/componentSlots.ts +++ b/packages/runtime-vapor/src/componentSlots.ts @@ -124,7 +124,10 @@ export function createSlot( fallback, ) } else { - fragment = new DynamicFragment(SLOT_ANCHOR_LABEL) + fragment = + isHydrating || __DEV__ + ? new DynamicFragment(SLOT_ANCHOR_LABEL) + : new DynamicFragment() const isDynamicName = isFunction(name) const renderSlot = () => { const slot = getSlot(rawSlots, isFunction(name) ? name() : name) diff --git a/packages/runtime-vapor/src/dom/hydration.ts b/packages/runtime-vapor/src/dom/hydration.ts index 9b2e0efa74..d82aa33f98 100644 --- a/packages/runtime-vapor/src/dom/hydration.ts +++ b/packages/runtime-vapor/src/dom/hydration.ts @@ -11,7 +11,7 @@ import { enableHydrationNodeLookup, next, } from './node' -import { isVaporFragmentEndAnchor } from '@vue/shared' +import { isDynamicAnchor, isVaporFragmentEndAnchor } from '@vue/shared' export let isHydrating = false export let currentHydrationNode: Node | null = null @@ -191,3 +191,29 @@ export function locateEndAnchor( } return null } + +export function isNonHydrationNode(node: Node): boolean { + return ( + // empty text nodes + isEmptyText(node) || + // dynamic node anchors (, ) + isDynamicAnchor(node) || + // fragment end anchor (``) + isComment(node, ']') || + // vapor fragment end anchors + isVaporFragmentEndAnchor(node) + ) +} + +export function findVaporFragmentAnchor( + node: Node, + anchorLabel: string, +): Comment | null { + let n = node.nextSibling + while (n) { + if (isComment(n, anchorLabel)) return n + n = n.nextSibling + } + + return null +} diff --git a/packages/runtime-vapor/src/dom/node.ts b/packages/runtime-vapor/src/dom/node.ts index b192294453..2384697ed0 100644 --- a/packages/runtime-vapor/src/dom/node.ts +++ b/packages/runtime-vapor/src/dom/node.ts @@ -1,8 +1,7 @@ -import { isComment, isEmptyText, locateEndAnchor } from './hydration' +import { isComment, isNonHydrationNode, locateEndAnchor } from './hydration' import { DYNAMIC_END_ANCHOR_LABEL, DYNAMIC_START_ANCHOR_LABEL, - isDynamicAnchor, isVaporFragmentEndAnchor, } from '@vue/shared' @@ -42,7 +41,7 @@ export function __child(node: ParentNode): Node { * _setInsertionState(n2, 0) -> slot fragment * * during hydration: - * const n2 = _template("
slotHi
")() + * const n2 =
slotHi
// server output * const n1 = _child(n2) -> should be `Hi` instead of the slot fragment * _setInsertionState(n2, 0) -> slot fragment */ @@ -79,7 +78,19 @@ function _next(node: Node): Node { /*! #__NO_SIDE_EFFECTS__ */ export function __next(node: Node): Node { - node = handleWrappedNode(node) + // process dynamic node (...) as a single node + if (isComment(node, DYNAMIC_START_ANCHOR_LABEL)) { + node = locateEndAnchor( + node, + DYNAMIC_START_ANCHOR_LABEL, + DYNAMIC_END_ANCHOR_LABEL, + )! + } + + // process fragment (...) as a single node + else if (isComment(node, '[')) { + node = locateEndAnchor(node)! + } let n = node.nextSibling! while (n && isNonHydrationNode(n)) { @@ -142,47 +153,3 @@ export function disableHydrationNodeLookup(): void { next.impl = _next nthChild.impl = _nthChild } - -function isNonHydrationNode(node: Node) { - return ( - // empty text nodes, no need to hydrate - isEmptyText(node) || - // dynamic node anchors (, ) - isDynamicAnchor(node) || - // fragment end anchor (``) - isComment(node, ']') || - // vapor fragment end anchors - isVaporFragmentEndAnchor(node) - ) -} - -export function findVaporFragmentAnchor( - node: Node, - anchorLabel: string, -): Comment | null { - let n = node.nextSibling - while (n) { - if (isComment(n, anchorLabel)) return n - n = n.nextSibling - } - - return null -} - -function handleWrappedNode(node: Node): Node { - // process dynamic node (...) as a single one - if (isComment(node, DYNAMIC_START_ANCHOR_LABEL)) { - return locateEndAnchor( - node, - DYNAMIC_START_ANCHOR_LABEL, - DYNAMIC_END_ANCHOR_LABEL, - )! - } - - // process fragment (...) as a single one - else if (isComment(node, '[')) { - return locateEndAnchor(node)! - } - - return node -}