]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
wip: inject block anchors in ssr vnode-based slot
authordaiwei <daiwei521@126.com>
Wed, 13 Aug 2025 01:05:03 +0000 (09:05 +0800)
committerdaiwei <daiwei521@126.com>
Wed, 13 Aug 2025 01:05:03 +0000 (09:05 +0800)
packages/compiler-ssr/__tests__/ssrVaporAnchors.spec.ts
packages/compiler-ssr/src/transforms/ssrTransformComponent.ts

index a2aab28e749dad855ad08a548abb631243d5db35..745bf65f42d698ceae6174657f54575b46655608 100644 (file)
@@ -95,9 +95,11 @@ describe('insertion anchors', () => {
                   _createVNode("div", null, [
                     _createCommentVNode("[p"),
                     _renderSlot(_ctx.$slots, "foo"),
+                    _createCommentVNode("slot"),
                     _createCommentVNode("p]"),
                     _createCommentVNode("[p"),
                     _renderSlot(_ctx.$slots, "default"),
+                    _createCommentVNode("slot"),
                     _createCommentVNode("p]"),
                     _createVNode("span")
                   ])
@@ -174,10 +176,16 @@ describe('insertion anchors', () => {
                   _createVNode("div", null, [
                     _createCommentVNode("[p"),
                     (_ctx.foo)
-                      ? (_openBlock(), _createBlock("span", { key: 0 }))
+                      ? (_openBlock(), _createBlock("span", { key: 0 }, [
+                          _createCommentVNode("if")
+                        ]))
                       : (_ctx.bar)
-                        ? (_openBlock(), _createBlock("span", { key: 1 }))
-                        : (_openBlock(), _createBlock("span", { key: 2 })),
+                        ? (_openBlock(), _createBlock("span", { key: 1 }, [
+                            _createCommentVNode("if--><!--if")
+                          ]))
+                        : (_openBlock(), _createBlock("span", { key: 2 }, [
+                            _createCommentVNode("if--><!--if")
+                          ])),
                     _createCommentVNode("p]"),
                     _createVNode("span")
                   ])
@@ -319,8 +327,10 @@ describe('insertion anchors', () => {
                           (_ctx.foo1)
                             ? (_openBlock(), _createBlock("span", { key: 0 }))
                             : _createCommentVNode("v-if", true),
+                          _createCommentVNode("if"),
                           _createCommentVNode("p]"),
-                          _createVNode("span")
+                          _createVNode("span"),
+                          _createCommentVNode("if")
                         ]))
                       : (_ctx.bar)
                         ? (_openBlock(), _createBlock("span", { key: 1 }, [
@@ -328,16 +338,20 @@ describe('insertion anchors', () => {
                             (_ctx.bar1)
                               ? (_openBlock(), _createBlock("span", { key: 0 }))
                               : _createCommentVNode("v-if", true),
+                            _createCommentVNode("if"),
                             _createCommentVNode("p]"),
-                            _createVNode("span")
+                            _createVNode("span"),
+                            _createCommentVNode("if--><!--if")
                           ]))
                         : (_openBlock(), _createBlock("span", { key: 2 }, [
                             _createCommentVNode("[p"),
                             (_ctx.bar2)
                               ? (_openBlock(), _createBlock("span", { key: 0 }))
                               : _createCommentVNode("v-if", true),
+                            _createCommentVNode("if"),
                             _createCommentVNode("p]"),
-                            _createVNode("span")
+                            _createVNode("span"),
+                            _createCommentVNode("if--><!--if")
                           ])),
                     _createCommentVNode("p]"),
                     _createVNode("span")
@@ -392,10 +406,12 @@ describe('insertion anchors', () => {
                               _createTextVNode(" foo ")
                             ], 64 /* STABLE_FRAGMENT */))
                           : _createCommentVNode("v-if", true),
+                        _createCommentVNode("if"),
                         _createCommentVNode("p]"),
                         _createVNode("div")
                       ]))
-                    : _createCommentVNode("v-if", true)
+                    : _createCommentVNode("v-if", true),
+                  _createCommentVNode("if")
                 ]
               }
             }),
