diff --git a/packages/compiler-core/__tests__/transforms/__snapshots__/cacheStatic.spec.ts.snap b/packages/compiler-core/__tests__/transforms/__snapshots__/cacheStatic.spec.ts.snap
index 375a0c8674a..80b1a7064fb 100644
--- a/packages/compiler-core/__tests__/transforms/__snapshots__/cacheStatic.spec.ts.snap
+++ b/packages/compiler-core/__tests__/transforms/__snapshots__/cacheStatic.spec.ts.snap
@@ -410,6 +410,32 @@ return function render(_ctx, _cache) {
}"
`;
+exports[`compiler: cacheStatic transform > should hoist props for root with single element excluding comments 1`] = `
+"const _Vue = Vue
+const { createElementVNode: _createElementVNode, createCommentVNode: _createCommentVNode } = _Vue
+
+const _hoisted_1 = { id: "a" }
+
+return function render(_ctx, _cache) {
+ with (_ctx) {
+ const { createCommentVNode: _createCommentVNode, createElementVNode: _createElementVNode, Fragment: _Fragment, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
+
+ return (_openBlock(), _createElementBlock(_Fragment, null, [
+ _createCommentVNode("comment"),
+ _createElementVNode("div", _hoisted_1, _cache[0] || (_cache[0] = [
+ _createElementVNode("div", { id: "b" }, [
+ _createElementVNode("div", { id: "c" }, [
+ _createElementVNode("div", { id: "d" }, [
+ _createElementVNode("div", { id: "e" }, "hello")
+ ])
+ ])
+ ], -1 /* HOISTED */)
+ ]))
+ ], 2112 /* STABLE_FRAGMENT, DEV_ROOT_FRAGMENT */))
+ }
+}"
+`;
+
exports[`compiler: cacheStatic transform > should hoist v-for children if static 1`] = `
"const _Vue = Vue
const { createElementVNode: _createElementVNode } = _Vue
diff --git a/packages/compiler-core/__tests__/transforms/cacheStatic.spec.ts b/packages/compiler-core/__tests__/transforms/cacheStatic.spec.ts
index 358c0e31c3d..f8965cdd697 100644
--- a/packages/compiler-core/__tests__/transforms/cacheStatic.spec.ts
+++ b/packages/compiler-core/__tests__/transforms/cacheStatic.spec.ts
@@ -543,6 +543,33 @@ describe('compiler: cacheStatic transform', () => {
expect(generate(root).code).toMatchSnapshot()
})
+ test('should hoist props for root with single element excluding comments', () => {
+ // deeply nested div to trigger stringification condition
+ const root = transformWithCache(
+ `
`,
+ )
+ expect(root.cached.length).toBe(1)
+ expect(root.hoists).toMatchObject([createObjectMatcher({ id: 'a' })])
+
+ expect((root.codegenNode as VNodeCall).children).toMatchObject([
+ {
+ type: NodeTypes.COMMENT,
+ content: 'comment',
+ },
+ {
+ type: NodeTypes.ELEMENT,
+ codegenNode: {
+ type: NodeTypes.VNODE_CALL,
+ tag: `"div"`,
+ props: { content: `_hoisted_1` },
+ children: { type: NodeTypes.JS_CACHE_EXPRESSION },
+ },
+ },
+ ])
+ console.log(generate(root).code)
+ expect(generate(root).code).toMatchSnapshot()
+ })
+
describe('prefixIdentifiers', () => {
test('cache nested static tree with static interpolation', () => {
const root = transformWithCache(
diff --git a/packages/compiler-core/src/transform.ts b/packages/compiler-core/src/transform.ts
index aeb96cc2b4a..9d8fd842935 100644
--- a/packages/compiler-core/src/transform.ts
+++ b/packages/compiler-core/src/transform.ts
@@ -37,7 +37,7 @@ import {
helperNameMap,
} from './runtimeHelpers'
import { isVSlot } from './utils'
-import { cacheStatic, isSingleElementRoot } from './transforms/cacheStatic'
+import { cacheStatic, getSingleElementRoot } from './transforms/cacheStatic'
import type { CompilerCompatOptions } from './compat/compatConfig'
// There are two types of transforms:
@@ -356,12 +356,12 @@ function createRootCodegen(root: RootNode, context: TransformContext) {
const { helper } = context
const { children } = root
if (children.length === 1) {
- const child = children[0]
+ const singleElementRootChild = getSingleElementRoot(root)
// if the single child is an element, turn it into a block.
- if (isSingleElementRoot(root, child) && child.codegenNode) {
+ if (singleElementRootChild && singleElementRootChild.codegenNode) {
// single element root is never hoisted so codegenNode will never be
// SimpleExpressionNode
- const codegenNode = child.codegenNode
+ const codegenNode = singleElementRootChild.codegenNode
if (codegenNode.type === NodeTypes.VNODE_CALL) {
convertToBlock(codegenNode, context)
}
@@ -370,7 +370,7 @@ function createRootCodegen(root: RootNode, context: TransformContext) {
// - single , IfNode, ForNode: already blocks.
// - single text node: always patched.
// root codegen falls through via genNode()
- root.codegenNode = child
+ root.codegenNode = children[0]
}
} else if (children.length > 1) {
// root has multiple nodes - return a fragment block.
diff --git a/packages/compiler-core/src/transforms/cacheStatic.ts b/packages/compiler-core/src/transforms/cacheStatic.ts
index e5d67380640..239ee689a9f 100644
--- a/packages/compiler-core/src/transforms/cacheStatic.ts
+++ b/packages/compiler-core/src/transforms/cacheStatic.ts
@@ -41,20 +41,19 @@ export function cacheStatic(root: RootNode, context: TransformContext): void {
context,
// Root node is unfortunately non-hoistable due to potential parent
// fallthrough attributes.
- isSingleElementRoot(root, root.children[0]),
+ !!getSingleElementRoot(root),
)
}
-export function isSingleElementRoot(
+export function getSingleElementRoot(
root: RootNode,
- child: TemplateChildNode,
-): child is PlainElementNode | ComponentNode | TemplateNode {
- const { children } = root
- return (
- children.length === 1 &&
- child.type === NodeTypes.ELEMENT &&
- !isSlotOutlet(child)
- )
+): PlainElementNode | ComponentNode | TemplateNode | null {
+ const children = root.children.filter(x => x.type !== NodeTypes.COMMENT)
+ return children.length === 1 &&
+ children[0].type === NodeTypes.ELEMENT &&
+ !isSlotOutlet(children[0])
+ ? children[0]
+ : null
}
function walk(