Skip to content

refactor(compiler-cli): parse template diagNodes lazily and skip unused devmode TCB binding - #71904

Merged
thePunderWoman merged 1 commit into
angular:mainfrom
atscott:perf/ngp-lazy-diag-nodes
Nov 10, 2026
Merged

thePunderWoman merged 1 commit into
angular:mainfrom
atscott:perf/ngp-lazy-diag-nodes

Conversation

@atscott

@atscott atscott commented Nov 9, 2026 •

Copy link
Copy Markdown
Contributor

parseComponentTemplate previously ran the template parser twice on every component template: once with parseOptions for runtime code generation and eagerly a second time with diagParseOptions for TCB generation, diagnostics, and indexing. In devmode, emitDeclarationOnly, and cross-file DependencyTemplates lookups (which only inspect ngContentSelectors), diagNodes is never read, so the second parse was wasted work.

This change makes diagNodes a lazy memoized getter on ParsedComponentTemplate so the second parse only runs when diagNodes is actually accessed. It also skips ensureBoundWithMetadata in HybridCompiler.prepareChunk and prepareChunkSync during devmode and emitDeclarationOnly builds (binding only runtime parsed.nodes when a multi-file devmode chunk has localCompilationExtraImports, and populating preparedTcbData on demand in the indexer and language service when needed), skips re-running adaptTcbInput in prepareTcbTargets Pass 2 for targets that were already adapted with requiresInline in Pass 1, and reuses cached source text from compiler.fileCache in ngp.ts.

@angular-robot angular-robot Bot added area: performance Issues related to performance area: compiler Issues related to `ngc`, Angular's template compiler labels Nov 9, 2026
@ngbot ngbot Bot added this to the Backlog milestone Nov 9, 2026
@atscott
atscott force-pushed the perf/ngp-lazy-diag-nodes branch from 618fbbd to e1e0870 Compare November 10, 2026 00:50
@atscott atscott changed the title perf(compiler-cli): parse template diagNodes lazily and skip unused devmode TCB binding perf(compiler-cli): parse template diagNodes lazily and bind devmode chunks against runtime nodes Nov 10, 2026
@atscott atscott changed the title perf(compiler-cli): parse template diagNodes lazily and bind devmode chunks against runtime nodes perf(compiler-cli): parse template diagNodes lazily and skip unused devmode TCB binding Nov 10, 2026
@atscott
atscott force-pushed the perf/ngp-lazy-diag-nodes branch from e1e0870 to 49ea51f Compare November 10, 2026 02:08
@atscott
atscott marked this pull request as ready for review November 10, 2026 17:15
@atscott
atscott requested a review from crisbeto November 10, 2026 17:15
@atscott atscott added the target: minor This PR is targeted for the next minor release label Nov 10, 2026
@atscott
atscott requested a review from mattrbeck November 10, 2026 17:17
@atscott atscott changed the title perf(compiler-cli): parse template diagNodes lazily and skip unused devmode TCB binding refactor(compiler-cli): parse template diagNodes lazily and skip unused devmode TCB binding Nov 10, 2026
@atscott
atscott force-pushed the perf/ngp-lazy-diag-nodes branch from 49ea51f to 703676a Compare November 10, 2026 17:17
};