@@ -446,6 +462,7 @@ describe('insertion anchors', () => {
                     (_openBlock(true), _createBlock(_Fragment, null, _renderList(_ctx.items, (item) => {
                       return (_openBlock(), _createBlock("span"))
                     }), 256 /* UNKEYED_FRAGMENT */)),
+                    _createCommentVNode("for"),
                     _createCommentVNode("p]"),
                     _createVNode("span")
                   ])
@@ -558,46 +575,186 @@ describe('block anchors', () => {
   test('if', () => {
     expect(
       getCompiledString(
-        `<span v-if="count === 1">1</span>
-        <span v-else-if="count === 2">2</span>
-        <span v-else-if="count === 3">3</span>
-        <span v-else>4</span>`,
+        `<component :is="tag">
+          <span v-if="count === 1">1</span>
+          <span v-else-if="count === 2">2</span>
+          <span v-else-if="count === 3">3</span>
+          <span v-else>4</span>
+        </component>`,
         {
           vapor: true,
         },
       ),
     ).toMatchInlineSnapshot(`
       "\`<!--[a-->\`)
-        if (_ctx.count === 1) {
-          _push(\`<span>1</span>\`)
-          _push(\`<!--if-->\`)
-        } else if (_ctx.count === 2) {
-          _push(\`<span>2</span>\`)
-          _push(\`<!--if--><!--if-->\`)
-        } else if (_ctx.count === 3) {
-          _push(\`<span>3</span>\`)
-          _push(\`<!--if--><!--if--><!--if-->\`)
-        } else {
-          _push(\`<span>4</span>\`)
-          _push(\`<!--if--><!--if--><!--if-->\`)
-        }
-        _push(\`<!--a]-->\`"
+        _ssrRenderVNode(_push, _createVNode(_resolveDynamicComponent(_ctx.tag), null, {
+          default: _withCtx((_, _push, _parent, _scopeId) => {
+            if (_push) {
+              if (_ctx.count === 1) {
+                _push(\`<span\${_scopeId}>1</span>\`)
+                _push(\`<!--if-->\`)
+              } else if (_ctx.count === 2) {
+                _push(\`<span\${_scopeId}>2</span>\`)
+                _push(\`<!--if--><!--if-->\`)
+              } else if (_ctx.count === 3) {
+                _push(\`<span\${_scopeId}>3</span>\`)
+                _push(\`<!--if--><!--if--><!--if-->\`)
+              } else {
+                _push(\`<span\${_scopeId}>4</span>\`)
+                _push(\`<!--if--><!--if--><!--if-->\`)
+              }
+            } else {
+              return [
+                (_ctx.count === 1)
+                  ? (_openBlock(), _createBlock("span", { key: 0 }, [
+                      _createTextVNode("1"),
+                      _createCommentVNode("if")
+                    ]))
+                  : (_ctx.count === 2)
+                    ? (_openBlock(), _createBlock("span", { key: 1 }, [
+                        _createTextVNode("2"),
+                        _createCommentVNode("if--><!--if")
+                      ]))
+                    : (_ctx.count === 3)
+                      ? (_openBlock(), _createBlock("span", { key: 2 }, [
+                          _createTextVNode("3"),
+                          _createCommentVNode("if--><!--if--><!--if")
+                        ]))
+                      : (_openBlock(), _createBlock("span", { key: 3 }, [
+                          _createTextVNode("4"),
+                          _createCommentVNode("if--><!--if--><!--if")
+                        ]))
+              ]
+            }
+          }),
+          _: 1 /* STABLE */
+        }), _parent)
+        _push(\`<!--dynamic-component--><!--a]-->\`"
     `)
   })
 
-  test('if in ssr slot vnode fallback', () => {})
-
-  test('for', () => {})
-
-  test('for in ssr slot vnode fallback', () => {})
-
-  test('slot', () => {})
-
-  test('slot in ssr slot vnode fallback', () => {})
+  test('for', () => {
+    expect(
+      getCompiledString(
+        `<component :is="tag">
+          <span v-for="item in items">{{item}}</span>
+        </component>`,
+        {
+          vapor: true,
+        },
+      ),
+    ).toMatchInlineSnapshot(`
+      "\`<!--[a-->\`)
+        _ssrRenderVNode(_push, _createVNode(_resolveDynamicComponent(_ctx.tag), null, {
+          default: _withCtx((_, _push, _parent, _scopeId) => {
+            if (_push) {
+              _push(\`<!--[-->\`)
+              _ssrRenderList(_ctx.items, (item) => {
+                _push(\`<span\${
+                  _scopeId
+                }>\${
+                  _ssrInterpolate(item)
+                }</span>\`)
+              })
+              _push(\`<!--]--><!--for-->\`)
+            } else {
+              return [
+                (_openBlock(true), _createBlock(_Fragment, null, _renderList(_ctx.items, (item) => {
+                  return (_openBlock(), _createBlock("span", null, _toDisplayString(item), 1 /* TEXT */))
+                }), 256 /* UNKEYED_FRAGMENT */)),
+                _createCommentVNode("for")
+              ]
+            }
+          }),
+          _: 1 /* STABLE */
+        }), _parent)
+        _push(\`<!--dynamic-component--><!--a]-->\`"
+    `)
+  })
 
-  test('forwarded slot', () => {})
+  test('slot', () => {
+    expect(
+      getCompiledString(
+        `<div>
+          <slot name="foo"/>
+          <slot/>
+        </div>`,
+        { vapor: true },
+      ),
+    ).toMatchInlineSnapshot(`
+      "\`<div><!--[a-->\`)
+        _ssrRenderSlot(_ctx.$slots, "foo", {}, null, _push, _parent)
+        _push(\`<!--slot--><!--a]--><!--[a-->\`)
+        _ssrRenderSlot(_ctx.$slots, "default", {}, null, _push, _parent)
+        _push(\`<!--slot--><!--a]--></div>\`"
+    `)
+  })
 
-  test('dynamic component', () => {})
+  test('forwarded slot', () => {
+    expect(
+      getCompiledString(
+        `<component :is="tag">
+          <slot name="foo"/>
+          <slot/>
+        </component>`,
+        { vapor: true },
+      ),
+    ).toMatchInlineSnapshot(`
+      "\`<!--[a-->\`)
+        _ssrRenderVNode(_push, _createVNode(_resolveDynamicComponent(_ctx.tag), null, {
+          default: _withCtx((_, _push, _parent, _scopeId) => {
+            if (_push) {
+              _ssrRenderSlot(_ctx.$slots, "foo", {}, null, _push, _parent, _scopeId)
+              _push(\`<!--slot-->\`)
+              _ssrRenderSlot(_ctx.$slots, "default", {}, null, _push, _parent, _scopeId)
+              _push(\`<!--slot-->\`)
+            } else {
+              return [
+                _renderSlot(_ctx.$slots, "foo"),
+                _createCommentVNode("slot"),
+                _renderSlot(_ctx.$slots, "default"),
+                _createCommentVNode("slot")
+              ]
+            }
+          }),
+          _: 3 /* FORWARDED */
+        }), _parent)
+        _push(\`<!--dynamic-component--><!--a]-->\`"
+    `)
+  })
 
-  test('dynamic in ssr slot vnode fallback', () => {})
+  test('dynamic component', () => {
+    expect(
+      getCompiledString(
+        `<component is='tag'>
+          <div>
+            <component is="foo"/>
+          </div>
+        </component>`,
+        { vapor: true },
+      ),
+    ).toMatchInlineSnapshot(`
+      "\`<!--[a-->\`)
+        _ssrRenderVNode(_push, _createVNode(_resolveDynamicComponent("tag"), null, {
+          default: _withCtx((_, _push, _parent, _scopeId) => {
+            if (_push) {
+              _push(\`<div\${_scopeId}><!--[a-->\`)
+              _ssrRenderVNode(_push, _createVNode(_resolveDynamicComponent("foo"), null, null), _parent, _scopeId)
+              _push(\`<!--dynamic-component--><!--a]--></div>\`)
+            } else {
+              return [
+                _createVNode("div", null, [
+                  _createCommentVNode("[a"),
+                  (_openBlock(), _createBlock(_resolveDynamicComponent("foo"))),
+                  _createCommentVNode("dynamic-component"),
+                  _createCommentVNode("a]")
+                ])
+              ]
+            }
+          }),
+          _: 1 /* STABLE */
+        }), _parent)
+        _push(\`<!--dynamic-component--><!--a]-->\`"
+    `)
+  })
 })
