Skip to content

Commit 80f5cb2

Browse files
xanfyyx990803
authored andcommitted
fix(compiler): do not hoist element with dynamic key (vuejs#187)
1 parent f11dadc commit 80f5cb2

File tree

3 files changed

+106
-18
lines changed

3 files changed

+106
-18
lines changed

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

Lines changed: 42 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,23 @@
11
// Jest Snapshot v1, https://goo.gl/fbAQLP
22

3-
exports[`compiler: hositStatic transform hoist nested static tree 1`] = `
3+
exports[`compiler: hoistStatic transform hoist element with static key 1`] = `
4+
"const _Vue = Vue
5+
const _createVNode = Vue.createVNode
6+
7+
const _hoisted_1 = _createVNode(\\"div\\", { key: \\"foo\\" })
8+
9+
return function render() {
10+
with (this) {
11+
const { createVNode: _createVNode, createBlock: _createBlock, openBlock: _openBlock } = _Vue
12+
13+
return (_openBlock(), _createBlock(\\"div\\", null, [
14+
_hoisted_1
15+
]))
16+
}
17+
}"
18+
`;
19+
20+
exports[`compiler: hoistStatic transform hoist nested static tree 1`] = `
421
"const _Vue = Vue
522
const _createVNode = Vue.createVNode
623
@@ -20,7 +37,7 @@ return function render() {
2037
}"
2138
`;
2239

23-
exports[`compiler: hositStatic transform hoist siblings with common non-hoistable parent 1`] = `
40+
exports[`compiler: hoistStatic transform hoist siblings with common non-hoistable parent 1`] = `
2441
"const _Vue = Vue
2542
const _createVNode = Vue.createVNode
2643
@@ -39,7 +56,7 @@ return function render() {
3956
}"
4057
`;
4158

42-
exports[`compiler: hositStatic transform hoist simple element 1`] = `
59+
exports[`compiler: hoistStatic transform hoist simple element 1`] = `
4360
"const _Vue = Vue
4461
const _createVNode = Vue.createVNode
4562
@@ -56,7 +73,7 @@ return function render() {
5673
}"
5774
`;
5875
59-
exports[`compiler: hositStatic transform hoist static props for elements with directives 1`] = `
76+
exports[`compiler: hoistStatic transform hoist static props for elements with directives 1`] = `
6077
"const _Vue = Vue
6178
const _createVNode = Vue.createVNode
6279
@@ -77,7 +94,7 @@ return function render() {
7794
}"
7895
`;
7996
80-
exports[`compiler: hositStatic transform hoist static props for elements with dynamic text children 1`] = `
97+
exports[`compiler: hoistStatic transform hoist static props for elements with dynamic text children 1`] = `
8198
"const _Vue = Vue
8299
const _createVNode = Vue.createVNode
83100
@@ -94,7 +111,7 @@ return function render() {
94111
}"
95112
`;
96113
97-
exports[`compiler: hositStatic transform hoist static props for elements with unhoistable children 1`] = `
114+
exports[`compiler: hoistStatic transform hoist static props for elements with unhoistable children 1`] = `
98115
"const _Vue = Vue
99116
const _createVNode = Vue.createVNode
100117
@@ -115,7 +132,7 @@ return function render() {
115132
}"
116133
`;
117134
118-
exports[`compiler: hositStatic transform should NOT hoist components 1`] = `
135+
exports[`compiler: hoistStatic transform should NOT hoist components 1`] = `
119136
"const _Vue = Vue
120137
121138
return function render() {
@@ -131,7 +148,21 @@ return function render() {
131148
}"
132149
`;
133150
134-
exports[`compiler: hositStatic transform should NOT hoist element with dynamic props 1`] = `
151+
exports[`compiler: hoistStatic transform should NOT hoist element with dynamic key 1`] = `
152+
"const _Vue = Vue
153+
154+
return function render() {
155+
with (this) {
156+
const { createVNode: _createVNode, createBlock: _createBlock, openBlock: _openBlock } = _Vue
157+
158+
return (_openBlock(), _createBlock(\\"div\\", null, [
159+
_createVNode(\\"div\\", { key: foo })
160+
]))
161+
}
162+
}"
163+
`;
164+
165+
exports[`compiler: hoistStatic transform should NOT hoist element with dynamic props 1`] = `
135166
"const _Vue = Vue
136167
137168
return function render() {
@@ -145,7 +176,7 @@ return function render() {
145176
}"
146177
`;
147178
148-
exports[`compiler: hositStatic transform should NOT hoist root node 1`] = `
179+
exports[`compiler: hoistStatic transform should NOT hoist root node 1`] = `
149180
"const _Vue = Vue
150181
151182
return function render() {
@@ -157,7 +188,7 @@ return function render() {
157188
}"
158189
`;
159190
160-
exports[`compiler: hositStatic transform should hoist v-for children if static 1`] = `
191+
exports[`compiler: hoistStatic transform should hoist v-for children if static 1`] = `
161192
"const _Vue = Vue
162193
const _createVNode = Vue.createVNode
163194
@@ -179,7 +210,7 @@ return function render() {
179210
}"
180211
`;
181212
182-
exports[`compiler: hositStatic transform should hoist v-if props/children if static 1`] = `
213+
exports[`compiler: hoistStatic transform should hoist v-if props/children if static 1`] = `
183214
"const _Vue = Vue
184215
const _createVNode = Vue.createVNode
185216

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

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ function transformWithHoist(template: string) {
4242
}
4343
}
4444

45-
describe('compiler: hositStatic transform', () => {
45+
describe('compiler: hoistStatic transform', () => {
4646
test('should NOT hoist root node', () => {
4747
// if the whole tree is static, the root still needs to be a block
4848
// so that it's patched in optimized mode to skip children
@@ -187,6 +187,52 @@ describe('compiler: hositStatic transform', () => {
187187
expect(generate(root).code).toMatchSnapshot()
188188
})
189189

190+
test('hoist element with static key', () => {
191+
const { root, args } = transformWithHoist(`<div><div key="foo"/></div>`)
192+
expect(root.hoists.length).toBe(1)
193+
expect(root.hoists).toMatchObject([
194+
{
195+
type: NodeTypes.JS_CALL_EXPRESSION,
196+
callee: CREATE_VNODE,
197+
arguments: [`"div"`, createObjectMatcher({ key: 'foo' })]
198+
}
199+
])
200+
expect(args).toMatchObject([
201+
`"div"`,
202+
`null`,
203+
[
204+
{
205+
type: NodeTypes.ELEMENT,
206+
codegenNode: {
207+
type: NodeTypes.SIMPLE_EXPRESSION,
208+
content: `_hoisted_1`
209+
}
210+
}
211+
]
212+
])
213+
expect(generate(root).code).toMatchSnapshot()
214+
})
215+
216+
test('should NOT hoist element with dynamic key', () => {
217+
const { root, args } = transformWithHoist(`<div><div :key="foo"/></div>`)
218+
expect(root.hoists.length).toBe(0)
219+
expect(args[2]).toMatchObject([
220+
{
221+
type: NodeTypes.ELEMENT,
222+
codegenNode: {
223+
callee: CREATE_VNODE,
224+
arguments: [
225+
`"div"`,
226+
createObjectMatcher({
227+
key: `[foo]`
228+
})
229+
]
230+
}
231+
}
232+
])
233+
expect(generate(root).code).toMatchSnapshot()
234+
})
235+
190236
test('hoist static props for elements with directives', () => {
191237
const { root, args } = transformWithHoist(
192238
`<div><div id="foo" v-foo/></div>`

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

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,18 @@ import {
66
ElementCodegenNode,
77
PlainElementNode,
88
ComponentNode,
9-
TemplateNode
9+
TemplateNode,
10+
ElementNode
1011
} from '../ast'
1112
import { TransformContext } from '../transform'
1213
import { APPLY_DIRECTIVES } from '../runtimeHelpers'
1314
import { PatchFlags } from '@vue/shared'
14-
import { isSlotOutlet } from '../utils'
15+
import { isSlotOutlet, findProp } from '../utils'
16+
17+
function hasDynamicKey(node: ElementNode) {
18+
const keyProp = findProp(node, 'key')
19+
return keyProp && keyProp.type === NodeTypes.DIRECTIVE
20+
}
1521

1622
export function hoistStatic(root: RootNode, context: TransformContext) {
1723
walk(
@@ -47,7 +53,11 @@ function walk(
4753
child.type === NodeTypes.ELEMENT &&
4854
child.tagType === ElementTypes.ELEMENT
4955
) {
50-
if (!doNotHoistNode && isStaticNode(child, resultCache)) {
56+
if (
57+
!doNotHoistNode &&
58+
isStaticNode(child, resultCache) &&
59+
!hasDynamicKey(child)
60+
) {
5161
// whole tree is static
5262
child.codegenNode = context.hoist(child.codegenNode!)
5363
continue
@@ -56,9 +66,10 @@ function walk(
5666
// hoisting.
5767
const flag = getPatchFlag(child)
5868
if (
59-
!flag ||
60-
flag === PatchFlags.NEED_PATCH ||
61-
flag === PatchFlags.TEXT
69+
(!flag ||
70+
flag === PatchFlags.NEED_PATCH ||
71+
flag === PatchFlags.TEXT) &&
72+
!hasDynamicKey(child)
6273
) {
6374
let codegenNode = child.codegenNode as ElementCodegenNode
6475
if (codegenNode.callee === APPLY_DIRECTIVES) {

0 commit comments

Comments
 (0)