]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
refactor: add anchors for v-else-if branches
authordaiwei <daiwei521@126.com>
Tue, 12 Aug 2025 09:14:32 +0000 (17:14 +0800)
committerdaiwei <daiwei521@126.com>
Tue, 12 Aug 2025 09:24:13 +0000 (17:24 +0800)
packages/compiler-ssr/__tests__/ssrVaporAnchors.spec.ts
packages/compiler-ssr/src/transforms/ssrVIf.ts
packages/compiler-vapor/__tests__/transforms/__snapshots__/TransformTransition.spec.ts.snap
packages/compiler-vapor/__tests__/transforms/__snapshots__/vIf.spec.ts.snap
packages/compiler-vapor/src/generators/if.ts
packages/runtime-vapor/src/apiCreateIf.ts
packages/runtime-vapor/src/fragment.ts
packages/shared/src/domAnchors.ts

index 232b12e25da3b14b1360d5c255cabfda2721d45a..f5747b5b325dd3f3792be3a5ea6f42dd50cb3172 100644 (file)
@@ -130,10 +130,10 @@ describe('insertion anchors', () => {
             _push(\`<!--if-->\`)
           } else if (_ctx.bar) {
             _push(\`<span></span>\`)
-            _push(\`<!--if-->\`)
+            _push(\`<!--if--><!--if-->\`)
           } else {
             _push(\`<span></span>\`)
-            _push(\`<!--if-->\`)
+            _push(\`<!--if--><!--if-->\`)
           }
           _push(\`<!--p]--><span></span></div>\`"
       `)
@@ -163,10 +163,10 @@ describe('insertion anchors', () => {
                   _push(\`<!--if-->\`)
                 } else if (_ctx.bar) {
                   _push(\`<span\${_scopeId}></span>\`)
-                  _push(\`<!--if-->\`)
+                  _push(\`<!--if--><!--if-->\`)
                 } else {
                   _push(\`<span\${_scopeId}></span>\`)
-                  _push(\`<!--if-->\`)
+                  _push(\`<!--if--><!--if-->\`)
                 }
                 _push(\`<!--p]--><span\${_scopeId}></span></div>\`)
               } else {
@@ -233,7 +233,7 @@ describe('insertion anchors', () => {
               _push(\`<!---->\`)
             }
             _push(\`<!--p]--><span></span></span>\`)
-            _push(\`<!--if-->\`)
+            _push(\`<!--if--><!--if-->\`)
           } else {
             _push(\`<span><!--[p-->\`)
             if (_ctx.bar2) {
@@ -243,7 +243,7 @@ describe('insertion anchors', () => {
               _push(\`<!---->\`)
             }
             _push(\`<!--p]--><span></span></span>\`)
-            _push(\`<!--if-->\`)
+            _push(\`<!--if--><!--if-->\`)
           }
           _push(\`<!--p]--><span></span></div>\`"
       `)
@@ -296,7 +296,7 @@ describe('insertion anchors', () => {
                     _push(\`<!---->\`)
                   }
                   _push(\`<!--p]--><span\${_scopeId}></span></span>\`)
-                  _push(\`<!--if-->\`)
+                  _push(\`<!--if--><!--if-->\`)
                 } else {
                   _push(\`<span\${_scopeId}><!--[p-->\`)
                   if (_ctx.bar2) {
@@ -306,7 +306,7 @@ describe('insertion anchors', () => {
                     _push(\`<!---->\`)
                   }
                   _push(\`<!--p]--><span\${_scopeId}></span></span>\`)
-                  _push(\`<!--if-->\`)
+                  _push(\`<!--if--><!--if-->\`)
                 }
                 _push(\`<!--p]--><span\${_scopeId}></span></div>\`)
               } else {
@@ -500,8 +500,36 @@ describe('insertion anchors', () => {
   })
 })
 
-describe.todo('block anchors', () => {
-  test('if', () => {})
+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>`,
+        {
+          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]-->\`"
+    `)
+  })
 
   test('if in ssr slot vnode fallback', () => {})
 
@@ -513,6 +541,8 @@ describe.todo('block anchors', () => {
 
   test('slot in ssr slot vnode fallback', () => {})
 
+  test('forwarded slot', () => {})
+
   test('dynamic component', () => {})
 
   test('dynamic in ssr slot vnode fallback', () => {})
index 1470850c701e9fe501e929ca47dc382525316870..17ac6311386a149f541088bb087fb0463181f4e0 100644 (file)
@@ -37,6 +37,16 @@ export function ssrProcessIf(
   )
   context.pushStatement(ifStatement)
 
+  // anchor addition rules (matching runtime-vapor behavior):
+  // - v-else-if: the N-th branch → add N anchors
+  // - v-else: if there are M preceding branches → add M anchors
+  const isVapor = context.options.vapor
+  if (isVapor) {
+    ifStatement.consequent.body.push(
+      createCallExpression(`_push`, createIfAnchors(1)),
+    )
+  }
+
   let currentIf = ifStatement
   for (let i = 1; i < node.branches.length; i++) {
     const branch = node.branches[i]
@@ -51,9 +61,21 @@ export function ssrProcessIf(
         branch.condition,
         branchBlockStatement,
       )
+
+      if (isVapor) {
+        branchBlockStatement.body.push(
+          createCallExpression(`_push`, createIfAnchors(i + 1)),
+        )
+      }
     } else {
       // else
       currentIf.alternate = branchBlockStatement
+
+      if (isVapor) {
+        branchBlockStatement.body.push(
+          createCallExpression(`_push`, createIfAnchors(i)),
+        )
+      }
     }
   }
 
@@ -75,18 +97,14 @@ function processIfBranch(
     (children.length !== 1 || children[0].type !== NodeTypes.ELEMENT) &&
     // optimize away nested fragments when the only child is a ForNode
     !(children.length === 1 && children[0].type === NodeTypes.FOR)
-  const statement = processChildrenAsStatement(
-    branch,
-    context,
-    needFragmentWrapper,
-  )
 
-  // anchor for vapor v-if/v-else-if
-  if (context.options.vapor) {
-    statement.body.push(
-      createCallExpression(`_push`, [`\`<!--${IF_ANCHOR_LABEL}-->\``]),
-    )
-  }
+  return processChildrenAsStatement(branch, context, needFragmentWrapper)
+}
 
-  return statement
+function createIfAnchors(count: number): string[] {
+  const anchors: string[] = []
+  for (let i = 0; i < count; i++) {
+    anchors.push(`<!--${IF_ANCHOR_LABEL}-->`)
+  }
+  return [`\`${anchors.join('')}\``]
 }
index f83c69bd5507751ec664169d3351435dd5dfb990..8a83143b9b720b37d326245d05c49b6779faab40 100644 (file)
@@ -61,7 +61,7 @@ export function render(_ctx) {
         })
         n14.$key = 14
         return n14
-      }, null, true))
+      }))
       return [n0, n3, n7]
     }
   }, true)
