]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
wip: refactor
authordaiwei <daiwei521@126.com>
Wed, 23 Apr 2025 13:45:10 +0000 (21:45 +0800)
committerdaiwei <daiwei521@126.com>
Wed, 23 Apr 2025 14:41:52 +0000 (22:41 +0800)
packages/compiler-ssr/__tests__/ssrElement.spec.ts
packages/compiler-ssr/src/ssrCodegenTransform.ts
packages/runtime-core/__tests__/hydration.spec.ts
packages/runtime-vapor/__tests__/hydration.spec.ts
packages/runtime-vapor/src/dom/node.ts

index fad89823893f99a08719544f5e458943f0dfcc17..d344405f3ed27d56acecb882c517ec53f53140c5 100644 (file)
@@ -398,7 +398,7 @@ describe('ssr: element', () => {
   })
 
   describe('dynamic anchor', () => {
-    test('consecutive components', () => {
+    test('two consecutive components', () => {
       expect(
         getCompiledString(`
         <div>
@@ -409,12 +409,37 @@ describe('ssr: element', () => {
         </div>
         `),
       ).toMatchInlineSnapshot(`
-        "\`<div><div></div><!--[[-->\`)
+        "\`<div><div></div>\`)
           _push(_ssrRenderComponent(_component_Comp1, null, null, _parent))
-          _push(\`<!--]]--><!--[[-->\`)
+          _push(\`<!--[[-->\`)
           _push(_ssrRenderComponent(_component_Comp2, null, null, _parent))
           _push(\`<!--]]--><div></div></div>\`"
       `)
     })
+
+    test('multiple consecutive components', () => {
+      expect(
+        getCompiledString(`
+        <div>
+          <div/>
+          <Comp1/>
+          <Comp2/>
+          <Comp3/>
+          <Comp4/>
+          <div/>
+        </div>
+        `),
+      ).toMatchInlineSnapshot(`
+        "\`<div><div></div>\`)
+          _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(\`<div></div></div>\`"
+      `)
+    })
   })
 })
index 85118d91b3491eac13b0f925e927ded41ded4f28..14ea4177dd45bccda466d360aeb9f0be8850f563 100644 (file)
@@ -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 `<!--]]-->`).
- *
- * <element>
- *   <element/>  // Static previous sibling
- *   <Comp/>     // Dynamic node (current)
- *   <Comp/>     // Dynamic next sibling
- *   <element/>  // Static next sibling
- * </element>
- */
-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
+ * <element>
+ *   <element/>  // Static node
+ *   <Comp/>     // Dynamic node -> should NOT be wrapped
+ *   <Comp/>     // Dynamic node -> should be wrapped
+ *   <element/>  // Static node
+ * </element>
+ *
+ * 2. three or more consecutive dynamic nodes should only wrap the
+ *    middle nodes, leaving the first and last static.
+ * <element>
+ *  <element/>  // Static node
+ *  <Comp/>     // Dynamic node -> should NOT be wrapped
+ *  <Comp/>     // Dynamic node -> should be wrapped
+ *  <Comp/>     // Dynamic node -> should be wrapped
+ *  <Comp/>     // Dynamic node -> should NOT be wrapped
+ *  <element/>  // 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
   }
index 23b223526ff6e8b461fe3eccd3c1e65b96ebc136..43d536e6d596e247c3826d2aeb48721b7dc3f50d 100644 (file)
@@ -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(
-        `<div><span></span><!--[[-->foo<!--]]--><!--[[-->foo<!--]]--><span></span></div>`,
+        `<div><span></span>foo<!--[[-->foo<!--]]--><span></span></div>`,
         () => 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(
+        `<div><span></span>foo<!--[[-->foo<!--]]-->foo<span></span></div>`,
+        () => 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', () => {
index 0f039674ef578ce7cc4aca24627596e390e3b5fd..67f5c634bb4b48994486f519b3651666171d2c25 100644 (file)
@@ -280,13 +280,13 @@ describe('Vapor Mode hydration', () => {
       },
     )
     expect(container.innerHTML).toMatchInlineSnapshot(
-      `"<div><span></span><!--[[-->foo<!--]]--><!--[[-->foo<!--]]--><span></span></div>"`,
+      `"<div><span></span>foo<!--[[-->foo<!--]]--><span></span></div>"`,
     )
 
     data.value = 'bar'
     await nextTick()
     expect(container.innerHTML).toMatchInlineSnapshot(
-      `"<div><span></span><!--[[-->bar<!--]]--><!--[[-->bar<!--]]--><span></span></div>"`,
+      `"<div><span></span>bar<!--[[-->bar<!--]]--><span></span></div>"`,
     )
   })
 
@@ -385,13 +385,13 @@ describe('Vapor Mode hydration', () => {
       },
     )
     expect(container.innerHTML).toMatchInlineSnapshot(
-      `"<div><span></span><!--[[--><!--[--><div>foo</div>-foo<!--]--><!--]]--><!--[[--><!--[--><div>foo</div>-foo<!--]--><!--]]--><span></span></div>"`,
+      `"<div><span></span><!--[--><div>foo</div>-foo<!--]--><!--[[--><!--[--><div>foo</div>-foo<!--]--><!--]]--><span></span></div>"`,
     )
 
     data.value = 'bar'
     await nextTick()
     expect(container.innerHTML).toMatchInlineSnapshot(
-      `"<div><span></span><!--[[--><!--[--><div>bar</div>-bar<!--]--><!--]]--><!--[[--><!--[--><div>bar</div>-bar<!--]--><!--]]--><span></span></div>"`,
+      `"<div><span></span><!--[--><div>bar</div>-bar<!--]--><!--[[--><!--[--><div>bar</div>-bar<!--]--><!--]]--><span></span></div>"`,
     )
   })
 
index 91fc2714fa3da58981ba2b504314d3fd217d056f..66f1a42f849006db5e76eb9cc1a71a6b0e7a2540 100644 (file)
@@ -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)!
   }