From: daiwei Date: Wed, 23 Apr 2025 13:45:10 +0000 (+0800) Subject: wip: refactor X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=04eadd859a6594ca144523e265ade5f087c4ba4b;p=thirdparty%2Fvuejs%2Fcore.git wip: refactor --- diff --git a/packages/compiler-ssr/__tests__/ssrElement.spec.ts b/packages/compiler-ssr/__tests__/ssrElement.spec.ts index fad8982389..d344405f3e 100644 --- a/packages/compiler-ssr/__tests__/ssrElement.spec.ts +++ b/packages/compiler-ssr/__tests__/ssrElement.spec.ts @@ -398,7 +398,7 @@ describe('ssr: element', () => { }) describe('dynamic anchor', () => { - test('consecutive components', () => { + test('two consecutive components', () => { expect( getCompiledString(`
@@ -409,12 +409,37 @@ describe('ssr: element', () => {
`), ).toMatchInlineSnapshot(` - "\`
\`) + "\`
\`) _push(_ssrRenderComponent(_component_Comp1, null, null, _parent)) - _push(\`\`) + _push(\`\`) _push(_ssrRenderComponent(_component_Comp2, null, null, _parent)) _push(\`
\`" `) }) + + test('multiple consecutive components', () => { + expect( + getCompiledString(` +
+
+ + + + +
+
+ `), + ).toMatchInlineSnapshot(` + "\`
\`) + _push(_ssrRenderComponent(_component_Comp1, null, null, _parent)) + _push(\`\`) + _push(_ssrRenderComponent(_component_Comp2, null, null, _parent)) + _push(\`\`) + _push(_ssrRenderComponent(_component_Comp3, null, null, _parent)) + _push(\`\`) + _push(_ssrRenderComponent(_component_Comp4, null, null, _parent)) + _push(\`
\`" + `) + }) }) }) diff --git a/packages/compiler-ssr/src/ssrCodegenTransform.ts b/packages/compiler-ssr/src/ssrCodegenTransform.ts index 85118d91b3..14ea4177dd 100644 --- a/packages/compiler-ssr/src/ssrCodegenTransform.ts +++ b/packages/compiler-ssr/src/ssrCodegenTransform.ts @@ -7,6 +7,7 @@ import { type IfStatement, type JSChildNode, NodeTypes, + type PlainElementNode, type RootNode, type TemplateChildNode, type TemplateLiteral, @@ -166,10 +167,14 @@ export function processChildren( context.pushStringPart(``) } - const { children } = parent + const { children, type, tagType } = parent as PlainElementNode + const inElement = + type === NodeTypes.ELEMENT && tagType === ElementTypes.ELEMENT + if (inElement) processChildrenDynamicInfo(children) + for (let i = 0; i < children.length; i++) { const child = children[i] - if (shouldProcessAsDynamic(parent, child)) { + if (inElement && shouldProcessChildAsDynamic(parent, child)) { processChildren( { children: [child] }, context, @@ -274,87 +279,127 @@ const isStaticChildNode = (c: TemplateChildNode): boolean => c.type === NodeTypes.TEXT || c.type === NodeTypes.COMMENT -/** - * Check if a node should be processed as dynamic. - * This is primarily used in Vapor mode hydration to wrap dynamic parts - * with markers (`` and ``). - * - * - * // Static previous sibling - * // Dynamic node (current) - * // Dynamic next sibling - * // Static next sibling - * - */ -function shouldProcessAsDynamic( - parent: { tag?: string; children: TemplateChildNode[] }, - node: TemplateChildNode, -): boolean { - // 1. Must be a dynamic node type - if (isStaticChildNode(node)) return false - // 2. Must be inside a parent element - if (!parent.tag) return false +interface DynamicInfo { + hasStaticPrevious: boolean + hasStaticNext: boolean + prevDynamicCount: number + nextDynamicCount: number +} - const children = parent.children.filter( +function processChildrenDynamicInfo( + children: (TemplateChildNode & { _ssrDynamicInfo?: DynamicInfo })[], +): void { + const filteredChildren = children.filter( child => !(child.type === NodeTypes.TEXT && !child.content.trim()), ) - const len = children.length - const index = children.indexOf(node) - // 3. Check for a static previous sibling - let hasStaticPreviousSibling = false - if (index > 0) { - for (let i = index - 1; i >= 0; i--) { - if (isStaticChildNode(children[i])) { - hasStaticPreviousSibling = true + for (let i = 0; i < filteredChildren.length; i++) { + const child = filteredChildren[i] + if (isStaticChildNode(child)) continue + + child._ssrDynamicInfo = { + hasStaticPrevious: false, + hasStaticNext: false, + prevDynamicCount: 0, + nextDynamicCount: 0, + } + + const info = child._ssrDynamicInfo + + // Calculate the previous static and dynamic node counts + let foundStaticPrev = false + let dynamicCountPrev = 0 + for (let j = i - 1; j >= 0; j--) { + const prevChild = filteredChildren[j] + if (isStaticChildNode(prevChild)) { + foundStaticPrev = true break } + // if the previous child has dynamic info, use it + else if (prevChild._ssrDynamicInfo) { + foundStaticPrev = prevChild._ssrDynamicInfo.hasStaticPrevious + dynamicCountPrev = prevChild._ssrDynamicInfo.prevDynamicCount + 1 + break + } + dynamicCountPrev++ } - } - if (!hasStaticPreviousSibling) return false + info.hasStaticPrevious = foundStaticPrev + info.prevDynamicCount = dynamicCountPrev - // 4. Check for a static next sibling - let hasStaticNextSibling = false - if (index > -1 && index < len - 1) { - for (let i = index + 1; i < len; i++) { - if (isStaticChildNode(children[i])) { - hasStaticNextSibling = true + // Calculate the number of static and dynamic nodes afterwards + let foundStaticNext = false + let dynamicCountNext = 0 + for (let j = i + 1; j < filteredChildren.length; j++) { + const nextChild = filteredChildren[j] + if (isStaticChildNode(nextChild)) { + foundStaticNext = true break } + // if the next child has dynamic info, use it + else if (nextChild._ssrDynamicInfo) { + foundStaticNext = nextChild._ssrDynamicInfo.hasStaticNext + dynamicCountNext = nextChild._ssrDynamicInfo.nextDynamicCount + 1 + break + } + dynamicCountNext++ } + info.hasStaticNext = foundStaticNext + info.nextDynamicCount = dynamicCountNext } - if (!hasStaticNextSibling) return false +} - // 5. Calculate the number and location of continuous dynamic nodes - let dynamicNodeCount = 1 // The current node is counted as one - let prevDynamicCount = 0 - let nextDynamicCount = 0 +/** + * Check if a node should be processed as dynamic. + * This is primarily used in Vapor mode hydration to wrap dynamic parts + * with markers (`` and ``). + * The purpose is to distinguish the boundaries of nodes during hydration + * + * 1. two consecutive dynamic nodes should only wrap the second one + * + * // Static node + * // Dynamic node -> should NOT be wrapped + * // Dynamic node -> should be wrapped + * // Static node + * + * + * 2. three or more consecutive dynamic nodes should only wrap the + * middle nodes, leaving the first and last static. + * + * // Static node + * // Dynamic node -> should NOT be wrapped + * // Dynamic node -> should be wrapped + * // Dynamic node -> should be wrapped + * // Dynamic node -> should NOT be wrapped + * // Static node + */ +function shouldProcessChildAsDynamic( + parent: { tag?: string; children: TemplateChildNode[] }, + node: TemplateChildNode & { _ssrDynamicInfo?: DynamicInfo }, +): boolean { + // must be inside a parent element + if (!parent.tag) return false - // Count consecutive dynamic nodes forward - for (let i = index - 1; i >= 0; i--) { - if (!isStaticChildNode(children[i])) { - prevDynamicCount++ - } else { - break - } - } + // must has dynamic info + const { _ssrDynamicInfo: info } = node + if (!info) return false - // Count consecutive dynamic nodes backwards - for (let i = index + 1; i < len; i++) { - if (!isStaticChildNode(children[i])) { - nextDynamicCount++ - } else { - break - } - } + const { + hasStaticPrevious, + hasStaticNext, + prevDynamicCount, + nextDynamicCount, + } = info + + // must have static nodes on both sides + if (!hasStaticPrevious || !hasStaticNext) return false - dynamicNodeCount = 1 + prevDynamicCount + nextDynamicCount + const dynamicNodeCount = 1 + prevDynamicCount + nextDynamicCount - // For two consecutive dynamic nodes, mark both as dynamic + // For two consecutive dynamic nodes, mark the second one as dynamic if (dynamicNodeCount === 2) { - return prevDynamicCount > 0 || nextDynamicCount > 0 + return prevDynamicCount > 0 } - // For three or more dynamic nodes, only mark the intermediate nodes as dynamic + // For three or more dynamic nodes, mark the intermediate node as dynamic else if (dynamicNodeCount >= 3) { return prevDynamicCount > 0 && nextDynamicCount > 0 } diff --git a/packages/runtime-core/__tests__/hydration.spec.ts b/packages/runtime-core/__tests__/hydration.spec.ts index 23b223526f..43d536e6d5 100644 --- a/packages/runtime-core/__tests__/hydration.spec.ts +++ b/packages/runtime-core/__tests__/hydration.spec.ts @@ -1844,19 +1844,33 @@ describe('SSR hydration', () => { }) describe('dynamic anchor', () => { - test('consecutive components', () => { + test('two consecutive components', () => { const Comp = { render() { return createTextVNode('foo') }, } const { vnode, container } = mountWithHydration( - `
foofoo
`, + `
foofoo
`, () => h('div', null, [h('span'), h(Comp), h(Comp), h('span')]), ) expect(vnode.el).toBe(container.firstChild) expect(`Hydration children mismatch`).not.toHaveBeenWarned() }) + + test('multiple consecutive components', () => { + const Comp = { + render() { + return createTextVNode('foo') + }, + } + const { vnode, container } = mountWithHydration( + `
foofoofoo
`, + () => h('div', null, [h('span'), h(Comp), h(Comp), h(Comp), h('span')]), + ) + expect(vnode.el).toBe(container.firstChild) + expect(`Hydration children mismatch`).not.toHaveBeenWarned() + }) }) describe('mismatch handling', () => { diff --git a/packages/runtime-vapor/__tests__/hydration.spec.ts b/packages/runtime-vapor/__tests__/hydration.spec.ts index 0f039674ef..67f5c634bb 100644 --- a/packages/runtime-vapor/__tests__/hydration.spec.ts +++ b/packages/runtime-vapor/__tests__/hydration.spec.ts @@ -280,13 +280,13 @@ describe('Vapor Mode hydration', () => { }, ) expect(container.innerHTML).toMatchInlineSnapshot( - `"
foofoo
"`, + `"
foofoo
"`, ) data.value = 'bar' await nextTick() expect(container.innerHTML).toMatchInlineSnapshot( - `"
barbar
"`, + `"
barbar
"`, ) }) @@ -385,13 +385,13 @@ describe('Vapor Mode hydration', () => { }, ) expect(container.innerHTML).toMatchInlineSnapshot( - `"
foo
-foo
foo
-foo
"`, + `"
foo
-foo
foo
-foo
"`, ) data.value = 'bar' await nextTick() expect(container.innerHTML).toMatchInlineSnapshot( - `"
bar
-bar
bar
-bar
"`, + `"
bar
-bar
bar
-bar
"`, ) }) diff --git a/packages/runtime-vapor/src/dom/node.ts b/packages/runtime-vapor/src/dom/node.ts index 91fc2714fa..66f1a42f84 100644 --- a/packages/runtime-vapor/src/dom/node.ts +++ b/packages/runtime-vapor/src/dom/node.ts @@ -42,8 +42,13 @@ function _next(node: Node): Node { /*! #__NO_SIDE_EFFECTS__ */ function __next(node: Node): Node { - // process fragment as a single node - if (node && isComment(node, '[')) { + // treat dynamic node (...) as a single node + if (node && isComment(node, '[[')) { + node = locateEndAnchor(node, '[[', ']]')! + } + + // treat dynamic node (...) as a single node + else if (node && isComment(node, '[')) { node = locateEndAnchor(node)! }