]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
fix(compiler-vapor): support `v-if` and `v-for` on the same `<template>` element...
authoredison <daiwei521@126.com>
Wed, 7 Jan 2026 08:49:10 +0000 (16:49 +0800)
committerGitHub <noreply@github.com>
Wed, 7 Jan 2026 08:49:10 +0000 (16:49 +0800)
packages/compiler-vapor/__tests__/transforms/__snapshots__/vIf.spec.ts.snap
packages/compiler-vapor/__tests__/transforms/vIf.spec.ts
packages/compiler-vapor/src/transforms/utils.ts

index d838c5f2f1b7ac0bfb3fb85074e51b3e6a0ce26e..36de803052b3d6afcba5195528326c391b487128 100644 (file)
@@ -153,6 +153,24 @@ export function render(_ctx) {
 }"
 `;
 
+exports[`compiler: v-if > template v-if (with v-for on same element) 1`] = `
+"import { txt as _txt, toDisplayString as _toDisplayString, setText as _setText, renderEffect as _renderEffect, createFor as _createFor, createIf as _createIf, template as _template } from 'vue';
+const t0 = _template("<div> ")
+
+export function render(_ctx) {
+  const n0 = _createIf(() => (_ctx.arr.length > 0), () => {
+    const n2 = _createFor(() => (_ctx.arr), (_for_item0, _for_key0) => {
+      const n4 = t0()
+      const x4 = _txt(n4)
+      _renderEffect(() => _setText(x4, "item: " + _toDisplayString(_for_item0.value)))
+      return n4
+    }, (item, index) => (index))
+    return n2
+  })
+  return n0
+}"
+`;
+
 exports[`compiler: v-if > template v-if + normal v-else 1`] = `
 "import { createIf as _createIf, template as _template } from 'vue';
 const t0 = _template("<div>hi")
index b5e8786fe65dc41f0000351d36e71f932eb992bb..3be2c301539a315e45f443cde48d62a2e61c0556 100644 (file)
@@ -172,6 +172,24 @@ describe('compiler: v-if', () => {
     expect([...ir.template.keys()]).toMatchObject(['<div>'])
   })
 
+  test('template v-if (with v-for on same element)', () => {
+    const { code, ir, helpers } = compileWithVIf(
+      `<template v-if="arr.length > 0" v-for="(item, index) in arr" :key="index">
+        <div>item: {{ item }}</div>
+      </template>`,
+    )
+
+    expect(code).toMatchSnapshot()
+    // should generate both createIf and createFor
+    expect(helpers).toContain('createIf')
+    expect(helpers).toContain('createFor')
+    // v-if should wrap v-for
+    const op = ir.block.dynamic.children[0].operation
+    expect(op).toMatchObject({
+      type: IRNodeTypes.IF,
+    })
+  })
+
   test('template v-if + normal v-else', () => {
     const { code, ir } = compileWithVIf(
       `<template v-if="foo"><div>hi</div><div>ho</div></template><div v-else/>`,
index f7d0594fe58151ac73eae39833d1978343d7116b..58e63fc9cf503402958cbc1984ef9da960328ee4 100644 (file)
@@ -33,8 +33,42 @@ export const newBlock = (node: BlockIRNode['node']): BlockIRNode => ({
 })
 
 export function wrapTemplate(node: ElementNode, dirs: string[]): TemplateNode {
+  // If the node is already a template, check if it has other structural directives
+  // that should be preserved (e.g., v-for when we're processing v-if)
   if (node.tagType === ElementTypes.TEMPLATE) {
-    return node
+    // Check if there are other structural directives that are NOT in the current dirs list
+    const otherStructuralDirs = ['if', 'else-if', 'else', 'for']
+    const hasOtherStructuralDir = node.props.some(
+      prop =>
+        prop.type === NodeTypes.DIRECTIVE &&
+        otherStructuralDirs.includes(prop.name) &&
+        !dirs.includes(prop.name),
+    )
+
+    // If no other structural directives, just return the node as is
+    if (!hasOtherStructuralDir) {
+      return node
+    }
+
+    // Otherwise, we need to wrap it: keep the current directive on the wrapper,
+    // and pass the rest (including the other structural directive) to the child
+    const reserved: Array<AttributeNode | DirectiveNode> = []
+    const pass: Array<AttributeNode | DirectiveNode> = []
+    node.props.forEach(prop => {
+      if (prop.type === NodeTypes.DIRECTIVE && dirs.includes(prop.name)) {
+        reserved.push(prop)
+      } else {
+        pass.push(prop)
+      }
+    })
+
+    return extend({}, node, {
+      type: NodeTypes.ELEMENT,
+      tag: 'template',
+      props: reserved,
+      tagType: ElementTypes.TEMPLATE,
+      children: [extend({}, node, { props: pass } as TemplateChildNode)],
+    } as Partial<TemplateNode>)
   }
 
   const reserved: Array<AttributeNode | DirectiveNode> = []