Skip to content

Commit 3be53d9

Browse files
committedNov 25, 2023
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%.
1 parent e8e3ec6 commit 3be53d9

File tree

1 file changed

+87
-28
lines changed

1 file changed

+87
-28
lines changed
 

‎packages/compiler-core/src/codegen.ts

+87-28
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,13 @@ export interface CodegenResult {
6969
map?: RawSourceMap
7070
}
7171

72+
const enum NewlineType {
73+
Start = 0,
74+
End = -1,
75+
None = -2,
76+
Unknown = -3
77+
}
78+
7279
export interface CodegenContext
7380
extends Omit<Required<CodegenOptions>, 'bindingMetadata' | 'inline'> {
7481
source: string
@@ -80,7 +87,7 @@ export interface CodegenContext
8087
pure: boolean
8188
map?: SourceMapGenerator
8289
helper(key: symbol): string
83-
push(code: string, node?: CodegenNode): void
90+
push(code: string, newlineIndex?: number, node?: CodegenNode): void
8491
indent(): void
8592
deindent(withoutNewLine?: boolean): void
8693
newline(): void
@@ -127,7 +134,7 @@ function createCodegenContext(
127134
helper(key) {
128135
return `_${helperNameMap[key]}`
129136
},
130-
push(code, node) {
137+
push(code, newlineIndex = NewlineType.None, node) {
131138
context.code += code
132139
if (!__BROWSER__ && context.map) {
133140
if (node) {
@@ -140,7 +147,41 @@ function createCodegenContext(
140147
}
141148
addMapping(node.loc.start, name)
142149
}
143-
advancePositionWithMutation(context, code)
150+
if (newlineIndex === NewlineType.Unknown) {
151+
// multiple newlines, full iteration
152+
advancePositionWithMutation(context, code)
153+
} else {
154+
// fast paths
155+
context.offset += code.length
156+
if (newlineIndex === NewlineType.None) {
157+
// no newlines; fast path to avoid newline detection
158+
if (__TEST__ && code.includes('\n')) {
159+
throw new Error(
160+
`CodegenContext.push() called newlineIndex: none, but contains` +
161+
`newlines: ${code.replace(/\n/g, '\\n')}`
162+
)
163+
}
164+
context.column += code.length
165+
} else {
166+
// single newline at known index
167+
if (newlineIndex === NewlineType.End) {
168+
newlineIndex = code.length - 1
169+
}
170+
if (
171+
__TEST__ &&
172+
(code.charAt(newlineIndex) !== '\n' ||
173+
code.slice(0, newlineIndex).includes('\n') ||
174+
code.slice(newlineIndex + 1).includes('\n'))
175+
) {
176+
throw new Error(
177+
`CodegenContext.push() called with newlineIndex: ${newlineIndex} ` +
178+
`but does not conform: ${code.replace(/\n/g, '\\n')}`
179+
)
180+
}
181+
context.line++
182+
context.column = code.length - newlineIndex
183+
}
184+
}
144185
if (node && node.loc !== locStub) {
145186
addMapping(node.loc.end)
146187
}
@@ -162,7 +203,7 @@ function createCodegenContext(
162203
}
163204

164205
function newline(n: number) {
165-
context.push('\n' + ` `.repeat(n))
206+
context.push('\n' + ` `.repeat(n), NewlineType.Start)
166207
}
167208

168209
function addMapping(loc: Position, name?: string) {
@@ -250,8 +291,10 @@ export function generate(
250291
// function mode const declarations should be inside with block
251292
// also they should be renamed to avoid collision with user properties
252293
if (hasHelpers) {
253-
push(`const { ${helpers.map(aliasHelper).join(', ')} } = _Vue`)
254-
push(`\n`)
294+
push(
295+
`const { ${helpers.map(aliasHelper).join(', ')} } = _Vue\n`,
296+
NewlineType.End
297+
)
255298
newline()
256299
}
257300
}
@@ -282,7 +325,7 @@ export function generate(
282325
}
283326
}
284327
if (ast.components.length || ast.directives.length || ast.temps) {
285-
push(`\n`)
328+
push(`\n`, NewlineType.Start)
286329
newline()
287330
}
288331

@@ -334,11 +377,14 @@ function genFunctionPreamble(ast: RootNode, context: CodegenContext) {
334377
const helpers = Array.from(ast.helpers)
335378
if (helpers.length > 0) {
336379
if (!__BROWSER__ && prefixIdentifiers) {
337-
push(`const { ${helpers.map(aliasHelper).join(', ')} } = ${VueBinding}\n`)
380+
push(
381+
`const { ${helpers.map(aliasHelper).join(', ')} } = ${VueBinding}\n`,
382+
NewlineType.End
383+
)
338384
} else {
339385
// "with" mode.
340386
// save Vue in a separate variable to avoid collision
341-
push(`const _Vue = ${VueBinding}\n`)
387+
push(`const _Vue = ${VueBinding}\n`, NewlineType.End)
342388
// in "with" mode, helpers are declared inside the with block to avoid
343389
// has check cost, but hoists are lifted out of the function - we need
344390
// to provide the helper here.
@@ -353,7 +399,7 @@ function genFunctionPreamble(ast: RootNode, context: CodegenContext) {
353399
.filter(helper => helpers.includes(helper))
354400
.map(aliasHelper)
355401
.join(', ')
356-
push(`const { ${staticHelpers} } = _Vue\n`)
402+
push(`const { ${staticHelpers} } = _Vue\n`, NewlineType.End)
357403
}
358404
}
359405
}
@@ -363,7 +409,8 @@ function genFunctionPreamble(ast: RootNode, context: CodegenContext) {
363409
push(
364410
`const { ${ast.ssrHelpers
365411
.map(aliasHelper)
366-
.join(', ')} } = require("${ssrRuntimeModuleName}")\n`
412+
.join(', ')} } = require("${ssrRuntimeModuleName}")\n`,
413+
NewlineType.End
367414
)
368415
}
369416
genHoists(ast.hoists, context)
@@ -402,18 +449,21 @@ function genModulePreamble(
402449
push(
403450
`import { ${helpers
404451
.map(s => helperNameMap[s])
405-
.join(', ')} } from ${JSON.stringify(runtimeModuleName)}\n`
452+
.join(', ')} } from ${JSON.stringify(runtimeModuleName)}\n`,
453+
NewlineType.End
406454
)
407455
push(
408456
`\n// Binding optimization for webpack code-split\nconst ${helpers
409457
.map(s => `_${helperNameMap[s]} = ${helperNameMap[s]}`)
410-
.join(', ')}\n`
458+
.join(', ')}\n`,
459+
NewlineType.End
411460
)
412461
} else {
413462
push(
414463
`import { ${helpers
415464
.map(s => `${helperNameMap[s]} as _${helperNameMap[s]}`)
416-
.join(', ')} } from ${JSON.stringify(runtimeModuleName)}\n`
465+
.join(', ')} } from ${JSON.stringify(runtimeModuleName)}\n`,
466+
NewlineType.End
417467
)
418468
}
419469
}
@@ -422,7 +472,8 @@ function genModulePreamble(
422472
push(
423473
`import { ${ast.ssrHelpers
424474
.map(s => `${helperNameMap[s]} as _${helperNameMap[s]}`)
425-
.join(', ')} } from "${ssrRuntimeModuleName}"\n`
475+
.join(', ')} } from "${ssrRuntimeModuleName}"\n`,
476+
NewlineType.End
426477
)
427478
}
428479

@@ -554,7 +605,7 @@ function genNodeList(
554605
for (let i = 0; i < nodes.length; i++) {
555606
const node = nodes[i]
556607
if (isString(node)) {
557-
push(node)
608+
push(node, NewlineType.Unknown)
558609
} else if (isArray(node)) {
559610
genNodeListAsArray(node, context)
560611
} else {
@@ -573,7 +624,7 @@ function genNodeList(
573624

574625
function genNode(node: CodegenNode | symbol | string, context: CodegenContext) {
575626
if (isString(node)) {
576-
context.push(node)
627+
context.push(node, NewlineType.Unknown)
577628
return
578629
}
579630
if (isSymbol(node)) {
@@ -671,12 +722,16 @@ function genText(
671722
node: TextNode | SimpleExpressionNode,
672723
context: CodegenContext
673724
) {
674-
context.push(JSON.stringify(node.content), node)
725+
context.push(JSON.stringify(node.content), NewlineType.Unknown, node)
675726
}
676727

677728
function genExpression(node: SimpleExpressionNode, context: CodegenContext) {
678729
const { content, isStatic } = node
679-
context.push(isStatic ? JSON.stringify(content) : content, node)
730+
context.push(
731+
isStatic ? JSON.stringify(content) : content,
732+
NewlineType.Unknown,
733+
node
734+
)
680735
}
681736

682737
function genInterpolation(node: InterpolationNode, context: CodegenContext) {
@@ -694,7 +749,7 @@ function genCompoundExpression(
694749
for (let i = 0; i < node.children!.length; i++) {
695750
const child = node.children![i]
696751
if (isString(child)) {
697-
context.push(child)
752+
context.push(child, NewlineType.Unknown)
698753
} else {
699754
genNode(child, context)
700755
}
@@ -715,9 +770,9 @@ function genExpressionAsPropertyKey(
715770
const text = isSimpleIdentifier(node.content)
716771
? node.content
717772
: JSON.stringify(node.content)
718-
push(text, node)
773+
push(text, NewlineType.None, node)
719774
} else {
720-
push(`[${node.content}]`, node)
775+
push(`[${node.content}]`, NewlineType.Unknown, node)
721776
}
722777
}
723778

@@ -726,7 +781,11 @@ function genComment(node: CommentNode, context: CodegenContext) {
726781
if (pure) {
727782
push(PURE_ANNOTATION)
728783
}
729-
push(`${helper(CREATE_COMMENT)}(${JSON.stringify(node.content)})`, node)
784+
push(
785+
`${helper(CREATE_COMMENT)}(${JSON.stringify(node.content)})`,
786+
NewlineType.Unknown,
787+
node
788+
)
730789
}
731790

732791
function genVNodeCall(node: VNodeCall, context: CodegenContext) {
@@ -754,7 +813,7 @@ function genVNodeCall(node: VNodeCall, context: CodegenContext) {
754813
const callHelper: symbol = isBlock
755814
? getVNodeBlockHelper(context.inSSR, isComponent)
756815
: getVNodeHelper(context.inSSR, isComponent)
757-
push(helper(callHelper) + `(`, node)
816+
push(helper(callHelper) + `(`, NewlineType.None, node)
758817
genNodeList(
759818
genNullableArgs([tag, props, children, patchFlag, dynamicProps]),
760819
context
@@ -785,7 +844,7 @@ function genCallExpression(node: CallExpression, context: CodegenContext) {
785844
if (pure) {
786845
push(PURE_ANNOTATION)
787846
}
788-
push(callee + `(`, node)
847+
push(callee + `(`, NewlineType.None, node)
789848
genNodeList(node.arguments, context)
790849
push(`)`)
791850
}
@@ -794,7 +853,7 @@ function genObjectExpression(node: ObjectExpression, context: CodegenContext) {
794853
const { push, indent, deindent, newline } = context
795854
const { properties } = node
796855
if (!properties.length) {
797-
push(`{}`, node)
856+
push(`{}`, NewlineType.None, node)
798857
return
799858
}
800859
const multilines =
@@ -834,7 +893,7 @@ function genFunctionExpression(
834893
// wrap slot functions with owner context
835894
push(`_${helperNameMap[WITH_CTX]}(`)
836895
}
837-
push(`(`, node)
896+
push(`(`, NewlineType.None, node)
838897
if (isArray(params)) {
839898
genNodeList(params, context)
840899
} else if (params) {
@@ -934,7 +993,7 @@ function genTemplateLiteral(node: TemplateLiteral, context: CodegenContext) {
934993
for (let i = 0; i < l; i++) {
935994
const e = node.elements[i]
936995
if (isString(e)) {
937-
push(e.replace(/(`|\$|\\)/g, '\\$1'))
996+
push(e.replace(/(`|\$|\\)/g, '\\$1'), NewlineType.Unknown)
938997
} else {
939998
push('${')
940999
if (multilines) indent()

0 commit comments

Comments
 (0)
Please sign in to comment.