Lazily load compose LSP client features and Java scaffolding parsers - #595
Draft
Brandon Waterloo [MSFT] (bwateratmsft) wants to merge 1 commit into
Draft
Lazily load compose LSP client features and Java scaffolding parsers#595Brandon Waterloo [MSFT] (bwateratmsft) wants to merge 1 commit into
Brandon Waterloo [MSFT] (bwateratmsft) wants to merge 1 commit into
Conversation
Both of these were statically imported by modules on the activation path, so their entire dependency subtrees were executed on every activation even though they are only needed for specific, user-initiated work. - `extension.ts` imported `AlternateYamlLanguageServiceClientFeature` and `DocumentSettingsClientFeature`, used only inside `activateComposeLanguageClient`. That import was the sole runtime edge into `vscode-languageserver-protocol`, whose CJS barrel re-exports `vscode-jsonrpc`, `vscode-languageserver-types`, `messages` and `protocol` via `__exportStar`. Because that is a runtime `for...in` copy loop, none of it can be tree-shaken, so ~0.30 MB of protocol definitions, LSP types and JSON-RPC connection machinery was loaded for every activation - including for users who only ever open a Dockerfile. - `JavaGatherInformationStep.ts` imported `fast-xml-parser` and `gradle-to-js` at module scope, though both are used only inside `prompt()`. That put ~0.24 MB of XML/Gradle parsing on every activation to serve users scaffolding a Java project. Deferring both drops the statically-reachable (eager) set from 1.97 MB to 1.44 MB, and median `require()` time of the extension bundle from ~163 ms to ~146 ms. Note that this does not change bundle size - esbuild has no code splitting in CJS output, so the code still lives in the entry file and is still read and pre-parsed. What changes is that these module bodies no longer execute at load. It also converts these subtrees to dynamic-only reachability, which is a prerequisite for splitting them into separate on-demand chunks later. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: e7a415ce-0c51-4b23-b395-191daf222ea7
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.
🤖
Summary
Defers two static imports that sat on the activation path but are only needed for specific, user-initiated work. Together they cut the statically-reachable ("eager") set by 27% and median bundle load time by ~10%.
This is a small, self-contained piece of the 2.4.0 code-loading regression work. It does not change the bundler.
The changes
1. Compose language-service client features (
extension.ts)AlternateYamlLanguageServiceClientFeatureandDocumentSettingsClientFeaturewere statically imported but are used only insideactivateComposeLanguageClient, which alreadyawaitsgetLanguageClient().That single import was the only runtime edge into the LSP stack — every other import in those files is already
import type. The chain:vscode-languageserver-protocol's entry is a CJS barrel:__exportStaris a runtimefor...incopy loop, so no bundler can determine which export came from which module — nothing can be pruned. ImportingRequestType(a 9-line class inmessages.js) therefore pulled in ~305 KB: the full protocol definitions, the LSP type system, and the JSON-RPC connection machinery. On every activation, including for users who only ever open a Dockerfile.2. Java scaffolding parsers (
JavaGatherInformationStep.ts)fast-xml-parserandgradle-to-jswere imported at module scope but used only insideprompt()— ~0.24 MB of parsing code loaded for everyone, to serve users scaffolding a Java project.Results
require()Measured with 11 interleaved runs per variant, comparing medians.
Bundle size is intentionally unchanged (+417 bytes, from esbuild's
__esm({...})lazy-initializer wrappers). esbuild has no code splitting in CJS output, so the code still lives in the entry file and is still read and pre-parsed by V8. What changes is that these module bodies no longer execute at load — which is why the win is ~10% rather than the 27% the eager-set number alone would suggest.The second benefit is structural: this converts both subtrees to dynamic-only reachability, which is what makes them eligible to be emitted as separate on-demand chunks. In a chunking prototype, these two changes take the entry bundle from 1.10 MB to 1.02 MB.
Notes for reviewers
await import()form is safe for these three packages, verified by bundling and inspecting the resolved namespaces.@microsoft/compose-language-service/vscodeis a workspace TS package (real ESM bindings);fast-xml-parserexposes discrete named exports (XMLBuilder,XMLParser,XMLValidator, nodefault);gradle-to-jsgets bothdefaultand namedparseText/parseFile. None needs thegetDefaultExport()helper fromlazyPackages.ts— that is required only for CJS packages that assign a single object tomodule.exports(e.g.handlebars), which esbuild cannot statically enumerate.awaitboundaries, so no call sites changed shape and no new async was introduced.tsc --noEmitandeslint --max-warnings 0both pass.Testing
vscode-jsonrpcis no longer statically reachable)await Promise.resolve().then(() => (init_vscode(), vscode_exports)))fast-xml-parserandgradle-to-jsround-tripped through the built bundle against real POM and Gradle contentpom.xmland abuild.gradleRelated
@azure/*packages: Move to modern@azure/*packages with functional-style exports #594