diff --git a/packages/compiler-core/src/codegen.ts b/packages/compiler-core/src/codegen.ts index fb3040dd944..b2f2a801797 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()