Skip to content

Commit 8a2cf21

Browse files
authored
perf(compiler-core): treat v-for with constant exp as a stable fragment (#1394)
1 parent 8899a90 commit 8a2cf21

File tree

9 files changed

+125
-24
lines changed

9 files changed

+125
-24
lines changed

packages/compiler-core/__tests__/__snapshots__/codegen.spec.ts.snap

+9
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,15 @@ return function render(_ctx, _cache) {
102102
}"
103103
`;
104104

105+
exports[`compiler: codegen forNode with constant expression 1`] = `
106+
"
107+
return function render(_ctx, _cache) {
108+
with (_ctx) {
109+
return (_openBlock(), _createBlock(_Fragment, null, _renderList(), 64 /* STABLE_FRAGMENT */))
110+
}
111+
}"
112+
`;
113+
105114
exports[`compiler: codegen function mode preamble 1`] = `
106115
"const _Vue = Vue
107116

packages/compiler-core/__tests__/codegen.spec.ts

+33-2
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ import {
3232
FRAGMENT,
3333
RENDER_LIST
3434
} from '../src/runtimeHelpers'
35-
import { createElementWithCodegen } from './testUtils'
35+
import { createElementWithCodegen, genFlagText } from './testUtils'
3636
import { PatchFlags } from '@vue/shared'
3737

3838
function createRoot(options: Partial<RootNode> = {}): RootNode {
@@ -283,7 +283,7 @@ describe('compiler: codegen', () => {
283283
type: NodeTypes.VNODE_CALL,
284284
tag: FRAGMENT,
285285
isBlock: true,
286-
isForBlock: true,
286+
disableTracking: true,
287287
props: undefined,
288288
children: createCallExpression(RENDER_LIST),
289289
patchFlag: '1',
@@ -298,6 +298,37 @@ describe('compiler: codegen', () => {
298298
expect(code).toMatchSnapshot()
299299
})
300300

301+
test('forNode with constant expression', () => {
302+
const { code } = generate(
303+
createRoot({
304+
codegenNode: {
305+
type: NodeTypes.FOR,
306+
loc: locStub,
307+
source: createSimpleExpression('1 + 2', false, locStub, true),
308+
valueAlias: undefined,
309+
keyAlias: undefined,
310+
objectIndexAlias: undefined,
311+
children: [],
312+
parseResult: {} as any,
313+
codegenNode: {
314+
type: NodeTypes.VNODE_CALL,
315+
tag: FRAGMENT,
316+
isBlock: true,
317+
disableTracking: false,
318+
props: undefined,
319+
children: createCallExpression(RENDER_LIST),
320+
patchFlag: genFlagText(PatchFlags.STABLE_FRAGMENT),
321+
dynamicProps: undefined,
322+
directives: undefined,
323+
loc: locStub
324+
} as ForCodegenNode
325+
}
326+
})
327+
)
328+
expect(code).toMatch(`openBlock()`)
329+
expect(code).toMatchSnapshot()
330+
})
331+
301332
test('Element (callExpression + objectExpression + TemplateChildNode[])', () => {
302333
const { code } = generate(
303334
createRoot({

packages/compiler-core/__tests__/testUtils.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ export function createElementWithCodegen(
6262
dynamicProps,
6363
directives: undefined,
6464
isBlock: false,
65-
isForBlock: false,
65+
disableTracking: false,
6666
loc: locStub
6767
}
6868
}

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

+14
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,20 @@ return function render(_ctx, _cache) {
150150
}"
151151
`;
152152

153+
exports[`compiler: v-for codegen v-for with constant expression 1`] = `
154+
"const _Vue = Vue
155+
156+
return function render(_ctx, _cache) {
157+
with (_ctx) {
158+
const { renderList: _renderList, Fragment: _Fragment, openBlock: _openBlock, createBlock: _createBlock, toDisplayString: _toDisplayString, createVNode: _createVNode } = _Vue
159+
160+
return (_openBlock(), _createBlock(_Fragment, null, _renderList(10, (item) => {
161+
return _createVNode(\\"p\\", null, _toDisplayString(item), 1 /* TEXT */)
162+
}), 64 /* STABLE_FRAGMENT */))
163+
}
164+
}"
165+
`;
166+
153167
exports[`compiler: v-for codegen v-if + v-for 1`] = `
154168
"const _Vue = Vue
155169

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

+47-7
Original file line numberDiff line numberDiff line change
@@ -560,15 +560,18 @@ describe('compiler: v-for', () => {
560560
function assertSharedCodegen(
561561
node: ForCodegenNode,
562562
keyed: boolean = false,
563-
customReturn: boolean = false
563+
customReturn: boolean = false,
564+
disableTracking: boolean = true
564565
) {
565566
expect(node).toMatchObject({
566567
type: NodeTypes.VNODE_CALL,
567568
tag: FRAGMENT,
568-
isForBlock: true,
569-
patchFlag: keyed
570-
? genFlagText(PatchFlags.KEYED_FRAGMENT)
571-
: genFlagText(PatchFlags.UNKEYED_FRAGMENT),
569+
disableTracking,
570+
patchFlag: !disableTracking
571+
? genFlagText(PatchFlags.STABLE_FRAGMENT)
572+
: keyed
573+
? genFlagText(PatchFlags.KEYED_FRAGMENT)
574+
: genFlagText(PatchFlags.UNKEYED_FRAGMENT),
572575
children: {
573576
type: NodeTypes.JS_CALL_EXPRESSION,
574577
callee: RENDER_LIST,
@@ -580,7 +583,7 @@ describe('compiler: v-for', () => {
580583
? {}
581584
: {
582585
type: NodeTypes.VNODE_CALL,
583-
isBlock: true
586+
isBlock: disableTracking
584587
}
585588
}
586589
]
@@ -658,6 +661,43 @@ describe('compiler: v-for', () => {
658661
expect(generate(root).code).toMatchSnapshot()
659662
})
660663

664+
test('v-for with constant expression', () => {
665+
const {
666+
root,
667+
node: { codegenNode }
668+
} = parseWithForTransform('<p v-for="item in 10">{{item}}</p>', {
669+
prefixIdentifiers: true
670+
})
671+
672+
expect(
673+
assertSharedCodegen(
674+
codegenNode,
675+
false /* keyed */,
676+
false /* customReturn */,
677+
false /* disableTracking */
678+
)
679+
).toMatchObject({
680+
source: { content: `10`, isConstant: true },
681+
params: [{ content: `item` }],
682+
innerVNodeCall: {
683+
tag: `"p"`,
684+
props: undefined,
685+
isBlock: false,
686+
children: {
687+
type: NodeTypes.INTERPOLATION,
688+
content: {
689+
type: NodeTypes.SIMPLE_EXPRESSION,
690+
content: 'item',
691+
isStatic: false,
692+
isConstant: false
693+
}
694+
},
695+
patchFlag: genFlagText(PatchFlags.TEXT)
696+
}
697+
})
698+
expect(generate(root).code).toMatchSnapshot()
699+
})
700+
661701
test('template v-for', () => {
662702
const {
663703
root,
@@ -777,7 +817,7 @@ describe('compiler: v-for', () => {
777817
key: `[0]`
778818
}),
779819
isBlock: true,
780-
isForBlock: true,
820+
disableTracking: true,
781821
patchFlag: genFlagText(PatchFlags.UNKEYED_FRAGMENT),
782822
children: {
783823
type: NodeTypes.JS_CALL_EXPRESSION,

packages/compiler-core/src/ast.ts

+4-4
Original file line numberDiff line numberDiff line change
@@ -281,7 +281,7 @@ export interface VNodeCall extends Node {
281281
dynamicProps: string | undefined
282282
directives: DirectiveArguments | undefined
283283
isBlock: boolean
284-
isForBlock: boolean
284+
disableTracking: boolean
285285
}
286286

287287
// JS Node Types ---------------------------------------------------------------
@@ -492,7 +492,7 @@ export interface ForCodegenNode extends VNodeCall {
492492
props: undefined
493493
children: ForRenderListExpression
494494
patchFlag: string
495-
isForBlock: true
495+
disableTracking: boolean
496496
}
497497

498498
export interface ForRenderListExpression extends CallExpression {
@@ -543,7 +543,7 @@ export function createVNodeCall(
543543
dynamicProps?: VNodeCall['dynamicProps'],
544544
directives?: VNodeCall['directives'],
545545
isBlock: VNodeCall['isBlock'] = false,
546-
isForBlock: VNodeCall['isForBlock'] = false,
546+
disableTracking: VNodeCall['disableTracking'] = false,
547547
loc = locStub
548548
): VNodeCall {
549549
if (context) {
@@ -567,7 +567,7 @@ export function createVNodeCall(
567567
dynamicProps,
568568
directives,
569569
isBlock,
570-
isForBlock,
570+
disableTracking,
571571
loc
572572
}
573573
}

packages/compiler-core/src/codegen.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -698,13 +698,13 @@ function genVNodeCall(node: VNodeCall, context: CodegenContext) {
698698
dynamicProps,
699699
directives,
700700
isBlock,
701-
isForBlock
701+
disableTracking
702702
} = node
703703
if (directives) {
704704
push(helper(WITH_DIRECTIVES) + `(`)
705705
}
706706
if (isBlock) {
707-
push(`(${helper(OPEN_BLOCK)}(${isForBlock ? `true` : ``}), `)
707+
push(`(${helper(OPEN_BLOCK)}(${disableTracking ? `true` : ``}), `)
708708
}
709709
if (pure) {
710710
push(PURE_ANNOTATION)

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -203,7 +203,7 @@ export const transformElement: NodeTransform = (node, context) => {
203203
vnodeDynamicProps,
204204
vnodeDirectives,
205205
!!shouldUseBlock,
206-
false /* isForBlock */,
206+
false /* disableTracking */,
207207
node.loc
208208
)
209209
}

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

+14-7
Original file line numberDiff line numberDiff line change
@@ -55,9 +55,14 @@ export const transformFor = createStructuralDirectiveTransform(
5555
forNode.source
5656
]) as ForRenderListExpression
5757
const keyProp = findProp(node, `key`)
58-
const fragmentFlag = keyProp
59-
? PatchFlags.KEYED_FRAGMENT
60-
: PatchFlags.UNKEYED_FRAGMENT
58+
const isStableFragment =
59+
forNode.source.type === NodeTypes.SIMPLE_EXPRESSION &&
60+
forNode.source.isConstant
61+
const fragmentFlag = isStableFragment
62+
? PatchFlags.STABLE_FRAGMENT
63+
: keyProp
64+
? PatchFlags.KEYED_FRAGMENT
65+
: PatchFlags.UNKEYED_FRAGMENT
6166
forNode.codegenNode = createVNodeCall(
6267
context,
6368
helper(FRAGMENT),
@@ -67,7 +72,7 @@ export const transformFor = createStructuralDirectiveTransform(
6772
undefined,
6873
undefined,
6974
true /* isBlock */,
70-
true /* isForBlock */,
75+
!isStableFragment /* disableTracking */,
7176
node.loc
7277
) as ForCodegenNode
7378

@@ -122,9 +127,11 @@ export const transformFor = createStructuralDirectiveTransform(
122127
// but mark it as a block.
123128
childBlock = (children[0] as PlainElementNode)
124129
.codegenNode as VNodeCall
125-
childBlock.isBlock = true
126-
helper(OPEN_BLOCK)
127-
helper(CREATE_BLOCK)
130+
childBlock.isBlock = !isStableFragment
131+
if (childBlock.isBlock) {
132+
helper(OPEN_BLOCK)
133+
helper(CREATE_BLOCK)
134+
}
128135
}
129136

130137
renderExp.arguments.push(createFunctionExpression(

0 commit comments

Comments
 (0)