perf(workspace-plugin): reuse a single api-extractor compiler state in generate-api - #36522
Open
Martin Hochel (Hotell) wants to merge 2 commits into
Open
Conversation
📊 Bundle size report✅ No changes found |
|
Pull request demo site: URL |
…g per-entry progress
Victor Genaev (mainframev)
approved these changes
Aug 6, 2026
Tudor Popa (tudorpopams)
requested review from
PaulGMardling and
Dmytro Kirpa (dmytrokirpa)
and removed request for
PaulGMardling and
Dmytro Kirpa (dmytrokirpa)
August 6, 2026 14:32
Dmytro Kirpa (dmytrokirpa)
approved these changes
Aug 6, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Previous Behavior
The
generate-apiexecutor invoked API Extractor once per api-extractor config without passing acompilerState. API Extractor therefore created a brand newts.Programinternally on every invocation.With
exportSubpathsenabled this scales linearly with the number of export subpaths.react-headless-components-preview/libraryhas 50 named subpaths + 1 primary entry point, so a singlegenerate-apirun built 51 TypeScript programs over the same declaration graph — ~40s for that one target.On top of that, API Extractor prints its version preamble on every invocation, so the console was flooded with 51 identical, useless lines that gave no indication of which entry point was being processed:
New Behavior
1. One shared TypeScript program
All api-extractor configs are now prepared up front, a single
CompilerStateis created from the primary config with every subpath entry passed viaadditionalEntryPoints, and that state is reused for allExtractor.invokecalls.This is viable because every config already compiles with the identical
overrideTsconfigproduced bygetTsConfigForApiExtractor, so the extra programs were genuinely redundant work. Only public API Extractor API is used (CompilerState.create/IExtractorInvokeOptions.compilerState) — no internals, no fork.Results —
react-headless-components-preview(51 entry points)generate-apiexecutor durationts.createProgramcallsA secondary win:
Collector.analyze()callsprogram.getSemanticDiagnostics()on each invocation, and TypeScript caches those per file on aPrograminstance — so calls 2..51 are now near-free instead of re-checking a fresh program each time.2. Readable console output
The repeated version preamble is deduplicated via a shared
messageCallback, and each entry point now logs what it is actually generating:Packages with a single entry point stay as quiet as before (the label drops to verbose level).
This also makes CI failures far easier to diagnose — the failing entry point is now obvious instead of being buried in 51 identical lines:
Implementation notes
apiExtractor()was split intoprepareExtractorConfig()andinvokeExtractor();createCompilerState()andcreateConsoleMessageDeduper()were added.tsc --emitDeclarationOnlystep, which wildcard expansion depends on).typescriptCompilerFolderis passed.Extractor.invokeper entry point) — only program construction is deduplicated.messageCallbackonly marksconsole-preambleandconsole-compiler-version-noticeas handled after their first occurrence. Every other message returns early untouched, so default console output and theerrorCount/warningCounttally are unaffected.Verification
etc/*.api.mdand 104dist/**/*.d.tsrollups are identical to a pre-change baseline snapshot;git statusis clean.etc/dialog.api.md(entry 11 of 51) and ran withCI=true(forcinglocal: false) — theApiReportNotCopiedwarning is still printed and still fails the target with exit code 1 (output above). This is expected from the code path too: the callback runs before the tally inMessageRouter._handleMessage, and non-local builds fail onerrorCount + warningCount > 0.react-button:generate-api(no export subpaths) is unaffected; repo-wide*.api.mddiff is empty.CompilerState.createis called exactly once, thatadditionalEntryPointsmatches the resolved subpath entry points, that every invocation receives the same instance, and that the deduper suppresses only repeated preambles while leaving unrelated console messages (e.g.ApiReportCopied) alone. 13/13 pass.workspace-plugin:lintpasses.No change file —
@fluentui/workspace-pluginis private.Related Issue(s)