From 167df1e381b71a95a274e314c5636e053c8eb7d3 Mon Sep 17 00:00:00 2001 From: daiwei Date: Tue, 5 Aug 2025 21:04:51 +0800 Subject: [PATCH] fix(hydration): handling empty text nodes --- .../compiler-vapor/src/generators/text.ts | 2 +- .../runtime-vapor/__tests__/hydration.spec.ts | 14 +++++++++++++ packages/runtime-vapor/src/dom/node.ts | 20 +++++++++++++------ 3 files changed, 29 insertions(+), 7 deletions(-) diff --git a/packages/compiler-vapor/src/generators/text.ts b/packages/compiler-vapor/src/generators/text.ts index 1f706ebd12..75faf3f57f 100644 --- a/packages/compiler-vapor/src/generators/text.ts +++ b/packages/compiler-vapor/src/generators/text.ts @@ -70,6 +70,6 @@ export function genGetTextChild( return [ NEWLINE, - `const x${oper.parent} = ${context.helper('child')}(n${oper.parent})`, + `const x${oper.parent} = ${context.helper('child')}(n${oper.parent}, -1)`, ] } diff --git a/packages/runtime-vapor/__tests__/hydration.spec.ts b/packages/runtime-vapor/__tests__/hydration.spec.ts index 5ff4cbf51f..26da9e3e74 100644 --- a/packages/runtime-vapor/__tests__/hydration.spec.ts +++ b/packages/runtime-vapor/__tests__/hydration.spec.ts @@ -182,6 +182,20 @@ describe('Vapor Mode hydration', () => { `"barAbarBbar"`, ) }) + + test('empty text nodes', async () => { + const data = reactive({ txt: '' }) + const { container } = await testHydration( + ``, + undefined, + data, + ) + expect(container.innerHTML).toMatchInlineSnapshot(`"
"`) + + data.txt = 'foo' + await nextTick() + expect(container.innerHTML).toMatchInlineSnapshot(`"
foo
"`) + }) }) describe('element', () => { diff --git a/packages/runtime-vapor/src/dom/node.ts b/packages/runtime-vapor/src/dom/node.ts index 7622c1676d..71b270868b 100644 --- a/packages/runtime-vapor/src/dom/node.ts +++ b/packages/runtime-vapor/src/dom/node.ts @@ -60,15 +60,23 @@ export function _child(node: ParentNode): Node { */ /*! #__NO_SIDE_EFFECTS__ */ export function __child(node: ParentNode, offset?: number): Node { - let n = offset ? __nthChild(node, offset) : node.firstChild! - - if (isComment(n, '[')) { - n = locateEndAnchor(n)!.nextSibling! + // when offset is -1, it means we need to get the text node of this element + // since server-side rendering doesn't generate whitespace placeholder text nodes, + // if firstChild is null, manually insert a text node and return it + if (offset === -1 && !node.firstChild) { + node.textContent = ' ' + return node.firstChild! } - while (n && isVaporAnchors(n)) { - n = n.nextSibling! + let n = offset ? __nthChild(node, offset) : node.firstChild! + while (n && (isComment(n, '[') || isVaporAnchors(n))) { + if (isComment(n, '[')) { + n = locateEndAnchor(n)!.nextSibling! + } else { + n = n.nextSibling! + } } + return n } -- 2.47.3