fix(compiler-vapor): treat template v-for with single component child as component (#13914)
ci / test (push) Has been cancelled Details
ci / continuous-release (push) Has been cancelled Details
size data / upload (push) Has been cancelled Details

This commit is contained in:
edison 2025-09-24 16:59:31 +08:00 committed by GitHub
parent b65a6b869e
commit 3b5e13c7eb
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 71 additions and 2 deletions

View File

@ -235,6 +235,38 @@ export function render(_ctx) {
}"
`;
exports[`compiler: v-for > v-for on component 1`] = `
"import { resolveComponent as _resolveComponent, createComponentWithFallback as _createComponentWithFallback, child as _child, toDisplayString as _toDisplayString, setText as _setText, renderEffect as _renderEffect, createFor as _createFor, template as _template } from 'vue';
const t0 = _template(" ")
export function render(_ctx) {
const _component_Comp = _resolveComponent("Comp")
const n0 = _createFor(() => (_ctx.list), (_for_item0) => {
const n3 = _createComponentWithFallback(_component_Comp)
const n2 = _child(n3)
_renderEffect(() => _setText(n2, _toDisplayString(_for_item0.value)))
return [n2, n3]
}, undefined, 2)
return n0
}"
`;
exports[`compiler: v-for > v-for on template with single component child 1`] = `
"import { resolveComponent as _resolveComponent, createComponentWithFallback as _createComponentWithFallback, child as _child, toDisplayString as _toDisplayString, setText as _setText, renderEffect as _renderEffect, createFor as _createFor, template as _template } from 'vue';
const t0 = _template(" ")
export function render(_ctx) {
const _component_Comp = _resolveComponent("Comp")
const n0 = _createFor(() => (_ctx.list), (_for_item0) => {
const n3 = _createComponentWithFallback(_component_Comp)
const n2 = _child(n3)
_renderEffect(() => _setText(n2, _toDisplayString(_for_item0.value)))
return [n2, n3]
}, undefined, 2)
return n0
}"
`;
exports[`compiler: v-for > w/o value 1`] = `
"import { createFor as _createFor, template as _template } from 'vue';
const t0 = _template("<div>item</div>", true)

View File

@ -368,4 +368,24 @@ describe('compiler: v-for', () => {
index: undefined,
})
})
test('v-for on component', () => {
const { code, ir } = compileWithVFor(
`<Comp v-for="item in list">{{item}}</Comp>`,
)
expect(code).matchSnapshot()
expect(
(ir.block.dynamic.children[0].operation as ForIRNode).component,
).toBe(true)
})
test('v-for on template with single component child', () => {
const { code, ir } = compileWithVFor(
`<template v-for="item in list"><Comp>{{item}}</Comp></template>`,
)
expect(code).matchSnapshot()
expect(
(ir.block.dynamic.children[0].operation as ForIRNode).component,
).toBe(true)
})
})

View File

@ -2,6 +2,7 @@ import {
type ElementNode,
ElementTypes,
ErrorCodes,
NodeTypes,
type SimpleExpressionNode,
createCompilerError,
} from '@vue/compiler-dom'
@ -28,7 +29,7 @@ export function processFor(
node: ElementNode,
dir: VaporDirectiveNode,
context: TransformContext<ElementNode>,
) {
): (() => void) | undefined {
if (!dir.exp) {
context.options.onError(
createCompilerError(ErrorCodes.X_V_FOR_NO_EXPRESSION, dir.loc),
@ -47,7 +48,10 @@ export function processFor(
const keyProp = findProp(node, 'key')
const keyProperty = keyProp && propToExpression(keyProp)
const isComponent = node.tagType === ElementTypes.COMPONENT
const isComponent =
node.tagType === ElementTypes.COMPONENT ||
// template v-for with a single component child
isTemplateWithSingleComponent(node)
context.node = node = wrapTemplate(node, ['for'])
context.dynamic.flags |= DynamicFlag.NON_TEMPLATE | DynamicFlag.INSERT
const id = context.reference()
@ -87,3 +91,16 @@ export function processFor(
}
}
}
function isTemplateWithSingleComponent(node: ElementNode): boolean {
if (node.tag !== 'template') return false
const nonCommentChildren = node.children.filter(
c => c.type !== NodeTypes.COMMENT,
)
return (
nonCommentChildren.length === 1 &&
nonCommentChildren[0].type === NodeTypes.ELEMENT &&
nonCommentChildren[0].tagType === ElementTypes.COMPONENT
)
}