diff --git a/packages/compiler-core/src/utils.ts b/packages/compiler-core/src/utils.ts
index d4de245b052..4ec8a778864 100644
--- a/packages/compiler-core/src/utils.ts
+++ b/packages/compiler-core/src/utils.ts
@@ -12,6 +12,7 @@ import {
type MemoExpression,
NodeTypes,
type ObjectExpression,
+ type ParentNode,
type Position,
type Property,
type RenderSlotCall,
@@ -568,4 +569,38 @@ export function getMemoedVNodeCall(
}
}
+export function filterNonCommentChildren(
+ node: ParentNode,
+): TemplateChildNode[] {
+ return node.children.filter(n => n.type !== NodeTypes.COMMENT)
+}
+
+export function hasSingleChild(node: ParentNode): boolean {
+ return filterNonCommentChildren(node).length === 1
+}
+
+export function isSingleIfBlock(parent: ParentNode): boolean {
+ // detect cases where the parent v-if is not the only root level node
+ let hasEncounteredIf = false
+ for (const c of filterNonCommentChildren(parent)) {
+ if (
+ c.type === NodeTypes.IF ||
+ (c.type === NodeTypes.ELEMENT && findDir(c, 'if'))
+ ) {
+ // multiple root v-if
+ if (hasEncounteredIf) return false
+ hasEncounteredIf = true
+ } else if (
+ // node before v-if
+ !hasEncounteredIf ||
+ // non else nodes
+ !(c.type === NodeTypes.ELEMENT && findDir(c, /^else(-if)?$/, true))
+ ) {
+ return false
+ }
+ }
+
+ return true
+}
+
export const forAliasRE: RegExp = /([\s\S]*?)\s+(?:in|of)\s+(\S[\s\S]*)/
diff --git a/packages/compiler-ssr/src/transforms/ssrInjectFallthroughAttrs.ts b/packages/compiler-ssr/src/transforms/ssrInjectFallthroughAttrs.ts
index b1aac0d74c2..8622421242e 100644
--- a/packages/compiler-ssr/src/transforms/ssrInjectFallthroughAttrs.ts
+++ b/packages/compiler-ssr/src/transforms/ssrInjectFallthroughAttrs.ts
@@ -2,20 +2,16 @@ import {
ElementTypes,
type NodeTransform,
NodeTypes,
- type ParentNode,
type RootNode,
type TemplateChildNode,
createSimpleExpression,
+ filterNonCommentChildren,
findDir,
+ hasSingleChild,
+ isSingleIfBlock,
locStub,
} from '@vue/compiler-dom'
-const filterChild = (node: ParentNode) =>
- node.children.filter(n => n.type !== NodeTypes.COMMENT)
-
-const hasSingleChild = (node: ParentNode): boolean =>
- filterChild(node).length === 1
-
export const ssrInjectFallthroughAttrs: NodeTransform = (node, context) => {
// _attrs is provided as a function argument.
// mark it as a known identifier so that it doesn't get prefixed by
@@ -32,7 +28,7 @@ export const ssrInjectFallthroughAttrs: NodeTransform = (node, context) => {
node.tag === 'KeepAlive' ||
node.tag === 'keep-alive')
) {
- const rootChildren = filterChild(context.root)
+ const rootChildren = filterNonCommentChildren(context.root)
if (rootChildren.length === 1 && rootChildren[0] === node) {
if (hasSingleChild(node)) {
injectFallthroughAttrs(node.children[0])
@@ -47,26 +43,9 @@ export const ssrInjectFallthroughAttrs: NodeTransform = (node, context) => {
}
if (node.type === NodeTypes.IF_BRANCH && hasSingleChild(node)) {
- // detect cases where the parent v-if is not the only root level node
- let hasEncounteredIf = false
- for (const c of filterChild(parent)) {
- if (
- c.type === NodeTypes.IF ||
- (c.type === NodeTypes.ELEMENT && findDir(c, 'if'))
- ) {
- // multiple root v-if
- if (hasEncounteredIf) return
- hasEncounteredIf = true
- } else if (
- // node before v-if
- !hasEncounteredIf ||
- // non else nodes
- !(c.type === NodeTypes.ELEMENT && findDir(c, /else/, true))
- ) {
- return
- }
+ if (isSingleIfBlock(parent)) {
+ injectFallthroughAttrs(node.children[0])
}
- injectFallthroughAttrs(node.children[0])
} else if (hasSingleChild(parent)) {
injectFallthroughAttrs(node)
}
diff --git a/packages/compiler-vapor/__tests__/__snapshots__/compile.spec.ts.snap b/packages/compiler-vapor/__tests__/__snapshots__/compile.spec.ts.snap
index 20c4e446dbf..9527a9dbd69 100644
--- a/packages/compiler-vapor/__tests__/__snapshots__/compile.spec.ts.snap
+++ b/packages/compiler-vapor/__tests__/__snapshots__/compile.spec.ts.snap
@@ -284,7 +284,7 @@ exports[`compile > expression parsing > v-bind 1`] = `
const n0 = t0()
_renderEffect(() => {
const _key = key.value
- _setDynamicProps(n0, [{ [_key+1]: _unref(foo)[_key+1]() }], true)
+ _setDynamicProps(n0, [{ [_key+1]: _unref(foo)[_key+1]() }])
})
return n0
"
diff --git a/packages/compiler-vapor/__tests__/compile.spec.ts b/packages/compiler-vapor/__tests__/compile.spec.ts
index 32070300b9c..7514cde1b6d 100644
--- a/packages/compiler-vapor/__tests__/compile.spec.ts
+++ b/packages/compiler-vapor/__tests__/compile.spec.ts
@@ -196,7 +196,7 @@ describe('compile', () => {
expect(code).contains('const _key = key.value')
expect(code).contains('_key+1')
expect(code).contains(
- '_setDynamicProps(n0, [{ [_key+1]: _unref(foo)[_key+1]() }], true)',
+ '_setDynamicProps(n0, [{ [_key+1]: _unref(foo)[_key+1]() }])',
)
})
diff --git a/packages/compiler-vapor/__tests__/transforms/__snapshots__/transformElement.spec.ts.snap b/packages/compiler-vapor/__tests__/transforms/__snapshots__/transformElement.spec.ts.snap
index 1befe5482f8..69ce4b9b28a 100644
--- a/packages/compiler-vapor/__tests__/transforms/__snapshots__/transformElement.spec.ts.snap
+++ b/packages/compiler-vapor/__tests__/transforms/__snapshots__/transformElement.spec.ts.snap
@@ -483,7 +483,7 @@ const t0 = _template("
", true)
export function render(_ctx) {
const n0 = t0()
- _renderEffect(() => _setDynamicProps(n0, [_ctx.obj], true))
+ _renderEffect(() => _setDynamicProps(n0, [_ctx.obj]))
return n0
}"
`;
@@ -494,7 +494,7 @@ const t0 = _template("", true)
export function render(_ctx) {
const n0 = t0()
- _renderEffect(() => _setDynamicProps(n0, [{ id: "foo" }, _ctx.obj], true))
+ _renderEffect(() => _setDynamicProps(n0, [{ id: "foo" }, _ctx.obj]))
return n0
}"
`;
@@ -505,7 +505,7 @@ const t0 = _template("", true)
export function render(_ctx) {
const n0 = t0()
- _renderEffect(() => _setDynamicProps(n0, [_ctx.obj, { id: "foo" }], true))
+ _renderEffect(() => _setDynamicProps(n0, [_ctx.obj, { id: "foo" }]))
return n0
}"
`;
@@ -516,7 +516,7 @@ const t0 = _template("", true)
export function render(_ctx) {
const n0 = t0()
- _renderEffect(() => _setDynamicProps(n0, [{ id: "foo" }, _ctx.obj, { class: "bar" }], true))
+ _renderEffect(() => _setDynamicProps(n0, [{ id: "foo" }, _ctx.obj, { class: "bar" }]))
return n0
}"
`;
diff --git a/packages/compiler-vapor/__tests__/transforms/__snapshots__/transformTemplateRef.spec.ts.snap b/packages/compiler-vapor/__tests__/transforms/__snapshots__/transformTemplateRef.spec.ts.snap
index 851c529bd06..73420681511 100644
--- a/packages/compiler-vapor/__tests__/transforms/__snapshots__/transformTemplateRef.spec.ts.snap
+++ b/packages/compiler-vapor/__tests__/transforms/__snapshots__/transformTemplateRef.spec.ts.snap
@@ -35,7 +35,7 @@ export function render(_ctx) {
exports[`compiler: template ref transform > ref + v-for 1`] = `
"import { createTemplateRefSetter as _createTemplateRefSetter, createFor as _createFor, template as _template } from 'vue';
-const t0 = _template("", true)
+const t0 = _template("")
export function render(_ctx) {
const _setTemplateRef = _createTemplateRefSetter()
diff --git a/packages/compiler-vapor/__tests__/transforms/__snapshots__/vBind.spec.ts.snap b/packages/compiler-vapor/__tests__/transforms/__snapshots__/vBind.spec.ts.snap
index e2579c9ff7f..e19adce62af 100644
--- a/packages/compiler-vapor/__tests__/transforms/__snapshots__/vBind.spec.ts.snap
+++ b/packages/compiler-vapor/__tests__/transforms/__snapshots__/vBind.spec.ts.snap
@@ -37,7 +37,7 @@ export function render(_ctx) {
const n0 = t0()
_renderEffect(() => {
const _key = _ctx.key
- _setDynamicProps(n0, [{ [_key+1]: _ctx.foo[_key+1]() }], true)
+ _setDynamicProps(n0, [{ [_key+1]: _ctx.foo[_key+1]() }])
})
return n0
}"
@@ -109,7 +109,7 @@ const t0 = _template("", true)
export function render(_ctx) {
const n0 = t0()
- _renderEffect(() => _setDynamicProps(n0, [{ foo: bar => _ctx.foo = bar }], true))
+ _renderEffect(() => _setDynamicProps(n0, [{ foo: bar => _ctx.foo = bar }]))
return n0
}"
`;
@@ -335,7 +335,7 @@ const t0 = _template("", true)
export function render(_ctx) {
const n0 = t0()
- _renderEffect(() => _setDynamicProps(n0, [{ [_camelize(_ctx.foo)]: _ctx.id }], true))
+ _renderEffect(() => _setDynamicProps(n0, [{ [_camelize(_ctx.foo)]: _ctx.id }]))
return n0
}"
`;
@@ -434,7 +434,7 @@ const t0 = _template("", true)
export function render(_ctx) {
const n0 = t0()
- _renderEffect(() => _setDynamicProps(n0, [{ ["." + _ctx.fooBar]: _ctx.id }], true))
+ _renderEffect(() => _setDynamicProps(n0, [{ ["." + _ctx.fooBar]: _ctx.id }]))
return n0
}"
`;
@@ -609,7 +609,7 @@ export function render(_ctx) {
_renderEffect(() => {
const _id = _ctx.id
const _title = _ctx.title
- _setDynamicProps(n0, [{ [_id]: _id, [_title]: _title }], true)
+ _setDynamicProps(n0, [{ [_id]: _id, [_title]: _title }])
})
return n0
}"
@@ -623,7 +623,7 @@ export function render(_ctx) {
const n0 = t0()
_renderEffect(() => {
const _id = _ctx.id
- _setDynamicProps(n0, [{ [_id]: _id, foo: "bar", checked: "" }], true)
+ _setDynamicProps(n0, [{ [_id]: _id, foo: "bar", checked: "" }])
})
return n0
}"
@@ -677,7 +677,7 @@ const t0 = _template("", true, 1)
export function render(_ctx) {
const n0 = t0()
- _renderEffect(() => _setDynamicProps(n0, [_ctx.obj], true, true))
+ _renderEffect(() => _setDynamicProps(n0, [_ctx.obj], true))
return n0
}"
`;
diff --git a/packages/compiler-vapor/__tests__/transforms/__snapshots__/vFor.spec.ts.snap b/packages/compiler-vapor/__tests__/transforms/__snapshots__/vFor.spec.ts.snap
index 54f0e5a9b0c..29cacf2bff3 100644
--- a/packages/compiler-vapor/__tests__/transforms/__snapshots__/vFor.spec.ts.snap
+++ b/packages/compiler-vapor/__tests__/transforms/__snapshots__/vFor.spec.ts.snap
@@ -2,7 +2,7 @@
exports[`compiler: v-for > array de-structured value (with rest) 1`] = `
"import { txt as _txt, toDisplayString as _toDisplayString, setText as _setText, renderEffect as _renderEffect, createFor as _createFor, template as _template } from 'vue';
-const t0 = _template("
", true)
+const t0 = _template("
")
export function render(_ctx) {
const n0 = _createFor(() => (_ctx.list), (_for_item0, _for_key0) => {
@@ -17,7 +17,7 @@ export function render(_ctx) {
exports[`compiler: v-for > array de-structured value 1`] = `
"import { txt as _txt, toDisplayString as _toDisplayString, setText as _setText, renderEffect as _renderEffect, createFor as _createFor, template as _template } from 'vue';
-const t0 = _template("
", true)
+const t0 = _template("
")
export function render(_ctx) {
const n0 = _createFor(() => (_ctx.list), (_for_item0, _for_key0) => {
@@ -32,7 +32,7 @@ export function render(_ctx) {
exports[`compiler: v-for > basic v-for 1`] = `
"import { txt as _txt, createInvoker as _createInvoker, toDisplayString as _toDisplayString, setText as _setText, renderEffect as _renderEffect, createFor as _createFor, delegateEvents as _delegateEvents, template as _template } from 'vue';
-const t0 = _template("
", true)
+const t0 = _template("
")
_delegateEvents("click")
export function render(_ctx) {
@@ -49,7 +49,7 @@ export function render(_ctx) {
exports[`compiler: v-for > key only binding pattern 1`] = `
"import { txt as _txt, toDisplayString as _toDisplayString, setText as _setText, createFor as _createFor, template as _template } from 'vue';
-const t0 = _template("
", true)
+const t0 = _template("
")
export function render(_ctx) {
const n0 = _createFor(() => (_ctx.rows), (_for_item0) => {
@@ -64,7 +64,7 @@ export function render(_ctx) {
exports[`compiler: v-for > multi effect 1`] = `
"import { setProp as _setProp, renderEffect as _renderEffect, createFor as _createFor, template as _template } from 'vue';
-const t0 = _template("", true)
+const t0 = _template("")
export function render(_ctx) {
const n0 = _createFor(() => (_ctx.items), (_for_item0, _for_key0) => {
@@ -82,7 +82,7 @@ export function render(_ctx) {
exports[`compiler: v-for > nested v-for 1`] = `
"import { setInsertionState as _setInsertionState, txt as _txt, toDisplayString as _toDisplayString, setText as _setText, renderEffect as _renderEffect, createFor as _createFor, template as _template } from 'vue';
const t0 = _template(" ")
-const t1 = _template("", true)
+const t1 = _template("")
export function render(_ctx) {
const n0 = _createFor(() => (_ctx.list), (_for_item0) => {
@@ -102,7 +102,7 @@ export function render(_ctx) {
exports[`compiler: v-for > object de-structured value (with rest) 1`] = `
"import { getRestElement as _getRestElement, txt as _txt, toDisplayString as _toDisplayString, setText as _setText, renderEffect as _renderEffect, createFor as _createFor, template as _template } from 'vue';
-const t0 = _template("
", true)
+const t0 = _template("
")
export function render(_ctx) {
const n0 = _createFor(() => (_ctx.list), (_for_item0, _for_key0) => {
@@ -117,7 +117,7 @@ export function render(_ctx) {
exports[`compiler: v-for > object de-structured value 1`] = `
"import { txt as _txt, toDisplayString as _toDisplayString, setText as _setText, renderEffect as _renderEffect, createFor as _createFor, template as _template } from 'vue';
-const t0 = _template(" ", true)
+const t0 = _template(" ")
export function render(_ctx) {
const n0 = _createFor(() => (_ctx.items), (_for_item0) => {
@@ -132,7 +132,7 @@ export function render(_ctx) {
exports[`compiler: v-for > object value, key and index 1`] = `
"import { txt as _txt, toDisplayString as _toDisplayString, setText as _setText, renderEffect as _renderEffect, createFor as _createFor, template as _template } from 'vue';
-const t0 = _template("
", true)
+const t0 = _template("
")
export function render(_ctx) {
const n0 = _createFor(() => (_ctx.list), (_for_item0, _for_key0, _for_index0) => {
@@ -147,7 +147,7 @@ export function render(_ctx) {
exports[`compiler: v-for > selector pattern 1`] = `
"import { txt as _txt, toDisplayString as _toDisplayString, setText as _setText, createFor as _createFor, template as _template } from 'vue';
-const t0 = _template("
", true)
+const t0 = _template("
")
export function render(_ctx) {
let _selector0_0
@@ -167,7 +167,7 @@ export function render(_ctx) {
exports[`compiler: v-for > selector pattern 2`] = `
"import { setClass as _setClass, createFor as _createFor, template as _template } from 'vue';
-const t0 = _template("
", true)
+const t0 = _template("
")
export function render(_ctx) {
let _selector0_0
@@ -186,7 +186,7 @@ export function render(_ctx) {
exports[`compiler: v-for > selector pattern 3`] = `
"import { setClass as _setClass, renderEffect as _renderEffect, createFor as _createFor, template as _template } from 'vue';
-const t0 = _template("
", true)
+const t0 = _template("
")
export function render(_ctx) {
const n0 = _createFor(() => (_ctx.rows), (_for_item0) => {
@@ -203,7 +203,7 @@ export function render(_ctx) {
exports[`compiler: v-for > selector pattern 4`] = `
"import { setClass as _setClass, createFor as _createFor, template as _template } from 'vue';
-const t0 = _template("
", true)
+const t0 = _template("
")
export function render(_ctx) {
let _selector0_0
@@ -222,7 +222,7 @@ export function render(_ctx) {
exports[`compiler: v-for > v-for aliases w/ complex expressions 1`] = `
"import { getDefaultValue as _getDefaultValue, txt as _txt, toDisplayString as _toDisplayString, setText as _setText, renderEffect as _renderEffect, createFor as _createFor, template as _template } from 'vue';
-const t0 = _template("
", true)
+const t0 = _template("
")
export function render(_ctx) {
const n0 = _createFor(() => (_ctx.list), (_for_item0) => {
@@ -269,7 +269,7 @@ export function render(_ctx) {
exports[`compiler: v-for > w/o value 1`] = `
"import { createFor as _createFor, template as _template } from 'vue';
-const t0 = _template("item
", true)
+const t0 = _template("item
")
export function render(_ctx) {
const n0 = _createFor(() => (_ctx.items), (_for_item0) => {
diff --git a/packages/compiler-vapor/__tests__/transforms/__snapshots__/vIf.spec.ts.snap b/packages/compiler-vapor/__tests__/transforms/__snapshots__/vIf.spec.ts.snap
index 0d6e13301c8..1bb82cc4826 100644
--- a/packages/compiler-vapor/__tests__/transforms/__snapshots__/vIf.spec.ts.snap
+++ b/packages/compiler-vapor/__tests__/transforms/__snapshots__/vIf.spec.ts.snap
@@ -61,11 +61,109 @@ export function render(_ctx) {
}"
`;
+exports[`compiler: v-if > multiple v-if at root 1`] = `
+"import { createIf as _createIf, template as _template } from 'vue';
+const t0 = _template("foo
")
+const t1 = _template("bar
")
+const t2 = _template("baz
")
+
+export function render(_ctx) {
+ const n0 = _createIf(() => (_ctx.foo), () => {
+ const n2 = t0()
+ return n2
+ }, () => _createIf(() => (_ctx.bar), () => {
+ const n4 = t1()
+ return n4
+ }))
+ const n6 = _createIf(() => (_ctx.baz), () => {
+ const n8 = t2()
+ return n8
+ })
+ return [n0, n6]
+}"
+`;
+
+exports[`compiler: v-if > template v-if (multiple element) 1`] = `
+"import { createIf as _createIf, template as _template } from 'vue';
+const t0 = _template("hi
")
+const t1 = _template("ho
")
+
+export function render(_ctx) {
+ const n0 = _createIf(() => (_ctx.foo), () => {
+ const n2 = t0()
+ const n3 = t1()
+ return [n2, n3]
+ })
+ return n0
+}"
+`;
+
+exports[`compiler: v-if > template v-if (single element) 1`] = `
+"import { createIf as _createIf, template as _template } from 'vue';
+const t0 = _template("hi
", true)
+
+export function render(_ctx) {
+ const n0 = _createIf(() => (_ctx.foo), () => {
+ const n2 = t0()
+ return n2
+ })
+ return n0
+}"
+`;
+
+exports[`compiler: v-if > template v-if (text) 1`] = `
+"import { createIf as _createIf, template as _template } from 'vue';
+const t0 = _template("hello")
+
+export function render(_ctx) {
+ const n0 = _createIf(() => (_ctx.foo), () => {
+ const n2 = t0()
+ return n2
+ })
+ return n0
+}"
+`;
+
+exports[`compiler: v-if > template v-if (with v-for inside) 1`] = `
+"import { createFor as _createFor, createIf as _createIf, template as _template } from 'vue';
+const t0 = _template("")
+
+export function render(_ctx) {
+ const n0 = _createIf(() => (_ctx.foo), () => {
+ const n2 = _createFor(() => (_ctx.list), (_for_item0) => {
+ const n4 = t0()
+ return n4
+ })
+ 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("hi
")
+const t1 = _template("ho
")
+const t2 = _template("", true)
+
+export function render(_ctx) {
+ const n0 = _createIf(() => (_ctx.foo), () => {
+ const n2 = t0()
+ const n3 = t1()
+ return [n2, n3]
+ }, () => {
+ const n5 = t2()
+ return n5
+ })
+ return n0
+}"
+`;
+
exports[`compiler: v-if > template v-if 1`] = `
"import { txt as _txt, toDisplayString as _toDisplayString, setText as _setText, renderEffect as _renderEffect, createIf as _createIf, template as _template } from 'vue';
const t0 = _template("")
const t1 = _template("hello")
-const t2 = _template("
", true)
+const t2 = _template("
")
export function render(_ctx) {
const n0 = _createIf(() => (_ctx.ok), () => {
@@ -82,8 +180,8 @@ export function render(_ctx) {
exports[`compiler: v-if > v-if + v-else 1`] = `
"import { createIf as _createIf, template as _template } from 'vue';
-const t0 = _template("")
-const t1 = _template("")
+const t0 = _template("", true)
+const t1 = _template("", true)
export function render(_ctx) {
const n0 = _createIf(() => (_ctx.ok), () => {
@@ -99,9 +197,9 @@ export function render(_ctx) {
exports[`compiler: v-if > v-if + v-else-if + v-else 1`] = `
"import { createIf as _createIf, template as _template } from 'vue';
-const t0 = _template("")
-const t1 = _template("")
-const t2 = _template("fine")
+const t0 = _template("", true)
+const t1 = _template("", true)
+const t2 = _template("fine", true)
export function render(_ctx) {
const n0 = _createIf(() => (_ctx.ok), () => {
@@ -123,8 +221,8 @@ export function render(_ctx) {
exports[`compiler: v-if > v-if + v-else-if 1`] = `
"import { createIf as _createIf, template as _template } from 'vue';
-const t0 = _template("")
-const t1 = _template("")
+const t0 = _template("", true)
+const t1 = _template("", true)
export function render(_ctx) {
const n0 = _createIf(() => (_ctx.ok), () => {
@@ -163,3 +261,22 @@ export function render(_ctx) {
return n8
}"
`;
+
+exports[`compiler: v-if > v-if and extra at root 1`] = `
+"import { createIf as _createIf, template as _template } from 'vue';
+const t0 = _template("foo
")
+const t1 = _template("bar
")
+const t2 = _template("baz
")
+
+export function render(_ctx) {
+ const n0 = _createIf(() => (_ctx.foo), () => {
+ const n2 = t0()
+ return n2
+ }, () => _createIf(() => (_ctx.bar), () => {
+ const n4 = t1()
+ return n4
+ }))
+ const n6 = t2()
+ return [n0, n6]
+}"
+`;
diff --git a/packages/compiler-vapor/__tests__/transforms/__snapshots__/vModel.spec.ts.snap b/packages/compiler-vapor/__tests__/transforms/__snapshots__/vModel.spec.ts.snap
index 4f177e27e40..4d6b9d5b406 100644
--- a/packages/compiler-vapor/__tests__/transforms/__snapshots__/vModel.spec.ts.snap
+++ b/packages/compiler-vapor/__tests__/transforms/__snapshots__/vModel.spec.ts.snap
@@ -237,7 +237,7 @@ const t0 = _template("", true)
export function render(_ctx) {
const n0 = t0()
_applyDynamicModel(n0, () => (_ctx.model), _value => (_ctx.model = _value))
- _renderEffect(() => _setDynamicProps(n0, [_ctx.obj], true))
+ _renderEffect(() => _setDynamicProps(n0, [_ctx.obj]))
return n0
}"
`;
diff --git a/packages/compiler-vapor/__tests__/transforms/__snapshots__/vOnce.spec.ts.snap b/packages/compiler-vapor/__tests__/transforms/__snapshots__/vOnce.spec.ts.snap
index ae5e9df743a..7f76c3cd7c1 100644
--- a/packages/compiler-vapor/__tests__/transforms/__snapshots__/vOnce.spec.ts.snap
+++ b/packages/compiler-vapor/__tests__/transforms/__snapshots__/vOnce.spec.ts.snap
@@ -62,7 +62,7 @@ export function render(_ctx) {
exports[`compiler: v-once > with v-for 1`] = `
"import { createFor as _createFor, template as _template } from 'vue';
-const t0 = _template("", true)
+const t0 = _template("")
export function render(_ctx) {
const n0 = _createFor(() => (_ctx.list), (_for_item0) => {
@@ -88,8 +88,8 @@ export function render(_ctx) {
exports[`compiler: v-once > with v-if/else 1`] = `
"import { createIf as _createIf, template as _template } from 'vue';
-const t0 = _template("")
-const t1 = _template("")
+const t0 = _template("", true)
+const t1 = _template("", true)
export function render(_ctx) {
const n0 = _createIf(() => (_ctx.expr), () => {
diff --git a/packages/compiler-vapor/__tests__/transforms/transformElement.spec.ts b/packages/compiler-vapor/__tests__/transforms/transformElement.spec.ts
index 3847567cf37..ffedee627fc 100644
--- a/packages/compiler-vapor/__tests__/transforms/transformElement.spec.ts
+++ b/packages/compiler-vapor/__tests__/transforms/transformElement.spec.ts
@@ -634,7 +634,7 @@ describe('compiler: element transform', () => {
],
},
])
- expect(code).contains('_setDynamicProps(n0, [_ctx.obj], true)')
+ expect(code).contains('_setDynamicProps(n0, [_ctx.obj])')
})
test('v-bind="obj" after static prop', () => {
@@ -670,9 +670,7 @@ describe('compiler: element transform', () => {
],
},
])
- expect(code).contains(
- '_setDynamicProps(n0, [{ id: "foo" }, _ctx.obj], true)',
- )
+ expect(code).contains('_setDynamicProps(n0, [{ id: "foo" }, _ctx.obj])')
})
test('v-bind="obj" before static prop', () => {
@@ -698,9 +696,7 @@ describe('compiler: element transform', () => {
],
},
])
- expect(code).contains(
- '_setDynamicProps(n0, [_ctx.obj, { id: "foo" }], true)',
- )
+ expect(code).contains('_setDynamicProps(n0, [_ctx.obj, { id: "foo" }])')
})
test('v-bind="obj" between static props', () => {
@@ -728,7 +724,7 @@ describe('compiler: element transform', () => {
},
])
expect(code).contains(
- '_setDynamicProps(n0, [{ id: "foo" }, _ctx.obj, { class: "bar" }], true)',
+ '_setDynamicProps(n0, [{ id: "foo" }, _ctx.obj, { class: "bar" }])',
)
})
diff --git a/packages/compiler-vapor/__tests__/transforms/vBind.spec.ts b/packages/compiler-vapor/__tests__/transforms/vBind.spec.ts
index 63744c2ad78..0d60c02fd54 100644
--- a/packages/compiler-vapor/__tests__/transforms/vBind.spec.ts
+++ b/packages/compiler-vapor/__tests__/transforms/vBind.spec.ts
@@ -171,7 +171,7 @@ describe('compiler v-bind', () => {
],
})
expect(code).contains(
- '_setDynamicProps(n0, [{ [_id]: _id, [_title]: _title }], true)',
+ '_setDynamicProps(n0, [{ [_id]: _id, [_title]: _title }])',
)
})
@@ -224,7 +224,7 @@ describe('compiler v-bind', () => {
],
})
expect(code).contains(
- '_setDynamicProps(n0, [{ [_id]: _id, foo: "bar", checked: "" }], true)',
+ '_setDynamicProps(n0, [{ [_id]: _id, foo: "bar", checked: "" }])',
)
})
@@ -341,7 +341,7 @@ describe('compiler v-bind', () => {
expect(code).matchSnapshot()
expect(code).contains('renderEffect')
expect(code).contains(
- `_setDynamicProps(n0, [{ [_camelize(_ctx.foo)]: _ctx.id }], true)`,
+ `_setDynamicProps(n0, [{ [_camelize(_ctx.foo)]: _ctx.id }])`,
)
})
@@ -422,7 +422,7 @@ describe('compiler v-bind', () => {
})
expect(code).contains('renderEffect')
expect(code).contains(
- `_setDynamicProps(n0, [{ ["." + _ctx.fooBar]: _ctx.id }], true)`,
+ `_setDynamicProps(n0, [{ ["." + _ctx.fooBar]: _ctx.id }])`,
)
})
@@ -669,7 +669,7 @@ describe('compiler v-bind', () => {
`)
expect(code).matchSnapshot()
- expect(code).contains('_setDynamicProps(n0, [_ctx.obj], true, true))')
+ expect(code).contains('_setDynamicProps(n0, [_ctx.obj], true))')
})
test('number value', () => {
diff --git a/packages/compiler-vapor/__tests__/transforms/vIf.spec.ts b/packages/compiler-vapor/__tests__/transforms/vIf.spec.ts
index cfac122885e..88fb6d39c92 100644
--- a/packages/compiler-vapor/__tests__/transforms/vIf.spec.ts
+++ b/packages/compiler-vapor/__tests__/transforms/vIf.spec.ts
@@ -6,6 +6,7 @@ import {
transformComment,
transformElement,
transformText,
+ transformVFor,
transformVIf,
transformVOnce,
transformVText,
@@ -16,6 +17,7 @@ const compileWithVIf = makeCompile({
nodeTransforms: [
transformVOnce,
transformVIf,
+ transformVFor,
transformText,
transformElement,
transformComment,
@@ -62,6 +64,38 @@ describe('compiler: v-if', () => {
expect(code).matchSnapshot()
})
+ test('multiple v-if at root', () => {
+ const { code, ir } = compileWithVIf(
+ `foo
bar
baz
`,
+ )
+
+ expect(code).toMatchSnapshot()
+ expect(code).contains(`_template("foo
")`)
+ expect(code).contains(`_template("bar
")`)
+ expect(code).contains(`_template("baz
")`)
+ expect([...ir.template.keys()]).toMatchObject([
+ 'foo
',
+ 'bar
',
+ 'baz
',
+ ])
+ })
+
+ test('v-if and extra at root', () => {
+ const { code, ir } = compileWithVIf(
+ `foo
bar
baz
`,
+ )
+
+ expect(code).toMatchSnapshot()
+ expect(code).contains(`_template("foo
")`)
+ expect(code).contains(`_template("bar
")`)
+ expect(code).contains(`_template("baz
")`)
+ expect([...ir.template.keys()]).toMatchObject([
+ 'foo
',
+ 'bar
',
+ 'baz
',
+ ])
+ })
+
test('template v-if', () => {
const { code, ir } = compileWithVIf(
`hello`,
@@ -102,6 +136,65 @@ describe('compiler: v-if', () => {
})
})
+ test('template v-if (text)', () => {
+ const { code, ir } = compileWithVIf(`hello`)
+
+ expect(code).toMatchSnapshot()
+ expect(code).toContain('_template("hello")')
+ expect([...ir.template.keys()]).toMatchObject(['hello'])
+ })
+
+ test('template v-if (single element)', () => {
+ // single element should not wrap with fragment
+ const { code, ir } = compileWithVIf(
+ `hi
`,
+ )
+
+ expect(code).toMatchSnapshot()
+ expect(code).toContain('_template("hi
", true)')
+ expect([...ir.template.keys()]).toMatchObject(['hi
'])
+ })
+
+ test('template v-if (multiple element)', () => {
+ const { code, ir } = compileWithVIf(
+ `hi
ho
`,
+ )
+
+ expect(code).toMatchSnapshot()
+ expect(code).toContain('_template("hi
")')
+ expect(code).toContain('_template("ho
")')
+ expect([...ir.template.keys()]).toMatchObject([
+ 'hi
',
+ 'ho
',
+ ])
+ })
+
+ test('template v-if (with v-for inside)', () => {
+ const { code, ir } = compileWithVIf(
+ ``,
+ )
+
+ expect(code).toMatchSnapshot()
+ expect(code).toContain('_template("")')
+ expect([...ir.template.keys()]).toMatchObject([''])
+ })
+
+ test('template v-if + normal v-else', () => {
+ const { code, ir } = compileWithVIf(
+ `hi
ho
`,
+ )
+
+ expect(code).toMatchSnapshot()
+ expect(code).toContain('_template("hi
")')
+ expect(code).toContain('_template("ho
")')
+ expect(code).toContain('_template("", true)')
+ expect([...ir.template.keys()]).toMatchObject([
+ 'hi
',
+ 'ho
',
+ '',
+ ])
+ })
+
test('dedupe same template', () => {
const { code, ir } = compileWithVIf(
`hello
hello
`,
diff --git a/packages/compiler-vapor/src/generate.ts b/packages/compiler-vapor/src/generate.ts
index 63b010be974..8f6d03b9acf 100644
--- a/packages/compiler-vapor/src/generate.ts
+++ b/packages/compiler-vapor/src/generate.ts
@@ -209,7 +209,7 @@ export function generate(
}
const delegates = genDelegates(context)
- const templates = genTemplates(ir.template, ir.rootTemplateIndex, context)
+ const templates = genTemplates(ir.template, ir.rootTemplateIndexes, context)
const imports = genHelperImports(context) + genAssetImports(context)
const preamble = imports + templates + delegates
diff --git a/packages/compiler-vapor/src/generators/prop.ts b/packages/compiler-vapor/src/generators/prop.ts
index 6b679da99f2..63c6471f1f4 100644
--- a/packages/compiler-vapor/src/generators/prop.ts
+++ b/packages/compiler-vapor/src/generators/prop.ts
@@ -92,7 +92,6 @@ export function genDynamicProps(
helper('setDynamicProps'),
`n${oper.element}`,
genMulti(DELIMITERS_ARRAY, ...values),
- oper.root && 'true',
isSVG && 'true',
),
]
diff --git a/packages/compiler-vapor/src/generators/template.ts b/packages/compiler-vapor/src/generators/template.ts
index d6f16f7014f..08c589a3e11 100644
--- a/packages/compiler-vapor/src/generators/template.ts
+++ b/packages/compiler-vapor/src/generators/template.ts
@@ -16,7 +16,7 @@ import {
export function genTemplates(
templates: Map,
- rootIndex: number | undefined,
+ rootIndexes: Set,
context: CodegenContext,
): string {
const result: string[] = []
@@ -29,7 +29,7 @@ export function genTemplates(
// replace import expressions with string concatenation
IMPORT_EXPR_RE,
`" + $1 + "`,
- )}${i === rootIndex ? ', true' : ns ? ', false' : ''}${ns ? `, ${ns}` : ''})\n`,
+ )}${rootIndexes.has(i) ? ', true' : ns ? ', false' : ''}${ns ? `, ${ns}` : ''})\n`,
)
i++
})
diff --git a/packages/compiler-vapor/src/ir/index.ts b/packages/compiler-vapor/src/ir/index.ts
index 9a4415e4103..13fd5e1e696 100644
--- a/packages/compiler-vapor/src/ir/index.ts
+++ b/packages/compiler-vapor/src/ir/index.ts
@@ -63,7 +63,7 @@ export interface RootIRNode {
source: string
template: Map
templateIndexMap: Map
- rootTemplateIndex?: number
+ rootTemplateIndexes: Set
component: Set
directive: Set
block: BlockIRNode
@@ -108,7 +108,6 @@ export interface SetPropIRNode extends BaseIRNode {
type: IRNodeTypes.SET_PROP
element: number
prop: IRProp
- root: boolean
tag: string
}
@@ -116,7 +115,6 @@ export interface SetDynamicPropsIRNode extends BaseIRNode {
type: IRNodeTypes.SET_DYNAMIC_PROPS
element: number
props: IRProps[]
- root: boolean
tag: string
}
diff --git a/packages/compiler-vapor/src/transform.ts b/packages/compiler-vapor/src/transform.ts
index 3d311e5cd8a..be0efa65471 100644
--- a/packages/compiler-vapor/src/transform.ts
+++ b/packages/compiler-vapor/src/transform.ts
@@ -251,6 +251,7 @@ export function transform(
source: node.source,
template: new Map(),
templateIndexMap: new Map(),
+ rootTemplateIndexes: new Set(),
component: new Set(),
directive: new Set(),
block: newBlock(node),
diff --git a/packages/compiler-vapor/src/transforms/transformElement.ts b/packages/compiler-vapor/src/transforms/transformElement.ts
index db3943f2ad8..587bcf997ca 100644
--- a/packages/compiler-vapor/src/transforms/transformElement.ts
+++ b/packages/compiler-vapor/src/transforms/transformElement.ts
@@ -6,9 +6,13 @@ import {
ErrorCodes,
NodeTypes,
type PlainElementNode,
+ type RootNode,
type SimpleExpressionNode,
+ type TemplateChildNode,
createCompilerError,
createSimpleExpression,
+ hasSingleChild,
+ isSingleIfBlock,
isStaticArgOf,
isValidHTMLNesting,
} from '@vue/compiler-dom'
@@ -73,21 +77,7 @@ export const transformElement: NodeTransform = (node, context) => {
getEffectIndex,
)
- let { parent } = context
- while (
- parent &&
- parent.parent &&
- parent.node.type === NodeTypes.ELEMENT &&
- parent.node.tagType === ElementTypes.TEMPLATE
- ) {
- parent = parent.parent
- }
- const singleRoot =
- (context.root === parent &&
- parent.node.children.filter(child => child.type !== NodeTypes.COMMENT)
- .length === 1) ||
- isCustomElement
-
+ const singleRoot = isSingleRoot(context)
if (isComponent) {
transformComponentElement(
node as ComponentNode,
@@ -109,6 +99,35 @@ export const transformElement: NodeTransform = (node, context) => {
}
}
+function isSingleRoot(
+ context: TransformContext,
+): boolean {
+ if (context.inVFor) {
+ return false
+ }
+
+ let { parent } = context
+ if (
+ parent &&
+ !(hasSingleChild(parent.node) || isSingleIfBlock(parent.node))
+ ) {
+ return false
+ }
+ while (
+ parent &&
+ parent.parent &&
+ parent.node.type === NodeTypes.ELEMENT &&
+ parent.node.tagType === ElementTypes.TEMPLATE
+ ) {
+ parent = parent.parent
+ if (!(hasSingleChild(parent.node) || isSingleIfBlock(parent.node))) {
+ return false
+ }
+ }
+
+ return context.root === parent
+}
+
function transformComponentElement(
node: ComponentNode,
propsResult: PropsResult,
@@ -165,7 +184,7 @@ function transformComponentElement(
tag,
props: propsResult[0] ? propsResult[1] : [propsResult[1]],
asset,
- root: singleRoot && !context.inVFor,
+ root: singleRoot,
slots: [...context.slots],
once: context.inVOnce,
dynamic: dynamicComponent,
@@ -235,7 +254,6 @@ function transformNativeElement(
type: IRNodeTypes.SET_DYNAMIC_PROPS,
element: context.reference(),
props: dynamicArgs,
- root: singleRoot,
tag,
},
getEffectIndex,
@@ -269,7 +287,6 @@ function transformNativeElement(
type: IRNodeTypes.SET_PROP,
element: context.reference(),
prop,
- root: singleRoot,
tag,
},
getEffectIndex,
@@ -285,7 +302,7 @@ function transformNativeElement(
}
if (singleRoot) {
- context.ir.rootTemplateIndex = context.ir.template.size
+ context.ir.rootTemplateIndexes.add(context.ir.template.size)
}
if (
diff --git a/packages/runtime-vapor/__tests__/componentAttrs.spec.ts b/packages/runtime-vapor/__tests__/componentAttrs.spec.ts
index 7c86c295fbd..7fd99b88fad 100644
--- a/packages/runtime-vapor/__tests__/componentAttrs.spec.ts
+++ b/packages/runtime-vapor/__tests__/componentAttrs.spec.ts
@@ -2,6 +2,7 @@ import { type Ref, nextTick, ref } from '@vue/runtime-dom'
import {
createComponent,
createDynamicComponent,
+ createIf,
createSlot,
defineVaporComponent,
renderEffect,
@@ -58,6 +59,139 @@ describe('attribute fallthrough', () => {
expect(host.innerHTML).toBe('2
')
})
+ it('should allow attrs to fallthrough on component with comment at root', async () => {
+ const t0 = template('')
+ const t1 = template('')
+ const { component: Child } = define({
+ props: ['foo'],
+ setup(props: any) {
+ const n0 = t0()
+ const n1 = t1()
+ renderEffect(() => setElementText(n1, props.foo))
+ return [n0, n1]
+ },
+ })
+
+ const foo = ref(1)
+ const id = ref('a')
+ const { host } = define({
+ setup() {
+ return createComponent(
+ Child,
+ {
+ foo: () => foo.value,
+ id: () => id.value,
+ },
+ null,
+ true,
+ )
+ },
+ }).render()
+ expect(host.innerHTML).toBe('
1
')
+
+ foo.value++
+ await nextTick()
+ expect(host.innerHTML).toBe('
2
')
+
+ id.value = 'b'
+ await nextTick()
+ expect(host.innerHTML).toBe('
2
')
+ })
+
+ it('if block', async () => {
+ const t0 = template('
foo
', true)
+ const t1 = template('
bar
', true)
+ const t2 = template('
baz
', true)
+ const { component: Child } = define({
+ setup() {
+ const n0 = createIf(
+ () => true,
+ () => {
+ const n2 = t0()
+ return n2
+ },
+ () =>
+ createIf(
+ () => false,
+ () => {
+ const n4 = t1()
+ return n4
+ },
+ () => {
+ const n7 = t2()
+ return n7
+ },
+ ),
+ )
+ return n0
+ },
+ })
+
+ const id = ref('a')
+ const { host } = define({
+ setup() {
+ return createComponent(
+ Child,
+ {
+ id: () => id.value,
+ },
+ null,
+ true,
+ )
+ },
+ }).render()
+ expect(host.innerHTML).toBe('
foo
')
+ })
+
+ it('should not allow attrs to fallthrough on component with multiple roots', async () => {
+ const t0 = template('
')
+ const t1 = template('')
+ const { component: Child } = define({
+ props: ['foo'],
+ setup(props: any) {
+ const n0 = t0()
+ const n1 = t1()
+ renderEffect(() => setElementText(n1, props.foo))
+ return [n0, n1]
+ },
+ })
+
+ const foo = ref(1)
+ const id = ref('a')
+ const { host } = define({
+ setup() {
+ return createComponent(
+ Child,
+ {
+ foo: () => foo.value,
+ id: () => id.value,
+ },
+ null,
+ true,
+ )
+ },
+ }).render()
+ expect(host.innerHTML).toBe('
1
')
+ })
+
+ it('should not allow attrs to fallthrough on component with single comment root', async () => {
+ const t0 = template('')
+ const { component: Child } = define({
+ setup() {
+ const n0 = t0()
+ return [n0]
+ },
+ })
+
+ const id = ref('a')
+ const { host } = define({
+ setup() {
+ return createComponent(Child, { id: () => id.value }, null, true)
+ },
+ }).render()
+ expect(host.innerHTML).toBe('')
+ })
+
it('should not fallthrough if explicitly pass inheritAttrs: false', async () => {
const t0 = template('
', true)
const { component: Child } = define({
diff --git a/packages/runtime-vapor/src/component.ts b/packages/runtime-vapor/src/component.ts
index 72a068508d5..cae2bd7860d 100644
--- a/packages/runtime-vapor/src/component.ts
+++ b/packages/runtime-vapor/src/component.ts
@@ -48,6 +48,7 @@ import {
EMPTY_OBJ,
ShapeFlags,
invokeArrayFns,
+ isArray,
isFunction,
isString,
} from '@vue/shared'
@@ -832,4 +833,25 @@ function getRootElement(block: Block): Element | undefined {
return nodes
}
}
+
+ // The root node contains comments. It is necessary to filter out
+ // the comment nodes and return a single root node.
+ // align with vdom behavior
+ if (isArray(block)) {
+ let singleRoot: Element | undefined
+ let hasComment = false
+ for (const b of block) {
+ if (b instanceof Comment) {
+ hasComment = true
+ continue
+ }
+ const thisRoot = getRootElement(b)
+ // only return root if there is exactly one eligible root in the array
+ if (!thisRoot || singleRoot) {
+ return
+ }
+ singleRoot = thisRoot
+ }
+ return hasComment ? singleRoot : undefined
+ }
}
diff --git a/packages/runtime-vapor/src/dom/prop.ts b/packages/runtime-vapor/src/dom/prop.ts
index c7f9631509f..9167d2b9b3b 100644
--- a/packages/runtime-vapor/src/dom/prop.ts
+++ b/packages/runtime-vapor/src/dom/prop.ts
@@ -430,16 +430,10 @@ function setHtmlToBlock(block: Block, value: any): void {
}
}
-export function setDynamicProps(
- el: any,
- args: any[],
- root?: boolean,
- isSVG?: boolean,
-): void {
+export function setDynamicProps(el: any, args: any[], isSVG?: boolean): void {
const props = args.length > 1 ? mergeProps(...args) : args[0]
const cacheKey = `$dprops${isApplyingFallthroughProps ? '$' : ''}`
const prevKeys = el[cacheKey] as string[]
- if (root) el.$root = root
if (prevKeys) {
for (const key of prevKeys) {