Skip to content

Commit f6ec77a

Browse files
committed
fix(compiler-core): fallthrough attributes with comment in root of child with certain structure (fix #13344)
1 parent 163b365 commit f6ec77a

File tree

4 files changed

+67
-15
lines changed

4 files changed

+67
-15
lines changed

packages/compiler-core/__tests__/transforms/__snapshots__/cacheStatic.spec.ts.snap

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -410,6 +410,32 @@ return function render(_ctx, _cache) {
410410
}"
411411
`;
412412

413+
exports[`compiler: cacheStatic transform > should hoist props for root with single element excluding comments 1`] = `
414+
"const _Vue = Vue
415+
const { createElementVNode: _createElementVNode, createCommentVNode: _createCommentVNode } = _Vue
416+
417+
const _hoisted_1 = { id: "a" }
418+
419+
return function render(_ctx, _cache) {
420+
with (_ctx) {
421+
const { createCommentVNode: _createCommentVNode, createElementVNode: _createElementVNode, Fragment: _Fragment, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
422+
423+
return (_openBlock(), _createElementBlock(_Fragment, null, [
424+
_createCommentVNode("comment"),
425+
_createElementVNode("div", _hoisted_1, _cache[0] || (_cache[0] = [
426+
_createElementVNode("div", { id: "b" }, [
427+
_createElementVNode("div", { id: "c" }, [
428+
_createElementVNode("div", { id: "d" }, [
429+
_createElementVNode("div", { id: "e" }, "hello")
430+
])
431+
])
432+
], -1 /* HOISTED */)
433+
]))
434+
], 2112 /* STABLE_FRAGMENT, DEV_ROOT_FRAGMENT */))
435+
}
436+
}"
437+
`;
438+
413439
exports[`compiler: cacheStatic transform > should hoist v-for children if static 1`] = `
414440
"const _Vue = Vue
415441
const { createElementVNode: _createElementVNode } = _Vue

packages/compiler-core/__tests__/transforms/cacheStatic.spec.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -543,6 +543,33 @@ describe('compiler: cacheStatic transform', () => {
543543
expect(generate(root).code).toMatchSnapshot()
544544
})
545545

546+
test('should hoist props for root with single element excluding comments', () => {
547+
// deeply nested div to trigger stringification condition
548+
const root = transformWithCache(
549+
`<!--comment--><div id="a"><div id="b"><div id="c"><div id="d"><div id="e">hello</div></div></div></div></div>`,
550+
)
551+
expect(root.cached.length).toBe(1)
552+
expect(root.hoists).toMatchObject([createObjectMatcher({ id: 'a' })])
553+
554+
expect((root.codegenNode as VNodeCall).children).toMatchObject([
555+
{
556+
type: NodeTypes.COMMENT,
557+
content: 'comment',
558+
},
559+
{
560+
type: NodeTypes.ELEMENT,
561+
codegenNode: {
562+
type: NodeTypes.VNODE_CALL,
563+
tag: `"div"`,
564+
props: { content: `_hoisted_1` },
565+
children: { type: NodeTypes.JS_CACHE_EXPRESSION },
566+
},
567+
},
568+
])
569+
console.log(generate(root).code)
570+
expect(generate(root).code).toMatchSnapshot()
571+
})
572+
546573
describe('prefixIdentifiers', () => {
547574
test('cache nested static tree with static interpolation', () => {
548575
const root = transformWithCache(

packages/compiler-core/src/transform.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ import {
3737
helperNameMap,
3838
} from './runtimeHelpers'
3939
import { isVSlot } from './utils'
40-
import { cacheStatic, isSingleElementRoot } from './transforms/cacheStatic'
40+
import { cacheStatic, getSingleElementRoot } from './transforms/cacheStatic'
4141
import type { CompilerCompatOptions } from './compat/compatConfig'
4242

4343
// There are two types of transforms:
@@ -356,12 +356,12 @@ function createRootCodegen(root: RootNode, context: TransformContext) {
356356
const { helper } = context
357357
const { children } = root
358358
if (children.length === 1) {
359-
const child = children[0]
359+
const singleElementRootChild = getSingleElementRoot(root)
360360
// if the single child is an element, turn it into a block.
361-
if (isSingleElementRoot(root, child) && child.codegenNode) {
361+
if (singleElementRootChild && singleElementRootChild.codegenNode) {
362362
// single element root is never hoisted so codegenNode will never be
363363
// SimpleExpressionNode
364-
const codegenNode = child.codegenNode
364+
const codegenNode = singleElementRootChild.codegenNode
365365
if (codegenNode.type === NodeTypes.VNODE_CALL) {
366366
convertToBlock(codegenNode, context)
367367
}
@@ -370,7 +370,7 @@ function createRootCodegen(root: RootNode, context: TransformContext) {
370370
// - single <slot/>, IfNode, ForNode: already blocks.
371371
// - single text node: always patched.
372372
// root codegen falls through via genNode()
373-
root.codegenNode = child
373+
root.codegenNode = children[0]
374374
}
375375
} else if (children.length > 1) {
376376
// root has multiple nodes - return a fragment block.

packages/compiler-core/src/transforms/cacheStatic.ts

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -41,20 +41,19 @@ export function cacheStatic(root: RootNode, context: TransformContext): void {
4141
context,
4242
// Root node is unfortunately non-hoistable due to potential parent
4343
// fallthrough attributes.
44-
isSingleElementRoot(root, root.children[0]),
44+
!!getSingleElementRoot(root),
4545
)
4646
}
4747

48-
export function isSingleElementRoot(
48+
export function getSingleElementRoot(
4949
root: RootNode,
50-
child: TemplateChildNode,
51-
): child is PlainElementNode | ComponentNode | TemplateNode {
52-
const { children } = root
53-
return (
54-
children.length === 1 &&
55-
child.type === NodeTypes.ELEMENT &&
56-
!isSlotOutlet(child)
57-
)
50+
): PlainElementNode | ComponentNode | TemplateNode | null {
51+
const children = root.children.filter(x => x.type !== NodeTypes.COMMENT)
52+
return children.length === 1 &&
53+
children[0].type === NodeTypes.ELEMENT &&
54+
!isSlotOutlet(children[0])
55+
? children[0]
56+
: null
5857
}
5958

6059
function walk(

0 commit comments

Comments
 (0)