Procházet zdrojové kódy

perf(codegen): optimize line / column calculation during codegen

Previously, many CodegenContext.push() calls were unnecessarily
iterating through the entire pushed string to find newlines, when we
already know the newline positions for most of calls. Providing fast
paths for these calls significantly improves codegen performance when
source map is needed.

In benchmarks, this PR improves full SFC compilation performance by ~6%.
Evan You před 2 roky
rodič
revize
3be53d9b97
1 změnil soubory, kde provedl 87 přidání a 28 odebrání
  1. 87 28
      packages/compiler-core/src/codegen.ts

+ 87 - 28
packages/compiler-core/src/codegen.ts

@@ -69,6 +69,13 @@ export interface CodegenResult {
   map?: RawSourceMap
   map?: RawSourceMap
 }
 }
 
 
+const enum NewlineType {
+  Start = 0,
+  End = -1,
+  None = -2,
+  Unknown = -3
+}
+
 export interface CodegenContext
 export interface CodegenContext
   extends Omit<Required<CodegenOptions>, 'bindingMetadata' | 'inline'> {
   extends Omit<Required<CodegenOptions>, 'bindingMetadata' | 'inline'> {
   source: string
   source: string
@@ -80,7 +87,7 @@ export interface CodegenContext
   pure: boolean
   pure: boolean
   map?: SourceMapGenerator
   map?: SourceMapGenerator
   helper(key: symbol): string
   helper(key: symbol): string
-  push(code: string, node?: CodegenNode): void
+  push(code: string, newlineIndex?: number, node?: CodegenNode): void
   indent(): void
   indent(): void
   deindent(withoutNewLine?: boolean): void
   deindent(withoutNewLine?: boolean): void
   newline(): void
   newline(): void
@@ -127,7 +134,7 @@ function createCodegenContext(
     helper(key) {
     helper(key) {
       return `_${helperNameMap[key]}`
       return `_${helperNameMap[key]}`
     },
     },
-    push(code, node) {
+    push(code, newlineIndex = NewlineType.None, node) {
       context.code += code
       context.code += code
       if (!__BROWSER__ && context.map) {
       if (!__BROWSER__ && context.map) {
         if (node) {
         if (node) {
@@ -140,7 +147,41 @@ function createCodegenContext(
           }
           }
           addMapping(node.loc.start, name)
           addMapping(node.loc.start, name)
         }
         }
-        advancePositionWithMutation(context, code)
+        if (newlineIndex === NewlineType.Unknown) {
+          // multiple newlines, full iteration
+          advancePositionWithMutation(context, code)
+        } else {
+          // fast paths
+          context.offset += code.length
+          if (newlineIndex === NewlineType.None) {
+            // no newlines; fast path to avoid newline detection
+            if (__TEST__ && code.includes('\n')) {
+              throw new Error(
+                `CodegenContext.push() called newlineIndex: none, but contains` +
+                  `newlines: ${code.replace(/\n/g, '\\n')}`
+              )
+            }
+            context.column += code.length
+          } else {
+            // single newline at known index
+            if (newlineIndex === NewlineType.End) {
+              newlineIndex = code.length - 1
+            }
+            if (
+              __TEST__ &&
+              (code.charAt(newlineIndex) !== '\n' ||
+                code.slice(0, newlineIndex).includes('\n') ||
+                code.slice(newlineIndex + 1).includes('\n'))
+            ) {
+              throw new Error(
+                `CodegenContext.push() called with newlineIndex: ${newlineIndex} ` +
+                  `but does not conform: ${code.replace(/\n/g, '\\n')}`
+              )
+            }
+            context.line++
+            context.column = code.length - newlineIndex
+          }
+        }
         if (node && node.loc !== locStub) {
         if (node && node.loc !== locStub) {
           addMapping(node.loc.end)
           addMapping(node.loc.end)
         }
         }
@@ -162,7 +203,7 @@ function createCodegenContext(
   }
   }
 
 
   function newline(n: number) {
   function newline(n: number) {
-    context.push('\n' + `  `.repeat(n))
+    context.push('\n' + `  `.repeat(n), NewlineType.Start)
   }
   }
 
 
   function addMapping(loc: Position, name?: string) {
   function addMapping(loc: Position, name?: string) {
@@ -250,8 +291,10 @@ export function generate(
     // function mode const declarations should be inside with block
     // function mode const declarations should be inside with block
     // also they should be renamed to avoid collision with user properties
     // also they should be renamed to avoid collision with user properties
     if (hasHelpers) {
     if (hasHelpers) {
-      push(`const { ${helpers.map(aliasHelper).join(', ')} } = _Vue`)
-      push(`\n`)
+      push(
+        `const { ${helpers.map(aliasHelper).join(', ')} } = _Vue\n`,
+        NewlineType.End
+      )
       newline()
       newline()
     }
     }
   }
   }
@@ -282,7 +325,7 @@ export function generate(
     }
     }
   }
   }
   if (ast.components.length || ast.directives.length || ast.temps) {
   if (ast.components.length || ast.directives.length || ast.temps) {
-    push(`\n`)
+    push(`\n`, NewlineType.Start)
     newline()
     newline()
   }
   }
 
 
@@ -334,11 +377,14 @@ function genFunctionPreamble(ast: RootNode, context: CodegenContext) {
   const helpers = Array.from(ast.helpers)
   const helpers = Array.from(ast.helpers)
   if (helpers.length > 0) {
   if (helpers.length > 0) {
     if (!__BROWSER__ && prefixIdentifiers) {
     if (!__BROWSER__ && prefixIdentifiers) {
-      push(`const { ${helpers.map(aliasHelper).join(', ')} } = ${VueBinding}\n`)
+      push(
+        `const { ${helpers.map(aliasHelper).join(', ')} } = ${VueBinding}\n`,
+        NewlineType.End
+      )
     } else {
     } else {
       // "with" mode.
       // "with" mode.
       // save Vue in a separate variable to avoid collision
       // save Vue in a separate variable to avoid collision
-      push(`const _Vue = ${VueBinding}\n`)
+      push(`const _Vue = ${VueBinding}\n`, NewlineType.End)
       // in "with" mode, helpers are declared inside the with block to avoid
       // in "with" mode, helpers are declared inside the with block to avoid
       // has check cost, but hoists are lifted out of the function - we need
       // has check cost, but hoists are lifted out of the function - we need
       // to provide the helper here.
       // to provide the helper here.
@@ -353,7 +399,7 @@ function genFunctionPreamble(ast: RootNode, context: CodegenContext) {
           .filter(helper => helpers.includes(helper))
           .filter(helper => helpers.includes(helper))
           .map(aliasHelper)
           .map(aliasHelper)
           .join(', ')
           .join(', ')
-        push(`const { ${staticHelpers} } = _Vue\n`)
+        push(`const { ${staticHelpers} } = _Vue\n`, NewlineType.End)
       }
       }
     }
     }
   }
   }
@@ -363,7 +409,8 @@ function genFunctionPreamble(ast: RootNode, context: CodegenContext) {
     push(
     push(
       `const { ${ast.ssrHelpers
       `const { ${ast.ssrHelpers
         .map(aliasHelper)
         .map(aliasHelper)
-        .join(', ')} } = require("${ssrRuntimeModuleName}")\n`
+        .join(', ')} } = require("${ssrRuntimeModuleName}")\n`,
+      NewlineType.End
     )
     )
   }
   }
   genHoists(ast.hoists, context)
   genHoists(ast.hoists, context)
@@ -402,18 +449,21 @@ function genModulePreamble(
       push(
       push(
         `import { ${helpers
         `import { ${helpers
           .map(s => helperNameMap[s])
           .map(s => helperNameMap[s])
-          .join(', ')} } from ${JSON.stringify(runtimeModuleName)}\n`
+          .join(', ')} } from ${JSON.stringify(runtimeModuleName)}\n`,
+        NewlineType.End
       )
       )
       push(
       push(
         `\n// Binding optimization for webpack code-split\nconst ${helpers
         `\n// Binding optimization for webpack code-split\nconst ${helpers
           .map(s => `_${helperNameMap[s]} = ${helperNameMap[s]}`)
           .map(s => `_${helperNameMap[s]} = ${helperNameMap[s]}`)
-          .join(', ')}\n`
+          .join(', ')}\n`,
+        NewlineType.End
       )
       )
     } else {
     } else {
       push(
       push(
         `import { ${helpers
         `import { ${helpers
           .map(s => `${helperNameMap[s]} as _${helperNameMap[s]}`)
           .map(s => `${helperNameMap[s]} as _${helperNameMap[s]}`)
-          .join(', ')} } from ${JSON.stringify(runtimeModuleName)}\n`
+          .join(', ')} } from ${JSON.stringify(runtimeModuleName)}\n`,
+        NewlineType.End
       )
       )
     }
     }
   }
   }
