feat(plugin-js): js_bundle driver, esm/cjs + node/browser variants - #292
Open
raphaelvigee wants to merge 2 commits into
Open
feat(plugin-js): js_bundle driver, esm/cjs + node/browser variants#292raphaelvigee wants to merge 2 commits into
raphaelvigee wants to merge 2 commits into
Conversation
M6 of the JS/TS plugin, the final milestone: a cacheable js_bundle ManagedDriver wrapping esbuild, with format (esm/cjs) and target (node/browser) as plain addr args resolved with a flat default rather than porting the Go plugin's ancestry/universe variant machinery — this plugin's addr model is a single flat workspace, so there's no ancestor chain or cross-subtree variant-pin problem for that machinery to solve (researched and confirmed against crates/plugin-go/src/plugingo/variant.rs and this crate's own flat workspace.rs model before deciding). Whole-graph cache key by design (the entry point's full transitive closure via ImportGraph::runtime_edges, cross-package recursion unlike js_test's one-hop trim), third-party deps resolved via the same lockfile-driven mechanism as every other driver, tsconfig (plus its extends chain) declared and staged the same way js_typecheck does. Reviewed by feature-quality/code-quality/hermeticity. Two functional BLOCKERs found and fixed: the discovered third-party import closure was never wired into esbuild's --external flags, so bundling any package with a real npm runtime dependency failed outright; and the output directory didn't vary by format/target, so esm and cjs variants of the same package collided on the same declared output path. A third BLOCKER (missing tsconfig Input) meant path-aliased/JSX/decorator-configured TS entry points silently failed or cached wrong. All three fixed with regression tests, including a real-esbuild end-to-end proof for the external-deps fix. Explicitly deferred (disclosed in module docs): target=browser doesn't yet change what's resolved (only what esbuild is told to assume) since the resolver has no browser condition/main-field support; a config-reference-scanning helper shared with js_test/js_lint reads a config-referenced file before checking workspace containment rather than after (dormant for js_bundle's only supported config format today, live for js_test already, flagged as a cross-cutting follow-up not specific to this milestone); rollup/webpack/vite bundlers. Note for follow-up: the design doc's v1 scope table lists six drivers including js_format, but the Milestones list only ever defined M0-M6 for the other five (install/typecheck/test/lint/bundle) — js_format has no milestone and isn't built. Flagged in plugin-js-cdylib's module doc; needs either an M7 or a corrected scope table. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01M3wZfyPsG8stfRQuybLRjN
raphaelvigee
changed the base branch from
master
to
raphaelvigee/feat-plugin-js-m5-lint
August 5, 2026 00:19
Speed was the plugin's stated #1 design goal from the start (ai-docs/js-plugin-plan.md) but had never actually been measured. Adds #[ignore]d ad-hoc benches (never run under tst/cargo test by default) in importgraph.rs measuring Provider::import_graph's cold cost (first parse+resolve per package) vs warm cost (M5's per-package memoization) on synthetic workspaces of 20 and 150 packages, plus a parse-vs-resolve phase split. Results (darwin/arm64, this machine only — not transferable to other targets without re-running there): cold ~2.5-3ms/package, ~0.16-0.2ms/file; warm ~500ns total across 150 packages (cache hit never re-enters the build path, confirming M5's fix delivers as designed). Resolution (oxc_resolver) is 97%+ of cold cost, parsing (oxc_parser) is noise by comparison. No regression baseline existed before this, so this is establishing one, not comparing against one. Flagged as a real gap: no JS/TS scenario exists in crates/bench/crates/bench-corpus (Go-only today), so this path has no standing CI perf-regression coverage — building that out is the natural follow-up, left for later rather than folded into this change. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01M3wZfyPsG8stfRQuybLRjN
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.
M6 of the JS/TS heph plugin plan (part 6/6, top of the stack) — the final milestone, plus a first performance baseline for the whole plugin.
A cacheable js_bundle ManagedDriver wrapping esbuild, with format (esm/cjs) and target (node/browser) as plain addr args resolved with a flat default rather than porting the Go plugin's ancestry/universe variant machinery — this plugin's addr model is a single flat workspace, so there's no ancestor chain or cross-subtree variant-pin problem for that machinery to solve (researched and confirmed against
crates/plugin-go/src/plugingo/variant.rsand this crate's own flatworkspace.rsmodel before deciding).Whole-graph cache key by design (the entry point's full transitive closure via ImportGraph::runtime_edges, cross-package recursion unlike js_test's one-hop trim), third-party deps resolved via the same lockfile-driven mechanism as every other driver, tsconfig (plus its extends chain) declared and staged the same way js_typecheck does.
Reviewed by feature-quality/code-quality/hermeticity. Two functional BLOCKERs found and fixed: the discovered third-party import closure was never wired into esbuild's
--externalflags, so bundling any package with a real npm runtime dependency failed outright; and the output directory didn't vary by format/target, so esm and cjs variants of the same package collided on the same declared output path. A third BLOCKER (missing tsconfig Input) meant path-aliased/JSX/decorator-configured TS entry points silently failed or cached wrong. All three fixed with regression tests, including a real-esbuild end-to-end proof for the external-deps fix.Explicitly deferred (disclosed in module docs):
target=browserdoesn't yet change what's resolved (only what esbuild is told to assume) since the resolver has no browser condition/main-field support; a config-reference-scanning helper shared with js_test/js_lint reads a config-referenced file before checking workspace containment rather than after (dormant for js_bundle's only supported config format today, live for js_test already — flagged as a cross-cutting follow-up); rollup/webpack/vite bundlers.Open item for a future PR: the design doc's v1 scope table lists six drivers including
js_format, but the Milestones list only ever defined M0-M6 for the other five (install/typecheck/test/lint/bundle) —js_formathas no milestone and isn't built yet.Performance baseline
Speed was the plugin's stated #1 design goal from the start — this had never actually been measured until now. Added
#[ignore]d ad-hoc benches (never run undertst/defaultcargo test) measuringProvider::import_graph's cold cost (first parse+resolve per package) vs warm cost (the M5 per-package memoization) on synthetic 20- and 150-package workspaces, plus a parse-vs-resolve phase split.Results (darwin/arm64, this machine only — not transferable to other targets without re-running there):
oxc_resolver) is 97%+ of cold cost; parsing (oxc_parser) is noise by comparison (~2.6-2.7%).No prior baseline existed, so this establishes one rather than comparing against one — no regression, no obvious hotspot found. Real gap flagged: there's no JS/TS scenario in
crates/bench/crates/bench-corpus(Go-only today), so this path has no standing CI perf-regression coverage yet — building that out is the natural follow-up.With this PR, M0-M6 of
ai-docs/js-plugin-plan.mdare all implemented across the stack below.Test plan
cargo build -p plugin-js -p plugin-js-cdylibcargo test -p plugin-js(319 passed, 18 ignored — require realtsc/vitest/jest/oxlint/esbuildbinaries not provisioned in this devenv)cargo clippy -p plugin-js -p plugin-js-cdylib --all-targets -- -D warnings(clean)cargo fmt --check -p plugin-js -p plugin-js-cdylibtst,lint) on this PR🤖 Generated with Claude Code
https://claude.ai/code/session_01M3wZfyPsG8stfRQuybLRjN