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 9ad4a155c..b739baf45 100644
--- a/packages/compiler-core/__tests__/transforms/__snapshots__/hoistStatic.spec.ts.snap
+++ b/packages/compiler-core/__tests__/transforms/__snapshots__/hoistStatic.spec.ts.snap
@@ -190,7 +190,7 @@ exports[`compiler: hoistStatic transform prefixIdentifiers hoist nested static t
"const _Vue = Vue
const _createVNode = Vue.createVNode
-const _hoisted_1 = _createVNode(\\"span\\", { foo: 0 }, _toString(1), 1 /* TEXT */)
+const _hoisted_1 = _createVNode(\\"span\\", { foo: 0 }, _toString(1))
return function render() {
with (this) {
@@ -300,6 +300,20 @@ return function render() {
}"
`;
+exports[`compiler: hoistStatic transform should NOT hoist element with dynamic ref 1`] = `
+"const _Vue = Vue
+
+return function render() {
+ with (this) {
+ const { createVNode: _createVNode, createBlock: _createBlock, openBlock: _openBlock } = _Vue
+
+ return (_openBlock(), _createBlock(\\"div\\", null, [
+ _createVNode(\\"div\\", { ref: foo }, null, 32 /* NEED_PATCH */)
+ ]))
+ }
+}"
+`;
+
exports[`compiler: hoistStatic transform should NOT hoist root node 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 bf4e2370b..f5567b982 100644
--- a/packages/compiler-core/__tests__/transforms/hoistStatic.spec.ts
+++ b/packages/compiler-core/__tests__/transforms/hoistStatic.spec.ts
@@ -273,6 +273,28 @@ describe('compiler: hoistStatic transform', () => {
expect(generate(root).code).toMatchSnapshot()
})
+ test('should NOT hoist element with dynamic ref', () => {
+ const { root, args } = transformWithHoist(`
`)
+ expect(root.hoists.length).toBe(0)
+ expect(args[2]).toMatchObject([
+ {
+ type: NodeTypes.ELEMENT,
+ codegenNode: {
+ callee: CREATE_VNODE,
+ arguments: [
+ `"div"`,
+ createObjectMatcher({
+ ref: `[foo]`
+ }),
+ `null`,
+ genFlagText(PatchFlags.NEED_PATCH)
+ ]
+ }
+ }
+ ])
+ expect(generate(root).code).toMatchSnapshot()
+ })
+
test('hoist static props for elements with directives', () => {
const { root, args } = transformWithHoist(
``
@@ -521,8 +543,7 @@ describe('compiler: hoistStatic transform', () => {
isStatic: false,
isConstant: true
}
- },
- '1 /* TEXT */'
+ }
]
}
])
diff --git a/packages/compiler-core/src/transforms/hoistStatic.ts b/packages/compiler-core/src/transforms/hoistStatic.ts
index ecadeddff..e528536df 100644
--- a/packages/compiler-core/src/transforms/hoistStatic.ts
+++ b/packages/compiler-core/src/transforms/hoistStatic.ts
@@ -15,9 +15,8 @@ import { APPLY_DIRECTIVES } from '../runtimeHelpers'
import { PatchFlags, isString, isSymbol } from '@vue/shared'
import { isSlotOutlet, findProp } from '../utils'
-function hasDynamicKey(node: ElementNode) {
- const keyProp = findProp(node, 'key')
- return keyProp && keyProp.type === NodeTypes.DIRECTIVE
+function hasDynamicKeyOrRef(node: ElementNode) {
+ return findProp(node, 'key', true) || findProp(node, 'ref', true)
}
export function hoistStatic(root: RootNode, context: TransformContext) {
@@ -57,7 +56,7 @@ function walk(
if (
!doNotHoistNode &&
isStaticNode(child, resultCache) &&
- !hasDynamicKey(child)
+ !hasDynamicKeyOrRef(child)
) {
// whole tree is static
child.codegenNode = context.hoist(child.codegenNode!)
@@ -70,7 +69,7 @@ function walk(
(!flag ||
flag === PatchFlags.NEED_PATCH ||
flag === PatchFlags.TEXT) &&
- !hasDynamicKey(child)
+ !hasDynamicKeyOrRef(child)
) {
let codegenNode = child.codegenNode as ElementCodegenNode
if (codegenNode.callee === APPLY_DIRECTIVES) {
@@ -107,9 +106,9 @@ function getPatchFlag(node: PlainElementNode): number | undefined {
return flag ? parseInt(flag, 10) : undefined
}
-function isStaticNode(
+export function isStaticNode(
node: TemplateChildNode | SimpleExpressionNode,
- resultCache: Map
+ resultCache: Map = new Map()
): boolean {
switch (node.type) {
case NodeTypes.ELEMENT:
@@ -121,7 +120,7 @@ function isStaticNode(
return cached
}
const flag = getPatchFlag(node)
- if (!flag || flag === PatchFlags.TEXT) {
+ if (!flag) {
// element self is static. check its children.
for (let i = 0; i < node.children.length; i++) {
if (!isStaticNode(node.children[i], resultCache)) {
diff --git a/packages/compiler-core/src/transforms/transformElement.ts b/packages/compiler-core/src/transforms/transformElement.ts
index d54f2be39..571eb89d5 100644
--- a/packages/compiler-core/src/transforms/transformElement.ts
+++ b/packages/compiler-core/src/transforms/transformElement.ts
@@ -28,6 +28,7 @@ import {
} from '../runtimeHelpers'
import { getInnerRange, isVSlot, toValidAssetId } from '../utils'
import { buildSlots } from './vSlot'
+import { isStaticNode } from './hoistStatic'
// some directive transforms (e.g. v-model) may return a symbol for runtime
// import, which should be used instead of a resolveDirective call.
@@ -93,10 +94,11 @@ export const transformElement: NodeTransform = (node, context) => {
} else if (node.children.length === 1) {
const child = node.children[0]
const type = child.type
+ // check for dynamic text children
const hasDynamicTextChild =
type === NodeTypes.INTERPOLATION ||
type === NodeTypes.COMPOUND_EXPRESSION
- if (hasDynamicTextChild) {
+ if (hasDynamicTextChild && !isStaticNode(child)) {
patchFlag |= PatchFlags.TEXT
}
// pass directly if the only child is a text node
diff --git a/packages/compiler-core/src/utils.ts b/packages/compiler-core/src/utils.ts
index a297d54c4..b2f552bcb 100644
--- a/packages/compiler-core/src/utils.ts
+++ b/packages/compiler-core/src/utils.ts
@@ -156,15 +156,18 @@ export function findDir(
export function findProp(
node: ElementNode,
- name: string
+ name: string,
+ dynamicOnly: boolean = false
): ElementNode['props'][0] | undefined {
for (let i = 0; i < node.props.length; i++) {
const p = node.props[i]
if (p.type === NodeTypes.ATTRIBUTE) {
+ if (dynamicOnly) continue
if (p.name === name && p.value && !p.value.isEmpty) {
return p
}
} else if (
+ p.name === 'bind' &&
p.arg &&
p.arg.type === NodeTypes.SIMPLE_EXPRESSION &&
p.arg.isStatic &&