let cachedDiagNodes: TmplAstNode[] | undefined;
return {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it make sense to have a class for this instead of the inline object literal?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✨ This mirrors ngtsc's parseExtractedTemplate, where the return type is the ParsedComponentTemplate interface (export interface ParsedComponentTemplate extends ParsedTemplate) and the return value spreads {...parsedTemplate, diagNodes}. Since parseTemplate returns a plain ParsedTemplate object, the object literal lets us spread ...parseWithOptions(parseOptions) without manually copying each ParsedTemplate field in a class constructor.

let cachedDiagNodes: TmplAstNode[] | undefined;
return {
...parseWithOptions(parseOptions),
get diagNodes(): TmplAstNode[] {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was under the impression that we needed these ahead of time so that the spans in the TCB are correct.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✨ Like ngtsc's parseExtractedTemplate, we still do a second parse with the span-preserving options (preserveWhitespaces: true, preserveLineEndings: true, preserveSignificantWhitespace: true, leadingTriviaChars: []) for TCBs, diagnostics, and indexing — ngtsc just runs that second parse eagerly inside parseExtractedTemplate even though diagNodes is only ever read later during typeCheck() / index().

When we generate a TCB here, buildTcbTargets still reads parsed.diagNodes, which runs parseWithOptions(diagParseOptions) with the same content, contentSpan, and span-preserving options, so the TCB gets the exact same AST and spans. Making it a lazy getter just avoids paying for that second parse in paths that never read diagNodes (devmode, emitDeclarationOnly, and cross-file DependencyTemplates lookups that only inspect ngContentSelectors).

@atscott
atscott force-pushed the perf/ngp-lazy-diag-nodes branch from 703676a to d40330f Compare November 10, 2026 17:39
@atscott atscott added the action: merge The PR is ready for merge by the caretaker label Nov 10, 2026
…ed devmode TCB binding

parseComponentTemplate previously ran the template parser twice on every
component template: once with parseOptions for runtime code generation
and eagerly a second time with diagParseOptions for TCB generation,
diagnostics, and indexing. In devmode, emitDeclarationOnly, and
cross-file DependencyTemplates lookups (which only inspect
ngContentSelectors), diagNodes is never read, so the second parse was
wasted work.

This change makes diagNodes a lazy memoized getter on
ParsedComponentTemplate so the second parse only runs when diagNodes is
actually accessed. It also skips ensureBoundWithMetadata in
HybridCompiler.prepareChunk and prepareChunkSync for single-file
devmode chunks and emitDeclarationOnly builds (populating
preparedTcbData on demand in the indexer and language service when
needed), skips re-running adaptTcbInput in prepareTcbTargets Pass 2 for
targets that were already adapted with requiresInline in Pass 1, and
reuses cached source text from compiler.fileCache in ngp.ts.
@thePunderWoman
thePunderWoman force-pushed the perf/ngp-lazy-diag-nodes branch from d40330f to 42b4191 Compare November 10, 2026 18:40
@thePunderWoman
thePunderWoman removed the request for review from mattrbeck November 10, 2026 18:41
@thePunderWoman
thePunderWoman merged commit c877d9b into angular:main Nov 10, 2026
23 checks passed
@thePunderWoman

Copy link
Copy Markdown
Contributor

This PR was merged into the repository. The changes were merged into the following branches:

mattrbeck added a commit to mattrbeck/angular that referenced this pull request Nov 10, 2026
… parser closures

Follows up on angular#71904 with two targeted hardening fixes:

1. In `HybridCompiler.invalidateStaleFileAnalysis`, drop the `existing.boundFingerprint !== undefined` guard when comparing `existing.boundFingerprint !== nextFingerprint` so un-fingerprinted cache entries (for example from a direct `processFileSync` call) clear stale `parsedTemplates`, `preparedTcbData`, and `tcb` before stamping a newly computed fingerprint. Also clear `fileAnalysis.tcb` in `populateBoundData` whenever `preparedTcbData` is recomputed so subsequent `getTcbForFile` lookups do not return stale TCB code.
2. In `parseComponentTemplate`, isolate the lazy `diagNodes` parser closures into `createInlineDiagParser` and `createExternalDiagParser` so external/indirect templates do not capture the containing TypeScript file's `content` or `ComponentMetadata`, and clear the parser closure reference inside `defineLazyDiagNodes` once `diagNodes` is evaluated.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

action: merge The PR is ready for merge by the caretaker area: compiler Issues related to `ngc`, Angular's template compiler area: performance Issues related to performance target: minor This PR is targeted for the next minor release

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants