diff --git a/packages/compiler-core/__tests__/transforms/__snapshots__/hoistStatic.spec.ts.snap b/packages/compiler-core/__tests__/transforms/__snapshots__/hoistStatic.spec.ts.snap
index 6926708345e..5e3fdf588bc 100644
--- a/packages/compiler-core/__tests__/transforms/__snapshots__/hoistStatic.spec.ts.snap
+++ b/packages/compiler-core/__tests__/transforms/__snapshots__/hoistStatic.spec.ts.snap
@@ -271,6 +271,22 @@ return function render(_ctx, _cache) {
}"
`;
+exports[`compiler: hoistStatic transform prefixIdentifiers should NOT hoist keyed template v-for with plain element child 1`] = `
+"const _Vue = Vue
+
+return function render(_ctx, _cache) {
+ with (_ctx) {
+ const { renderList: _renderList, Fragment: _Fragment, openBlock: _openBlock, createBlock: _createBlock, createVNode: _createVNode } = _Vue
+
+ return (_openBlock(), _createBlock(\\"div\\", null, [
+ (_openBlock(true), _createBlock(_Fragment, null, _renderList(items, (item) => {
+ return (_openBlock(), _createBlock(\\"span\\", { key: item }))
+ }), 128 /* KEYED_FRAGMENT */))
+ ]))
+ }
+}"
+`;
+
exports[`compiler: hoistStatic transform should NOT hoist components 1`] = `
"const _Vue = Vue
diff --git a/packages/compiler-core/__tests__/transforms/hoistStatic.spec.ts b/packages/compiler-core/__tests__/transforms/hoistStatic.spec.ts
index 5751b63f9af..c13f1c48f0c 100644
--- a/packages/compiler-core/__tests__/transforms/hoistStatic.spec.ts
+++ b/packages/compiler-core/__tests__/transforms/hoistStatic.spec.ts
@@ -600,5 +600,13 @@ describe('compiler: hoistStatic transform', () => {
}).code
).toMatchSnapshot()
})
+
+ test('should NOT hoist keyed template v-for with plain element child', () => {
+ const root = transformWithHoist(
+ `
`
+ )
+ expect(root.hoists.length).toBe(0)
+ expect(generate(root).code).toMatchSnapshot()
+ })
})
})
diff --git a/packages/compiler-core/src/transforms/hoistStatic.ts b/packages/compiler-core/src/transforms/hoistStatic.ts
index baf1a2afce4..240f9bacf4f 100644
--- a/packages/compiler-core/src/transforms/hoistStatic.ts
+++ b/packages/compiler-core/src/transforms/hoistStatic.ts
@@ -7,13 +7,12 @@ import {
PlainElementNode,
ComponentNode,
TemplateNode,
- ElementNode,
VNodeCall,
ParentNode
} from '../ast'
import { TransformContext } from '../transform'
import { PatchFlags, isString, isSymbol } from '@vue/shared'
-import { isSlotOutlet, findProp } from '../utils'
+import { isSlotOutlet } from '../utils'
export function hoistStatic(root: RootNode, context: TransformContext) {
walk(
@@ -93,8 +92,7 @@ function walk(
(!flag ||
flag === PatchFlags.NEED_PATCH ||
flag === PatchFlags.TEXT) &&
- !hasDynamicKeyOrRef(child) &&
- !hasCachedProps(child)
+ !hasNonHoistableProps(child)
) {
const props = getNodeProps(child)
if (props) {
@@ -156,7 +154,7 @@ export function getStaticType(
return StaticType.NOT_STATIC
}
const flag = getPatchFlag(codegenNode)
- if (!flag && !hasDynamicKeyOrRef(node) && !hasCachedProps(node)) {
+ if (!flag && !hasNonHoistableProps(node)) {
// element self is static. check its children.
let returnType = StaticType.FULL_STATIC
for (let i = 0; i < node.children.length; i++) {
@@ -238,28 +236,23 @@ export function getStaticType(
}
}
-function hasDynamicKeyOrRef(node: ElementNode): boolean {
- return !!(findProp(node, 'key', true) || findProp(node, 'ref', true))
-}
-
-function hasCachedProps(node: PlainElementNode): boolean {
- if (__BROWSER__) {
- return false
- }
+/**
+ * Even for a node with no patch flag, it is possible for it to contain
+ * non-hoistable expressions that refers to scope variables, e.g. compiler
+ * injected keys or cached event handlers. Therefore we need to always check the
+ * codegenNode's props to be sure.
+ */
+function hasNonHoistableProps(node: PlainElementNode): boolean {
const props = getNodeProps(node)
if (props && props.type === NodeTypes.JS_OBJECT_EXPRESSION) {
const { properties } = props
for (let i = 0; i < properties.length; i++) {
- const val = properties[i].value
- if (val.type === NodeTypes.JS_CACHE_EXPRESSION) {
- return true
- }
- // merged event handlers
+ const { key, value } = properties[i]
if (
- val.type === NodeTypes.JS_ARRAY_EXPRESSION &&
- val.elements.some(
- e => !isString(e) && e.type === NodeTypes.JS_CACHE_EXPRESSION
- )
+ key.type !== NodeTypes.SIMPLE_EXPRESSION ||
+ !key.isStatic ||
+ (value.type !== NodeTypes.SIMPLE_EXPRESSION ||
+ (!value.isStatic && !value.isConstant))
) {
return true
}
diff --git a/packages/compiler-core/src/transforms/vIf.ts b/packages/compiler-core/src/transforms/vIf.ts
index cab3dd92e0a..bebf3eb9082 100644
--- a/packages/compiler-core/src/transforms/vIf.ts
+++ b/packages/compiler-core/src/transforms/vIf.ts
@@ -19,7 +19,8 @@ import {
BlockCodegenNode,
IfNode,
createVNodeCall,
- AttributeNode
+ AttributeNode,
+ locStub
} from '../ast'
import { createCompilerError, ErrorCodes } from '../errors'
import { processExpression } from './transformExpression'
@@ -222,7 +223,7 @@ function createChildrenCodegenNode(
const { helper } = context
const keyProperty = createObjectProperty(
`key`,
- createSimpleExpression(`${keyIndex}`, false)
+ createSimpleExpression(`${keyIndex}`, false, locStub, true)
)
const { children } = branch
const firstChild = children[0]