perf(compiler-core): treat v-for with constant exp as a stable fragment (#1394)

This commit is contained in:
HcySunYang 2020-06-18 04:13:14 +08:00 committed by GitHub
parent 8899a90fc4
commit 8a2cf21b71
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 125 additions and 24 deletions

View File

@ -102,6 +102,15 @@ return function render(_ctx, _cache) {
}" }"
`; `;
exports[`compiler: codegen forNode with constant expression 1`] = `
"
return function render(_ctx, _cache) {
with (_ctx) {
return (_openBlock(), _createBlock(_Fragment, null, _renderList(), 64 /* STABLE_FRAGMENT */))
}
}"
`;
exports[`compiler: codegen function mode preamble 1`] = ` exports[`compiler: codegen function mode preamble 1`] = `
"const _Vue = Vue "const _Vue = Vue

View File

@ -32,7 +32,7 @@ import {
FRAGMENT, FRAGMENT,
RENDER_LIST RENDER_LIST
} from '../src/runtimeHelpers' } from '../src/runtimeHelpers'
import { createElementWithCodegen } from './testUtils' import { createElementWithCodegen, genFlagText } from './testUtils'
import { PatchFlags } from '@vue/shared' import { PatchFlags } from '@vue/shared'
function createRoot(options: Partial<RootNode> = {}): RootNode { function createRoot(options: Partial<RootNode> = {}): RootNode {
@ -283,7 +283,7 @@ describe('compiler: codegen', () => {
type: NodeTypes.VNODE_CALL, type: NodeTypes.VNODE_CALL,
tag: FRAGMENT, tag: FRAGMENT,
isBlock: true, isBlock: true,
isForBlock: true, disableTracking: true,
props: undefined, props: undefined,
children: createCallExpression(RENDER_LIST), children: createCallExpression(RENDER_LIST),
patchFlag: '1', patchFlag: '1',
@ -298,6 +298,37 @@ describe('compiler: codegen', () => {
expect(code).toMatchSnapshot() expect(code).toMatchSnapshot()
}) })
test('forNode with constant expression', () => {
const { code } = generate(
createRoot({
codegenNode: {
type: NodeTypes.FOR,
loc: locStub,
source: createSimpleExpression('1 + 2', false, locStub, true),
valueAlias: undefined,
keyAlias: undefined,
objectIndexAlias: undefined,
children: [],
parseResult: {} as any,
codegenNode: {
type: NodeTypes.VNODE_CALL,
tag: FRAGMENT,
isBlock: true,
disableTracking: false,
props: undefined,
children: createCallExpression(RENDER_LIST),
patchFlag: genFlagText(PatchFlags.STABLE_FRAGMENT),
dynamicProps: undefined,
directives: undefined,
loc: locStub
} as ForCodegenNode
}
})
)
expect(code).toMatch(`openBlock()`)
expect(code).toMatchSnapshot()
})
test('Element (callExpression + objectExpression + TemplateChildNode[])', () => { test('Element (callExpression + objectExpression + TemplateChildNode[])', () => {
const { code } = generate( const { code } = generate(
createRoot({ createRoot({

View File

@ -62,7 +62,7 @@ export function createElementWithCodegen(
dynamicProps, dynamicProps,
directives: undefined, directives: undefined,
isBlock: false, isBlock: false,
isForBlock: false, disableTracking: false,
loc: locStub loc: locStub
} }
} }

View File

@ -150,6 +150,20 @@ return function render(_ctx, _cache) {
}" }"
`; `;
exports[`compiler: v-for codegen v-for with constant expression 1`] = `
"const _Vue = Vue
return function render(_ctx, _cache) {
with (_ctx) {
const { renderList: _renderList, Fragment: _Fragment, openBlock: _openBlock, createBlock: _createBlock, toDisplayString: _toDisplayString, createVNode: _createVNode } = _Vue
return (_openBlock(), _createBlock(_Fragment, null, _renderList(10, (item) => {
return _createVNode(\\"p\\", null, _toDisplayString(item), 1 /* TEXT */)
}), 64 /* STABLE_FRAGMENT */))
}
}"
`;
exports[`compiler: v-for codegen v-if + v-for 1`] = ` exports[`compiler: v-for codegen v-if + v-for 1`] = `
"const _Vue = Vue "const _Vue = Vue

View File

@ -560,13 +560,16 @@ describe('compiler: v-for', () => {
function assertSharedCodegen( function assertSharedCodegen(
node: ForCodegenNode, node: ForCodegenNode,
keyed: boolean = false, keyed: boolean = false,
customReturn: boolean = false customReturn: boolean = false,
disableTracking: boolean = true
) { ) {
expect(node).toMatchObject({ expect(node).toMatchObject({
type: NodeTypes.VNODE_CALL, type: NodeTypes.VNODE_CALL,
tag: FRAGMENT, tag: FRAGMENT,
isForBlock: true, disableTracking,
patchFlag: keyed patchFlag: !disableTracking
? genFlagText(PatchFlags.STABLE_FRAGMENT)
: keyed
? genFlagText(PatchFlags.KEYED_FRAGMENT) ? genFlagText(PatchFlags.KEYED_FRAGMENT)
: genFlagText(PatchFlags.UNKEYED_FRAGMENT), : genFlagText(PatchFlags.UNKEYED_FRAGMENT),
children: { children: {
@ -580,7 +583,7 @@ describe('compiler: v-for', () => {
? {} ? {}
: { : {
type: NodeTypes.VNODE_CALL, type: NodeTypes.VNODE_CALL,
isBlock: true isBlock: disableTracking
} }
} }
] ]
@ -658,6 +661,43 @@ describe('compiler: v-for', () => {
expect(generate(root).code).toMatchSnapshot() expect(generate(root).code).toMatchSnapshot()
}) })
test('v-for with constant expression', () => {
const {
root,
node: { codegenNode }
} = parseWithForTransform('<p v-for="item in 10">{{item}}</p>', {
prefixIdentifiers: true
})
expect(
assertSharedCodegen(
codegenNode,
false /* keyed */,
false /* customReturn */,
false /* disableTracking */
)
).toMatchObject({
source: { content: `10`, isConstant: true },
params: [{ content: `item` }],
innerVNodeCall: {
tag: `"p"`,
props: undefined,
isBlock: false,
children: {
type: NodeTypes.INTERPOLATION,
content: {
type: NodeTypes.SIMPLE_EXPRESSION,
content: 'item',
isStatic: false,
isConstant: false
}
},
patchFlag: genFlagText(PatchFlags.TEXT)
}
})
expect(generate(root).code).toMatchSnapshot()
})
test('template v-for', () => { test('template v-for', () => {
const { const {
root, root,
@ -777,7 +817,7 @@ describe('compiler: v-for', () => {
key: `[0]` key: `[0]`
}), }),
isBlock: true, isBlock: true,
isForBlock: true, disableTracking: true,
patchFlag: genFlagText(PatchFlags.UNKEYED_FRAGMENT), patchFlag: genFlagText(PatchFlags.UNKEYED_FRAGMENT),
children: { children: {
type: NodeTypes.JS_CALL_EXPRESSION, type: NodeTypes.JS_CALL_EXPRESSION,

View File

@ -281,7 +281,7 @@ export interface VNodeCall extends Node {
dynamicProps: string | undefined dynamicProps: string | undefined
directives: DirectiveArguments | undefined directives: DirectiveArguments | undefined
isBlock: boolean isBlock: boolean
isForBlock: boolean disableTracking: boolean
} }
// JS Node Types --------------------------------------------------------------- // JS Node Types ---------------------------------------------------------------
@ -492,7 +492,7 @@ export interface ForCodegenNode extends VNodeCall {
props: undefined props: undefined
children: ForRenderListExpression children: ForRenderListExpression
patchFlag: string patchFlag: string
isForBlock: true disableTracking: boolean
} }
export interface ForRenderListExpression extends CallExpression { export interface ForRenderListExpression extends CallExpression {
@ -543,7 +543,7 @@ export function createVNodeCall(
dynamicProps?: VNodeCall['dynamicProps'], dynamicProps?: VNodeCall['dynamicProps'],
directives?: VNodeCall['directives'], directives?: VNodeCall['directives'],
isBlock: VNodeCall['isBlock'] = false, isBlock: VNodeCall['isBlock'] = false,
isForBlock: VNodeCall['isForBlock'] = false, disableTracking: VNodeCall['disableTracking'] = false,
loc = locStub loc = locStub
): VNodeCall { ): VNodeCall {
if (context) { if (context) {
@ -567,7 +567,7 @@ export function createVNodeCall(
dynamicProps, dynamicProps,
directives, directives,
isBlock, isBlock,
isForBlock, disableTracking,
loc loc
} }
} }

View File

@ -698,13 +698,13 @@ function genVNodeCall(node: VNodeCall, context: CodegenContext) {
dynamicProps, dynamicProps,
directives, directives,
isBlock, isBlock,
isForBlock disableTracking
} = node } = node
if (directives) { if (directives) {
push(helper(WITH_DIRECTIVES) + `(`) push(helper(WITH_DIRECTIVES) + `(`)
} }
if (isBlock) { if (isBlock) {
push(`(${helper(OPEN_BLOCK)}(${isForBlock ? `true` : ``}), `) push(`(${helper(OPEN_BLOCK)}(${disableTracking ? `true` : ``}), `)
} }
if (pure) { if (pure) {
push(PURE_ANNOTATION) push(PURE_ANNOTATION)

View File

@ -203,7 +203,7 @@ export const transformElement: NodeTransform = (node, context) => {
vnodeDynamicProps, vnodeDynamicProps,
vnodeDirectives, vnodeDirectives,
!!shouldUseBlock, !!shouldUseBlock,
false /* isForBlock */, false /* disableTracking */,
node.loc node.loc
) )
} }

View File

@ -55,7 +55,12 @@ export const transformFor = createStructuralDirectiveTransform(
forNode.source forNode.source
]) as ForRenderListExpression ]) as ForRenderListExpression
const keyProp = findProp(node, `key`) const keyProp = findProp(node, `key`)
const fragmentFlag = keyProp const isStableFragment =
forNode.source.type === NodeTypes.SIMPLE_EXPRESSION &&
forNode.source.isConstant
const fragmentFlag = isStableFragment
? PatchFlags.STABLE_FRAGMENT
: keyProp
? PatchFlags.KEYED_FRAGMENT ? PatchFlags.KEYED_FRAGMENT
: PatchFlags.UNKEYED_FRAGMENT : PatchFlags.UNKEYED_FRAGMENT
forNode.codegenNode = createVNodeCall( forNode.codegenNode = createVNodeCall(
@ -67,7 +72,7 @@ export const transformFor = createStructuralDirectiveTransform(
undefined, undefined,
undefined, undefined,
true /* isBlock */, true /* isBlock */,
true /* isForBlock */, !isStableFragment /* disableTracking */,
node.loc node.loc
) as ForCodegenNode ) as ForCodegenNode
@ -122,10 +127,12 @@ export const transformFor = createStructuralDirectiveTransform(
// but mark it as a block. // but mark it as a block.
childBlock = (children[0] as PlainElementNode) childBlock = (children[0] as PlainElementNode)
.codegenNode as VNodeCall .codegenNode as VNodeCall
childBlock.isBlock = true childBlock.isBlock = !isStableFragment
if (childBlock.isBlock) {
helper(OPEN_BLOCK) helper(OPEN_BLOCK)
helper(CREATE_BLOCK) helper(CREATE_BLOCK)
} }
}
renderExp.arguments.push(createFunctionExpression( renderExp.arguments.push(createFunctionExpression(
createForLoopParams(forNode.parseResult), createForLoopParams(forNode.parseResult),