mirror of https://github.com/vuejs/core.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%.
This commit is contained in:
parent
e8e3ec6ca7
commit
3be53d9b97
|
@ -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<Required<CodegenOptions>, '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()
|
||||
|
|
Loading…
Reference in New Issue