From: Evan You Date: Thu, 23 Nov 2023 16:58:47 +0000 (+0800) Subject: perf(codegen): optimize line / column calculation during codegen X-Git-Tag: v3.4.0-alpha.2~17 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=3be53d9b974dae1a10eb795cade71ae765e17574;p=thirdparty%2Fvuejs%2Fcore.git 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%. --- diff --git a/packages/compiler-core/src/codegen.ts b/packages/compiler-core/src/codegen.ts index fb3040dd94..b2f2a80179 100644 --- a/packages/compiler-core/src/codegen.ts +++ b/packages/compiler-core/src/codegen.ts @@ -69,6 +69,13 @@ export interface CodegenResult { map?: RawSourceMap } +const enum NewlineType { + Start = 0, + End = -1, + None = -2, + Unknown = -3 +} + export interface CodegenContext extends Omit, 'bindingMetadata' | 'inline'> { source: string @@ -80,7 +87,7 @@ export interface CodegenContext pure: boolean map?: SourceMapGenerator helper(key: symbol): string - push(code: string, node?: CodegenNode): void + push(code: string, newlineIndex?: number, node?: CodegenNode): void indent(): void deindent(withoutNewLine?: boolean): void newline(): void @@ -127,7 +134,7 @@ function createCodegenContext( helper(key) { return `_${helperNameMap[key]}` }, - push(code, node) { + push(code, newlineIndex = NewlineType.None, node) { context.code += code if (!__BROWSER__ && context.map) { if (node) { @@ -140,7 +147,41 @@ function createCodegenContext( } 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) { addMapping(node.loc.end) } @@ -162,7 +203,7 @@ function createCodegenContext( } function newline(n: number) { - context.push('\n' + ` `.repeat(n)) + context.push('\n' + ` `.repeat(n), NewlineType.Start) } function addMapping(loc: Position, name?: string) { @@ -250,8 +291,10 @@ export function generate( // function mode const declarations should be inside with block // also they should be renamed to avoid collision with user properties if (hasHelpers) { - push(`const { ${helpers.map(aliasHelper).join(', ')} } = _Vue`) - push(`\n`) + push( + `const { ${helpers.map(aliasHelper).join(', ')} } = _Vue\n`, + NewlineType.End + ) newline() } } @@ -282,7 +325,7 @@ export function generate( } } if (ast.components.length || ast.directives.length || ast.temps) { - push(`\n`) + push(`\n`, NewlineType.Start) newline() } @@ -334,11 +377,14 @@ function genFunctionPreamble(ast: RootNode, context: CodegenContext) { const helpers = Array.from(ast.helpers) if (helpers.length > 0) { if (!__BROWSER__ && prefixIdentifiers) { - push(`const { ${helpers.map(aliasHelper).join(', ')} } = ${VueBinding}\n`) + push( + `const { ${helpers.map(aliasHelper).join(', ')} } = ${VueBinding}\n`, + NewlineType.End + ) } else { // "with" mode. // 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 // has check cost, but hoists are lifted out of the function - we need // to provide the helper here. @@ -353,7 +399,7 @@ function genFunctionPreamble(ast: RootNode, context: CodegenContext) { .filter(helper => helpers.includes(helper)) .map(aliasHelper) .join(', ') - push(`const { ${staticHelpers} } = _Vue\n`) + push(`const { ${staticHelpers} } = _Vue\n`, NewlineType.End) } } } @@ -363,7 +409,8 @@ function genFunctionPreamble(ast: RootNode, context: CodegenContext) { push( `const { ${ast.ssrHelpers .map(aliasHelper) - .join(', ')} } = require("${ssrRuntimeModuleName}")\n` + .join(', ')} } = require("${ssrRuntimeModuleName}")\n`, + NewlineType.End ) } genHoists(ast.hoists, context) @@ -402,18 +449,21 @@ function genModulePreamble( push( `import { ${helpers .map(s => helperNameMap[s]) - .join(', ')} } from ${JSON.stringify(runtimeModuleName)}\n` + .join(', ')} } from ${JSON.stringify(runtimeModuleName)}\n`, + NewlineType.End ) push( `\n// Binding optimization for webpack code-split\nconst ${helpers .map(s => `_${helperNameMap[s]} = ${helperNameMap[s]}`) - .join(', ')}\n` + .join(', ')}\n`, + NewlineType.End ) } else { push( `import { ${helpers .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( `import { ${ast.ssrHelpers .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++) { const node = nodes[i] if (isString(node)) { - push(node) + push(node, NewlineType.Unknown) } else if (isArray(node)) { genNodeListAsArray(node, context) } else { @@ -573,7 +624,7 @@ function genNodeList( function genNode(node: CodegenNode | symbol | string, context: CodegenContext) { if (isString(node)) { - context.push(node) + context.push(node, NewlineType.Unknown) return } if (isSymbol(node)) { @@ -671,12 +722,16 @@ function genText( node: TextNode | SimpleExpressionNode, context: CodegenContext ) { - context.push(JSON.stringify(node.content), node) + context.push(JSON.stringify(node.content), NewlineType.Unknown, node) } function genExpression(node: SimpleExpressionNode, context: CodegenContext) { 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) { @@ -694,7 +749,7 @@ function genCompoundExpression( for (let i = 0; i < node.children!.length; i++) { const child = node.children![i] if (isString(child)) { - context.push(child) + context.push(child, NewlineType.Unknown) } else { genNode(child, context) } @@ -715,9 +770,9 @@ function genExpressionAsPropertyKey( const text = isSimpleIdentifier(node.content) ? node.content : JSON.stringify(node.content) - push(text, node) + push(text, NewlineType.None, node) } else { - push(`[${node.content}]`, node) + push(`[${node.content}]`, NewlineType.Unknown, node) } } @@ -726,7 +781,11 @@ function genComment(node: CommentNode, context: CodegenContext) { if (pure) { 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) { @@ -754,7 +813,7 @@ function genVNodeCall(node: VNodeCall, context: CodegenContext) { const callHelper: symbol = isBlock ? getVNodeBlockHelper(context.inSSR, isComponent) : getVNodeHelper(context.inSSR, isComponent) - push(helper(callHelper) + `(`, node) + push(helper(callHelper) + `(`, NewlineType.None, node) genNodeList( genNullableArgs([tag, props, children, patchFlag, dynamicProps]), context @@ -785,7 +844,7 @@ function genCallExpression(node: CallExpression, context: CodegenContext) { if (pure) { push(PURE_ANNOTATION) } - push(callee + `(`, node) + push(callee + `(`, NewlineType.None, node) genNodeList(node.arguments, context) push(`)`) } @@ -794,7 +853,7 @@ function genObjectExpression(node: ObjectExpression, context: CodegenContext) { const { push, indent, deindent, newline } = context const { properties } = node if (!properties.length) { - push(`{}`, node) + push(`{}`, NewlineType.None, node) return } const multilines = @@ -834,7 +893,7 @@ function genFunctionExpression( // wrap slot functions with owner context push(`_${helperNameMap[WITH_CTX]}(`) } - push(`(`, node) + push(`(`, NewlineType.None, node) if (isArray(params)) { genNodeList(params, context) } else if (params) { @@ -934,7 +993,7 @@ function genTemplateLiteral(node: TemplateLiteral, context: CodegenContext) { for (let i = 0; i < l; i++) { const e = node.elements[i] if (isString(e)) { - push(e.replace(/(`|\$|\\)/g, '\\$1')) + push(e.replace(/(`|\$|\\)/g, '\\$1'), NewlineType.Unknown) } else { push('${') if (multilines) indent()