Repository navigation
perf(cli): Code-split the npm package and the SDK like the standalone binary - #1843
Merged
Merged
Conversation
… binary
The npm package shipped the CLI and the library as one ~7 MB `index.cjs`,
so every run and every `require("sentry")` compiled all of it. Both now
ship as code-split chunks, built and loaded by the same code as the
standalone binary:
- `script/lib/split-bundle.ts` builds code-split CommonJS (an ESM pass with
splitting, then each chunk converted to CommonJS). Every `import()` and
every lazy `_require()` (telemetry, cli-highlight, the --fields parser,
the WASM SQLite driver) starts a chunk. `script/bundle.ts` uses it for
`cli.cjs` + `index.cjs` (shared chunks) and the split ESM `index.mjs`;
`script/build.ts` for the binary, which embeds every chunk as a SEA asset.
- `script/lib/chunk-build.ts` holds the shared post-build steps: debug IDs
and sourcemap upload for every chunk, and the main script.
- `src/chunk-main.ts` (replaces `sea-main.ts`) is `dist/bin.cjs` and the SEA
main script. It runs `cli.cjs` or `complete.cjs` through
`src/lib/chunk-loader.ts` (replaces `sea-chunk.ts`), which reads chunks
from files or SEA assets and keeps a V8 code cache per chunk. Node < 22
loads the chunks natively instead: Node 20 drops `import()` from scripts
compiled from a code cache.
- The library no longer imports the CLI runner (`_cli` is gone, `bin.cjs`
runs `cli.cjs`). SDK calls load only the routes they run
(`src/sdk-app.ts`), so `require("sentry")` reads 79 KB instead of 6.8 MB.
- Each chunk registers its own debug ID with the injected snippet, so the
`__SENTRY_DEBUG_ID__` placeholder in `constants.ts` is gone.
Co-Authored-By: BYK <126780+BYK@users.noreply.github.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
BYK
requested changes
Nov 3, 2026
BYK
left a comment
Member
There was a problem hiding this comment.
Are we still compatible with Bun btw?
… loads need no limit
Contributor
Author
|
Re: Bun — yes. I tested this branch's npm build on Bun 1.4.4 (it reports Node 26 compat):
The standalone binary embeds Node, so Bun doesn't apply to it. There's no Bun job in CI, and |
BYK
approved these changes
Nov 3, 2026
sentry-junior Bot
added a commit
that referenced
this pull request
Nov 3, 2026
Resolve the AGENTS.md conflict with the code-split npm package (#1843), and point sdk-app.ts at selectApp(), which replaced routesForArgs(). The SDK loads the full app for the top-level help, whose precomputed copy it does not register. Co-Authored-By: BYK <burak.kaya@sentry.io>
This branch was previously deployed
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.
Follow-up to #1810. That PR split the standalone binary into a CLI asset and a completion asset with a V8 code cache. The npm package still shipped the CLI and the library as one ~7 MB
index.cjs, so everysentryrun and everyrequire("sentry")compiled all of it.Now the npm package, the SDK, and the binary all ship as code-split chunks. One build helper and one runtime loader produce and load them.
Build:
script/lib/split-bundle.ts, shared bybundle.tsandbuild.tssrc/(checked with trace-mapping).import()starts a chunk (each route inroute-loaders.ts). So does every lazy_require()thatrequire-aliasrewrites: telemetry, cli-highlight, the--fieldsparser, and the WASM SQLite driver. Each of these becomes alazy-*.cjsentry.import("./chunk-X.cjs")is rewritten to arequire-based__load()helper. That way a chunk loads through the same loader as its parent. Otherimport()calls stay native: builtins, and the Ink sidecar, which uses top-level await.cli.cjsandindex.cjsshare their chunks.index.mjsis split the same way and keeps the zlib shim.complete.cjsstays one file.package.json#filesnow usesdist/*.cjsanddist/*.mjs.dist-build/<chunk>.script/lib/chunk-build.tsholds the shared steps after the build: debug IDs and sourcemap upload for every chunk, the build hash, and bundling the main script. Each chunk registers its own debug ID through the injected snippet. The__SENTRY_DEBUG_ID__placeholder inconstants.tsis now dead, so it is removed.Runtime:
src/chunk-main.ts+src/lib/chunk-loader.tsThese replace
sea-main.tsandsea-chunk.ts.dist/bin.cjs(npm) and as the SEA main script. It runscli.cjsorcomplete.cjs.require("./chunk-*.cjs")itself. It reads each chunk from a file or a SEA asset. It keeps a V8 code cache per chunk under<config dir>/cache/code/<build>-<platform>-<v8 tag>/, writes it on exit, keeps the 3 newest builds, and deletes the cache files left by perf(cli): split the SEA binary into a bootstrap and cached chunks #1810.import()callback (ERR_VM_DYNAMIC_IMPORT_CALLBACK_MISSING). I hit this with the packed bin on Node 20.20.2. There is now a regression test forimport()from a cached chunk.SDK
index.tsno longer imports the CLI runner._cliis gone, becausebin.cjsrunscli.cjs.src/sdk-app.ts. It works likeapp-for-args.ts, except that the loaded routes accumulate so concurrent calls can't drop each other's routes.cli-startup.test.tsnow asserts thatindex.ts,sdk-app.ts, andapp-for-args.tsload no command.Not done: a consumer's bundler still can't drop unused commands at build time.
createSentrySDK()returns one object that references every command. The chunks also have real top-level side effects (module init, registrations), so I did not setsideEffects: false. For now, "tree-shakeable" means runtime laziness: an app loads only what it calls.Measurements (Node 22, this sandbox; medians, noisy)
Warm code cache,
SENTRY_CLI_NO_TELEMETRY=1, 21 runs:--versionissue list --helporg list --helpissue list --help: npm 431 → 372 ms, SEA 472 → 363 ms.issue list --help: npm 152 → 104 MB, SEA 130 → 105 MB.require("sentry")reads 6.8 MB on main and 79 KB here (~229 → 33 ms process time).import()ofindex.mjstakes ~289 → 43 ms.sdk.org.list()loads 1.66 MB in 80 files.--versionandhelpstill load every route on main. perf(cli): Skip loading commands for --version and leading global flags #1826 fixes that, so the two PRs add up.Verification
tsc, rootpnpm run lint, andci:policypass.mainin this sandbox: 6 chmod/EACCES tests, plus 5errors/oauth/auto-login-host-guardmessage tests.SENTRY_CLI_BINARY=dist/bin.cjs, 207 tests) and against a linux-x64 SEA built withbuild.ts --single(179 tests).import()with and without a cache, Node 20 gate)sdk-app(partial, accumulated, concurrent, full tree)__completereads no CLI chunk, andissue list --helpreads under half of the chunksnpm packcontains every chunk and no sourcemap.mjschunkvia @BYK.
--
View Session in Sentry