index beec63151c851966e6d02b166e650e4147e25386..53e6355fab9f01ad1468c6e16a0f98cf9c84b801 100644 (file)
@@ -1,6 +1,7 @@
 import {
   CREATE_VNODE,
   type CallExpression,
+  type CommentNode,
   type CompilerOptions,
   type ComponentNode,
   DOMDirectiveTransforms,
@@ -62,6 +63,9 @@ import {
 } from './ssrTransformTransitionGroup'
 import {
   DYNAMIC_COMPONENT_ANCHOR_LABEL,
+  FOR_ANCHOR_LABEL,
+  IF_ANCHOR_LABEL,
+  SLOT_ANCHOR_LABEL,
   extend,
   isArray,
   isObject,
@@ -338,9 +342,8 @@ function createVNodeSlotBranch(
   if (vFor) {
     wrapperProps.push(extend({}, vFor))
   }
-
   if (parentContext.vapor) {
-    children = injectVaporInsertionAnchors(children, parent)
+    children = injectVaporAnchors(children, parent)
   }
 
   const wrapperNode: TemplateNode = {
@@ -395,7 +398,7 @@ function subTransform(
   // - hoists are not enabled for the client branch here
 }
 
-function injectVaporInsertionAnchors(
+function injectVaporAnchors(
   children: TemplateChildNode[],
   parent: TemplateChildNode,
 ): TemplateChildNode[] {
@@ -403,26 +406,25 @@ function injectVaporInsertionAnchors(
     processBlockNodeAnchor(children)
   }
 
-  const newChildren: TemplateChildNode[] = new Array(children.length * 3)
-  let newIndex = 0
+  const newChildren: TemplateChildNode[] = []
 
   for (let i = 0; i < children.length; i++) {
     const child = children[i]
 
     if (child.type !== NodeTypes.ELEMENT) {
-      newChildren[newIndex++] = child
+      newChildren.push(child)
       continue
     }
 
     const { tagType, props } = child
-    let anchor: string | undefined
+    let insertionAnchor: string | undefined
 
     if (
       tagType === ElementTypes.COMPONENT ||
       tagType === ElementTypes.SLOT ||
       tagType === ElementTypes.TEMPLATE
     ) {
-      anchor = child.anchor
+      insertionAnchor = child.anchor
     } else if (tagType === ElementTypes.ELEMENT) {
       let hasIf = false
       let hasFor = false
@@ -437,49 +439,73 @@ function injectVaporInsertionAnchors(
       }
 
       if (hasIf) {
-        anchor = (child as any as IfNode).anchor
-        if (anchor) {
-          // find sibling else-if/else branches
-          // inject anchor after else-if/else branch if founded
-          // otherwise inject after if node
-          const lastBranchIndex = findLastIfBranchIndex(children, i)
-          if (lastBranchIndex > i) {
-            // inject anchor before if node
-            newChildren[newIndex++] = createAnchorComment(`[${anchor}`)
-
-            // copy branch nodes
-            for (let j = i; j <= lastBranchIndex; j++) {
-              const node = children[j] as PlainElementNode
-              newChildren[newIndex++] = node
-
-              node.children = injectVaporInsertionAnchors(node.children, node)
+        insertionAnchor = (child as any as IfNode).anchor
+        // find sibling else-if/else branches
+        // inject anchor after else-if/else branch if founded
+        // otherwise inject after if node
+        const lastBranchIndex = findLastIfBranchIndex(children, i)
+        if (lastBranchIndex > i) {
+          // inject anchor before if node
+          if (insertionAnchor) {
+            newChildren.push(createAnchor(`[${insertionAnchor}`))
+          }
+
+          // copy branch nodes
+          for (let j = i; j <= lastBranchIndex; j++) {
+            const node = children[j] as PlainElementNode
+            newChildren.push(node)
+
+            // inject block anchor
+            const blockAnchorLabel = getBlockAnchorLabel(node)
+            if (blockAnchorLabel) {
+              const isElse = node.props.some(p => p.name === 'else')
+              const repeatCount = j - i - (isElse ? 1 : 0) + 1
+              node.children.push(
+                createAnchor(
+                  `<!--${blockAnchorLabel}-->`.repeat(repeatCount).slice(4, -3),
+                ),
+              )
             }
 
-            // inject anchor after branch nodes
-            newChildren[newIndex++] = createAnchorComment(`${anchor}]`)
+            node.children = injectVaporAnchors(node.children, node)
+          }
 
-            i = lastBranchIndex
-            continue
+          // inject anchor after branch nodes
+          if (insertionAnchor) {
+            newChildren.push(createAnchor(`${insertionAnchor}]`))
           }
+
+          i = lastBranchIndex
+          continue
         }
       } else if (hasFor) {
-        anchor = (child as any as ForNode).anchor
+        insertionAnchor = (child as any as ForNode).anchor
       }
     }
 
     // inject anchor before and after the child
-    if (anchor) newChildren[newIndex++] = createAnchorComment(`[${anchor}`)
-    newChildren[newIndex++] = child
-    if (anchor) newChildren[newIndex++] = createAnchorComment(`${anchor}]`)
+    if (insertionAnchor) {
+      newChildren.push(createAnchor(`[${insertionAnchor}`))
+    }
+
+    newChildren.push(child)
 
-    child.children = injectVaporInsertionAnchors(child.children, child)
+    // inject block anchor
+    const blockAnchorLabel = getBlockAnchorLabel(child)
+    if (blockAnchorLabel) newChildren.push(createAnchor(blockAnchorLabel))
+
+    // inject insertion anchor
+    if (insertionAnchor) {
+      newChildren.push(createAnchor(`${insertionAnchor}]`))
+    }
+
+    child.children = injectVaporAnchors(child.children, child)
   }
 
-  newChildren.length = newIndex
   return newChildren
 }
 
-function createAnchorComment(content: string): TemplateChildNode {
+function createAnchor(content: string): CommentNode {
   return {
     type: NodeTypes.COMMENT,
     content,
@@ -526,6 +552,24 @@ function findLastIfBranchIndex(
   return lastIndex
 }
 
+function getBlockAnchorLabel(child: TemplateChildNode): string | undefined {
+  if (child.type !== NodeTypes.ELEMENT) return
+
+  if (child.tagType === ElementTypes.COMPONENT && child.tag === 'component') {
+    return DYNAMIC_COMPONENT_ANCHOR_LABEL
+  } else if (child.tagType === ElementTypes.SLOT) {
+    return SLOT_ANCHOR_LABEL
+  } else if (
+    child.props.some(
+      p => p.name === 'if' || p.name === 'else-if' || p.name === 'else',
+    )
+  ) {
+    return IF_ANCHOR_LABEL
+  } else if (child.props.some(p => p.name === 'for')) {
+    return FOR_ANCHOR_LABEL
+  }
+}
+
 function clone(v: any): any {
   if (isArray(v)) {
     return v.map(clone)