index 12b7acd93721378f4b7317a11e5f67c8702368a1..272a28bc27f6efb4a5271d932e86c9fc35d64367 100644 (file)
@@ -36,7 +36,7 @@ export function render(_ctx) {
     const n10 = t3()
     const n11 = t4()
     return [n10, n11]
-  }, null, true))
+  }))
   const n13 = t5()
   const x13 = _child(n13, -1)
   _renderEffect(() => _setText(x13, _toDisplayString(_ctx.text)))
@@ -113,7 +113,7 @@ export function render(_ctx) {
   }, () => {
     const n7 = t2()
     return n7
-  }, null, true))
+  }))
   return n0
 }"
 `;
@@ -130,7 +130,7 @@ export function render(_ctx) {
   }, () => _createIf(() => (_ctx.orNot), () => {
     const n4 = t1()
     return n4
-  }, null, null, true))
+  }))
   return n0
 }"
 `;
index 3d16bee7c84bf3ce9b5061874d8ee748853ec78f..f4a3e599fab5ff9b8c282f5f3784d92321d7e1fa 100644 (file)
@@ -38,7 +38,6 @@ export function genIf(
       positiveArg,
       negativeArg,
       once && 'true',
-      isNested && 'true',
     ),
   )
 
index 01a9e3eb3aa24209272993ebf6348e683b059fd1..26f96a7e451515626f566c336327f731c5f70ee5 100644 (file)
@@ -1,4 +1,4 @@
-import { ELSE_IF_ANCHOR_LABEL, IF_ANCHOR_LABEL } from '@vue/shared'
+import { IF_ANCHOR_LABEL } from '@vue/shared'
 import { type Block, type BlockFn, insert } from './block'
 import { advanceHydrationNode, isHydrating } from './dom/hydration'
 import {
@@ -14,7 +14,6 @@ export function createIf(
   b1: BlockFn,
   b2?: BlockFn,
   once?: boolean,
-  elseIf?: boolean,
 ): Block {
   const _insertionParent = insertionParent
   const _insertionAnchor = insertionAnchor
@@ -26,9 +25,7 @@ export function createIf(
   } else {
     frag =
       isHydrating || __DEV__
-        ? new DynamicFragment(
-            elseIf && isHydrating ? ELSE_IF_ANCHOR_LABEL : IF_ANCHOR_LABEL,
-          )
+        ? new DynamicFragment(IF_ANCHOR_LABEL)
         : new DynamicFragment()
     renderEffect(() => (frag as DynamicFragment).update(condition() ? b1 : b2))
   }
index 53fc4340de29785e52b6eedbada42d63a0202b73..6e1cf5ecea7d46c2bc23f87799d94558fd706061 100644 (file)
@@ -23,7 +23,6 @@ import {
   applyTransitionLeaveHooks,
 } from './components/Transition'
 import type { VaporComponentInstance } from './component'
-import { ELSE_IF_ANCHOR_LABEL } from '@vue/shared'
 
 export class VaporFragment<T extends Block = Block>
   implements TransitionOptions
@@ -148,34 +147,25 @@ export class DynamicFragment extends VaporFragment {
     // avoid repeated hydration during rendering fallback
     if (this.anchor) return
 
-    const createAnchor = () => {
-      const { parentNode, nextSibling } = findLastChild(this.nodes)!
-      parentNode!.insertBefore(
-        // TODO use empty text node in PROD?
-        (this.anchor = createComment(label)),
-        nextSibling,
-      )
-    }
-
-    // manually create anchors for:
-    // 1. else-if branch
-    // (not present in SSR output)
-    if (label === ELSE_IF_ANCHOR_LABEL) {
-      createAnchor()
+    // for `v-if="false"`, the node will be an empty comment, use it as the anchor.
+    // otherwise, find next sibling vapor fragment anchor
+    if (label === 'if' && isEmpty) {
+      this.anchor = locateVaporFragmentAnchor(currentHydrationNode!, '')!
     } else {
-      // for `v-if="false"`, the node will be an empty comment, use it as the anchor.
-      // otherwise, find next sibling vapor fragment anchor
-      if (label === 'if' && isEmpty) {
-        this.anchor = locateVaporFragmentAnchor(currentHydrationNode!, '')!
-      } else {
-        this.anchor = locateVaporFragmentAnchor(currentHydrationNode!, label)!
-        if (!this.anchor && label === 'slot') {
-          // fallback to fragment end anchor for
-          this.anchor = locateVaporFragmentAnchor(currentHydrationNode!, ']')!
-        }
+      this.anchor = locateVaporFragmentAnchor(currentHydrationNode!, label)!
+      if (!this.anchor && label === 'slot') {
+        // fallback to fragment end anchor for
+        this.anchor = locateVaporFragmentAnchor(currentHydrationNode!, ']')!
+      }
 
-        // anchors are not present in ssr slot vnode fallback
-        if (!this.anchor) createAnchor()
+      // anchors are not present in ssr slot vnode fallback
+      if (!this.anchor) {
+        const { parentNode, nextSibling } = findLastChild(this.nodes)!
+        parentNode!.insertBefore(
+          // TODO use empty text node in PROD?
+          (this.anchor = createComment(label)),
+          nextSibling,
+        )
       }
     }
 
index fc8bc095c086edf762b61040ffdcb26db8af9e76..25b1b975c92f1b0e415b8e602e8117f2d3bbd3f9 100644 (file)
@@ -2,7 +2,6 @@ export const BLOCK_INSERTION_ANCHOR_LABEL = 'i'
 export const BLOCK_APPEND_ANCHOR_LABEL = 'a'
 export const BLOCK_PREPEND_ANCHOR_LABEL = 'p'
 export const IF_ANCHOR_LABEL: string = 'if'
-export const ELSE_IF_ANCHOR_LABEL: string = 'else-if'
 export const DYNAMIC_COMPONENT_ANCHOR_LABEL: string = 'dynamic-component'
 export const FOR_ANCHOR_LABEL: string = 'for'
 export const SLOT_ANCHOR_LABEL: string = 'slot'