@@ -422,7 +472,8 @@ function genModulePreamble(
     push(
     push(
       `import { ${ast.ssrHelpers
       `import { ${ast.ssrHelpers
         .map(s => `${helperNameMap[s]} as _${helperNameMap[s]}`)
         .map(s => `${helperNameMap[s]} as _${helperNameMap[s]}`)
-        .join(', ')} } from "${ssrRuntimeModuleName}"\n`
+        .join(', ')} } from "${ssrRuntimeModuleName}"\n`,
+      NewlineType.End
     )
     )
   }
   }
 
 
@@ -554,7 +605,7 @@ function genNodeList(
   for (let i = 0; i < nodes.length; i++) {
   for (let i = 0; i < nodes.length; i++) {
     const node = nodes[i]
     const node = nodes[i]
     if (isString(node)) {
     if (isString(node)) {
-      push(node)
+      push(node, NewlineType.Unknown)
     } else if (isArray(node)) {
     } else if (isArray(node)) {
       genNodeListAsArray(node, context)
       genNodeListAsArray(node, context)
     } else {
     } else {
@@ -573,7 +624,7 @@ function genNodeList(
 
 
 function genNode(node: CodegenNode | symbol | string, context: CodegenContext) {
 function genNode(node: CodegenNode | symbol | string, context: CodegenContext) {
   if (isString(node)) {
   if (isString(node)) {
-    context.push(node)
+    context.push(node, NewlineType.Unknown)
     return
     return
   }
   }
   if (isSymbol(node)) {
   if (isSymbol(node)) {
@@ -671,12 +722,16 @@ function genText(
   node: TextNode | SimpleExpressionNode,
   node: TextNode | SimpleExpressionNode,
   context: CodegenContext
   context: CodegenContext
 ) {
 ) {
-  context.push(JSON.stringify(node.content), node)
+  context.push(JSON.stringify(node.content), NewlineType.Unknown, node)
 }
 }
 
 
 function genExpression(node: SimpleExpressionNode, context: CodegenContext) {
 function genExpression(node: SimpleExpressionNode, context: CodegenContext) {
   const { content, isStatic } = node
   const { content, isStatic } = node
-  context.push(isStatic ? JSON.stringify(content) : content, node)
+  context.push(
+    isStatic ? JSON.stringify(content) : content,
+    NewlineType.Unknown,
+    node
+  )
 }
 }
 
 
 function genInterpolation(node: InterpolationNode, context: CodegenContext) {
 function genInterpolation(node: InterpolationNode, context: CodegenContext) {
@@ -694,7 +749,7 @@ function genCompoundExpression(
   for (let i = 0; i < node.children!.length; i++) {
   for (let i = 0; i < node.children!.length; i++) {
     const child = node.children![i]
     const child = node.children![i]
     if (isString(child)) {
     if (isString(child)) {
-      context.push(child)
+      context.push(child, NewlineType.Unknown)
     } else {
     } else {
       genNode(child, context)
       genNode(child, context)
     }
     }
@@ -715,9 +770,9 @@ function genExpressionAsPropertyKey(
     const text = isSimpleIdentifier(node.content)
     const text = isSimpleIdentifier(node.content)
       ? node.content
       ? node.content
       : JSON.stringify(node.content)
       : JSON.stringify(node.content)
-    push(text, node)
+    push(text, NewlineType.None, node)
   } else {
   } else {
-    push(`[${node.content}]`, node)
+    push(`[${node.content}]`, NewlineType.Unknown, node)
   }
   }
 }
 }
 
 
@@ -726,7 +781,11 @@ function genComment(node: CommentNode, context: CodegenContext) {
   if (pure) {
   if (pure) {
     push(PURE_ANNOTATION)
     push(PURE_ANNOTATION)
   }
   }
-  push(`${helper(CREATE_COMMENT)}(${JSON.stringify(node.content)})`, node)
+  push(
+    `${helper(CREATE_COMMENT)}(${JSON.stringify(node.content)})`,
+    NewlineType.Unknown,
+    node
+  )
 }
 }
 
 
 function genVNodeCall(node: VNodeCall, context: CodegenContext) {
 function genVNodeCall(node: VNodeCall, context: CodegenContext) {
@@ -754,7 +813,7 @@ function genVNodeCall(node: VNodeCall, context: CodegenContext) {
   const callHelper: symbol = isBlock
   const callHelper: symbol = isBlock
     ? getVNodeBlockHelper(context.inSSR, isComponent)
     ? getVNodeBlockHelper(context.inSSR, isComponent)
     : getVNodeHelper(context.inSSR, isComponent)
     : getVNodeHelper(context.inSSR, isComponent)
-  push(helper(callHelper) + `(`, node)
+  push(helper(callHelper) + `(`, NewlineType.None, node)
   genNodeList(
   genNodeList(
     genNullableArgs([tag, props, children, patchFlag, dynamicProps]),
     genNullableArgs([tag, props, children, patchFlag, dynamicProps]),
     context
     context
@@ -785,7 +844,7 @@ function genCallExpression(node: CallExpression, context: CodegenContext) {
   if (pure) {
   if (pure) {
     push(PURE_ANNOTATION)
     push(PURE_ANNOTATION)
   }
   }
-  push(callee + `(`, node)
+  push(callee + `(`, NewlineType.None, node)
   genNodeList(node.arguments, context)
   genNodeList(node.arguments, context)
   push(`)`)
   push(`)`)
 }
 }
@@ -794,7 +853,7 @@ function genObjectExpression(node: ObjectExpression, context: CodegenContext) {
   const { push, indent, deindent, newline } = context
   const { push, indent, deindent, newline } = context
   const { properties } = node
   const { properties } = node
   if (!properties.length) {
   if (!properties.length) {
-    push(`{}`, node)
+    push(`{}`, NewlineType.None, node)
     return
     return
   }
   }
   const multilines =
   const multilines =
@@ -834,7 +893,7 @@ function genFunctionExpression(
     // wrap slot functions with owner context
     // wrap slot functions with owner context
     push(`_${helperNameMap[WITH_CTX]}(`)
     push(`_${helperNameMap[WITH_CTX]}(`)
   }
   }
-  push(`(`, node)
+  push(`(`, NewlineType.None, node)
   if (isArray(params)) {
   if (isArray(params)) {
     genNodeList(params, context)
     genNodeList(params, context)
   } else if (params) {
   } else if (params) {
@@ -934,7 +993,7 @@ function genTemplateLiteral(node: TemplateLiteral, context: CodegenContext) {
   for (let i = 0; i < l; i++) {
   for (let i = 0; i < l; i++) {
     const e = node.elements[i]
     const e = node.elements[i]
     if (isString(e)) {
     if (isString(e)) {
-      push(e.replace(/(`|\$|\\)/g, '\\$1'))
+      push(e.replace(/(`|\$|\\)/g, '\\$1'), NewlineType.Unknown)
     } else {
     } else {
       push('${')
       push('${')
       if (multilines) indent()
       if (multilines) indent()