Prechádzať zdrojové kódy

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

HcySunYang 5 rokov pred
rodič
commit
8a2cf21b71

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

@@ -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`] = `
 "const _Vue = Vue
 

+ 33 - 2
packages/compiler-core/__tests__/codegen.spec.ts

@@ -32,7 +32,7 @@ import {
   FRAGMENT,
   RENDER_LIST
 } from '../src/runtimeHelpers'
-import { createElementWithCodegen } from './testUtils'
+import { createElementWithCodegen, genFlagText } from './testUtils'
 import { PatchFlags } from '@vue/shared'
 
 function createRoot(options: Partial<RootNode> = {}): RootNode {
@@ -283,7 +283,7 @@ describe('compiler: codegen', () => {
             type: NodeTypes.VNODE_CALL,
             tag: FRAGMENT,
             isBlock: true,
-            isForBlock: true,
+            disableTracking: true,
             props: undefined,
             children: createCallExpression(RENDER_LIST),
             patchFlag: '1',
@@ -298,6 +298,37 @@ describe('compiler: codegen', () => {
     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[])', () => {
     const { code } = generate(
       createRoot({

+ 1 - 1
packages/compiler-core/__tests__/testUtils.ts

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

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

@@ -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`] = `
 "const _Vue = Vue
 

+ 47 - 7
packages/compiler-core/__tests__/transforms/vFor.spec.ts

@@ -560,15 +560,18 @@ describe('compiler: v-for', () => {
     function assertSharedCodegen(
       node: ForCodegenNode,
       keyed: boolean = false,
-      customReturn: boolean = false
+      customReturn: boolean = false,
+      disableTracking: boolean = true
     ) {
       expect(node).toMatchObject({
         type: NodeTypes.VNODE_CALL,
         tag: FRAGMENT,
-        isForBlock: true,
-        patchFlag: keyed
-          ? genFlagText(PatchFlags.KEYED_FRAGMENT)
-          : genFlagText(PatchFlags.UNKEYED_FRAGMENT),
+        disableTracking,
+        patchFlag: !disableTracking
+          ? genFlagText(PatchFlags.STABLE_FRAGMENT)
+          : keyed
+            ? genFlagText(PatchFlags.KEYED_FRAGMENT)
+            : genFlagText(PatchFlags.UNKEYED_FRAGMENT),
         children: {
           type: NodeTypes.JS_CALL_EXPRESSION,
           callee: RENDER_LIST,
@@ -580,7 +583,7 @@ describe('compiler: v-for', () => {
                 ? {}
                 : {
                     type: NodeTypes.VNODE_CALL,
-                    isBlock: true
+                    isBlock: disableTracking
                   }
             }
           ]
@@ -658,6 +661,43 @@ describe('compiler: v-for', () => {
       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', () => {
       const {
         root,
@@ -777,7 +817,7 @@ describe('compiler: v-for', () => {
             key: `[0]`
           }),
           isBlock: true,
-          isForBlock: true,
+          disableTracking: true,
           patchFlag: genFlagText(PatchFlags.UNKEYED_FRAGMENT),
           children: {
             type: NodeTypes.JS_CALL_EXPRESSION,

+ 4 - 4
packages/compiler-core/src/ast.ts

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

+ 2 - 2
packages/compiler-core/src/codegen.ts

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

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

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

+ 14 - 7
packages/compiler-core/src/transforms/vFor.ts

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