build: compile on one Rspack engine with Rslib 1.0 and Rsbuild 2.2 (#566) - #575
Conversation
) Bump @rslib/core to 1.0.0 and @rsbuild/core to 2.2.3 in every workspace package, example, and scaffolder template, and @rstest/* to 0.11.12, so a consumer installs one @rspack/core (2.2.2) and one native binding. Compiler (src/build/rslib.ts): output.autoExternal: false (v1 form); URL and worker parsing off for plugin builds so generated `new URL(…)` and `new Worker(new URL(…))` expressions survive verbatim; inspectConfig runs in production mode with NODE_ENV restored; persistent cache off. externalsType stays Rslib's ESM default, which reproduces the 0.x createRequire shim for CommonJS requires of Node builtins. Own sources resolve sibling modules with fileURLToPath + path.join instead of new URL(…, import.meta.url). Tests: lowered-config and packed assertions that plugin output is self-contained (Node builtins the only externals), a packed guard for a single Rspack engine, and inspect coverage under NODE_ENV=development.
🦋 Changeset detectedLatest commit: eec78a4 The changes in this PR will be included in the next version bump. This PR includes changesets to release 3 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: e9deb59fb9
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| The hatch executes on one bundler engine — the `@rspack/core` behind agent-bundle's pinned | ||
| `@rsbuild/core`, shared by the Rslib-compiled outputs (artifact scripts, MCP entries, hooks, the | ||
| package build) and the MCP App views — but that engine is agent-bundle's dependency, not your |
There was a problem hiding this comment.
Update the public tools TSDoc to match the one-engine claim
This new one-engine documentation conflicts with the exported AgentBundleToolsConfig TSDoc in src/core/types.ts, which still says the hatch runs under two engine copies, names Rslib's nested 2.1.x stack, and will be rendered into the generated public API pages. Users consulting that API reference therefore receive the opposite engine-identity guidance; update the source TSDoc as part of this migration so the generated and handwritten documentation agree.
AGENTS.md reference: AGENTS.md:L78-L85
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Fixed in 3bcaa47: the AgentBundleToolsConfig TSDoc in src/core/types.ts now states the one-engine reality (and the engine-identity caveat: the engine is agent-bundle's dependency, so hatch authors still use the { rspack } utils argument rather than an imported @rspack/core). The two internal comments that pointed at the old "dual-engine reality" (src/build/meta.ts, src/build/rslib.ts) were updated in the same commit.
…he inspection throws
# Conflicts: # docs/entry-conventions.md
commit: |
…nfig (Codex review)
# Conflicts: # package.json # pnpm-lock.yaml
…uperseded by #571's single module graph)
Closes the two P1 findings of #566 Section 1:
agent-bundlenow builds on one Rspack engine, and the Rslib v0→v1 migration blockers specific to this repo are resolved with tests.Versions
@rslib/core@rsbuild/core@rspack/core/@rspack/binding-*@rstest/*Bumped in root,
packages/{agent-bundle,rsc-runtime,rsc-markdown-stream,create-agent-bundle,workbench}, everyexamples/*, and the threecreate-agent-bundletemplates.packages/rsc-runtimedevDependency@rspack/coremoves2.2.1→2.2.2in lockstep with what@rsbuild/core@2.2.3resolves (~2.2.2); it is pinned exactly only because every devDependency in this repo is (no peer requires it —react-server-dom-rspack@0.1.0peers^2.2.0-0).pnpm-workspace.yamldrops the@rsbuild/core@2.2.1release-age exclusion and theeffect-rstest>@rstest/corepeer override (0.11.12 satisfieseffect-rstest's^0.11.11natively). Lockfile: one@rspack/core@2.2.2, one@rsbuild/core@2.2.3, one@rslib/core@1.0.0.@rstest/adapter-rslib@0.11.12peers@rslib/core: ">=0.18.6 || ^1.0.0-0"; Rstest v0.11.12 (2026-09-03) ships "chore(deps): update Rslib to 1.0.0 (web-infra-dev/rstest#1778)".Migration blockers (rslib.rs/guide/upgrade/v0-to-v1)
new URL('./…', import.meta.url)in the compiler's own buildfileURLToPath+path.join:src/build/entry-shell.ts:45(runtimeModulePath,cliEntryRuntimePathdelegates),src/build/entries.ts:58,src/host-contracts/codex-plugin-validation.ts:214,src/dev/playground/lifecycle-replay-service.ts:192,packages/create-agent-bundle/src/scaffold-cli.ts:65.new URL("../", import.meta.url)/new Worker(new URL('./<worker>.mjs', import.meta.url))into user buildsrspackIgnorecomments, so the generated code stays readable:src/build/rslib.ts:577setsmodule.parser.javascript.{url,worker}: falseinenforceInvariants, andpreserveResourceReferences(rslib.ts:70, wired at:713viatools.bundlerChain) turns off Rslib 1's ownrslib:new-urlrule, whose rule-level parser option would otherwise outrank the global one. Proven bytests/build.test.ts:1201(every URL/Worker expression survives verbatim, no asset emitted), the projection pool, and the packed harness.externalsTypedefaultmodule-import→modern-modulepackages/agent-bundle/rslib.config.ts:88and the compiler profile (rslib.ts:694), with the reasoning in code comments: the only CommonJSrequire()s of externals are Node builtins inside bundled dependencies (the TypeScript parser;yaml), andmodern-modulereproduces thecreateRequire()shim chunk 0.x emitted for them byte for byte (dist/6872.jsbefore ≡dist/6686.jsafter), whereas pinningmodule-importwould have changed them into hoistedimport * asnamespaces. NocreateRequireparser flag is needed: the shim chunk is reachable only from the parser chunk, never from the runtime entries the compiler re-bundles into plugin artifacts.createRequireshim counts in the built example artifacts are identical before/after.autoExternal: false→output: { autoExternal: false }src/build/rslib.ts:685. Asserted, not assumed (see below).rslib.inspectConfig()withoutmodeinspectProductionConfig(rslib.ts:83, used at:826) passes{ mode: 'production' }and restores the process'sNODE_ENVafterwards (Rslib'sinspectConfigwrites it). Test:tests/build.test.ts:1166runs a build underNODE_ENV=development, asserts one bundler config was lowered andNODE_ENVis unchanged.src/build/mcp-apps.tsalready passed the mode.dts: trueunder TypeScript 7 → tsgo,redirect.dts.extensionon.ts→.jsspecifier rewrite.syntax: 'es2022'explicitpackages/agent-bundle/rslib.config.ts, the compiler profile (rslib.ts),packages/rsc-runtime,packages/rsc-markdown-stream,packages/create-agent-bundle;engines.nodeinference does not change output.Also: the compiler profile sets
performance.buildCache: false(rslib.ts:668) — Rslib 1 defaults the persistent cache on, and a plugin build must be a function of its inputs, not of the previous run.Self-contained plugin output
The contract: every dependency of a generated executable is bundled; Node builtins are the only external modules; the framework never adds
externalsto a plugin build. Verified three ways:tests/self-contained-bundler-config.test.ts(integration pool) builds a fixture executable that importsnode:pathandyaml, capturesinspection.origin.{environmentConfigs,bundlerConfigs}, and asserts: composed libbundle: true,splitChunks: false,output.autoExternal: false, no top-levelautoExternal, nooutput.externals; loweredoutput.autoExternal === falsesurvives normalization; everyexternalsdeclaration on both the environment and the Rspack config is a Node builtin name,/^node:/, orpnpapi(Rslib's ownnode-target list, 56 declarations; no function, no object map, no package name);externalsType === 'modern-module';output.asyncChunks === false; the emitted bundle's only imports arenode:pathandnode:module(thecreateRequireshim),yamlis inlined, anddist/holds exactly one file. Note: the loweredoptimization.splitChunksunder Rslib 1 is{ chunks: 'async' }, notfalse— Rslib's ESM layer writes that over Rsbuild's lowering; withasyncChunks: falsethere is nothing to split, which thereaddirassertion proves directly.composeMcpAppsRsbuildConfig→createRsbuild().inspectConfig({ mode: 'production' }); environmentsplitChunks: false,inlineScripts: true,inlineStyles: true,output.externalsempty, noautoExternal; the Rspack config has noexternalsandasyncChunks: false. Thecomprehensiveexample's built MCP App output is byte-identical before/after.tests/packed-consumer.test.ts(emittedModuleReport, line 86) walks every.js/.mjsa real consumer builds from the installed tarball (all three host trees of the artifact plus the package build'sdist/), lexes them withes-module-lexer, and fails on any non-literal dynamic import, any bare non-builtin specifier, or any relative specifier that does not resolve to a file inside the same tree; it also checks@modelcontextprotocol/serveris inlined into the MCP bundle. Externals found:node:fs,node:path,node:process,node:url,node:child_process,node:crypto,node:fs/promises,node:os,node:module,node:readline,node:stream— nothing else. The same rule is enforced inside every build byAB6005(src/build/validate-artifact-modules.ts); the author's owntoolshatch is the only way a non-builtin may remain external.The plugin-build lowered configs differ from 0.23 only in ways the write-up below attributes; in particular the 0.23 externals array carried a Rslib-internal warn-only function (
composeExternalsWarnConfig, deleted upstream) and never a package external.One-engine guard (agent-bundle's own dependency tree)
tests/public-api-packed.test.ts:168installs the packed tarball into a scratch consumer and walks itsnode_modules: exactly one@rspack/core, one@rsbuild/core, one@rslib/core, one@rspack/binding-*, with the binding's version equal to@rspack/core's. Distinct from the plugin-output assertions above; a future split like thersc-runtimepin drifting from Rsbuild's~range fails this test.Before/after evidence
Full write-up (every hunk of both lowered-config diffs attributed to a cause with Rslib/Rsbuild source references):
/tmp/rslib1-after/diff-writeup.mdon the author's machine; summary:cache.buildDependencies+rslib.config.ts_privateMetato RsbuildcomposeExternalsWarnConfigdeleted(?:$|[/\\])regexes after builtinsautoExternalnow implemented by Rsbuildoutput.autoExternalparser.javascript.worker: false→{ url: 'new-url-relative' }; JS-ruleparser.url: false→rslib:new-urlrulefileURLToPath+joinurl: false,worker: false;rslib:new-urlrule parserurl: falseenforceInvariants+preserveResourceReferencessplitChunks.minSize: 0output.filenamefn,chunkFilename[id]~0;runtimeChunk: undefineddroppedasyncChunks: false)import.meta.env.*,process.env.BASE_URL,process.env.ASSET_PREFIXremoveEnvPresetDefinesautoCjsNodeBuiltin: trueremoved;externalsType→modern-modulecreateRequireshim identicallib.autoExternal→output.autoExternaldist (
/tmp/rslib1-{before,after}/dist-*.txt):agent-bundle411 → 411 files, −2,333 B (−0.015%), one chunk id renamed (6872.js→6686.js, same 808 B — thecryptobuiltin shim);@agent-bundle/runtime60 → 60 files, +20 B; measured before #571, its shared chunks carried Rslib 1's multi-compiler~0suffix — moot now that #571 builds the runtime as one lib entry (chunk names are plain ids again on the merged head);rsc-markdown-streambyte-identical;create-agent-bundlescaffold-cli.js−463 B. Built example artifacts:hooks-and-scriptsandcomprehensivebyte-identical;host-testdiffers only in embedded pnpm store path strings and Rspack 2.2.2 concatenated-module identifier suffixes.Types (
attw,publint):.d.tsfile sets identical (333/333agent-bundle, 48/48 runtime); 263 + 1 files changed, all 984 changed lines are relative specifier rewrites.ts→.js(static imports, re-exports, inlineimport("…")type paths); after normalizing that,diff -ris empty — tsgo introduced no formatting, ordering, or comment differences. One cosmetic residue: four adapter declarations keepdeclare module '../core/types.ts'un-rewritten while their imports say.js;tsc --traceResolutionresolves both spellings todist/core/types.d.tsand a strict consumer typecheck shows theAgentBundleConfigExtensionsaugmentation lands identically before/after.attw --pack --profile esm-only: byte-identical output, 13/13 entry points green (node16from ESM,bundler).publint0.3.24: "All good!" foragent-bundle(identical to baseline),@agent-bundle/runtime,rsc-markdown-stream,create-agent-bundle.Rsdoctor (
@rsdoctor/rspack-plugin1.6.3 on theagent-bundlepackage build, run from a lane-only config outside the repo; nothing committed): 109 assets / 16.61 MiB total, of which the library is 78 files / 13.81 MiB (gzip 2.37 MB) and the copied Workbench 31 files / 2.80 MiB; 333.d.ts; 884 modules (297srcTS, 34 JSON, 94zod, 1typescript, 458 external references); 21 entrypoints; no duplicate packages. Top chunks: the bundled TypeScript 5.9.3 parserdist/1178.js10.57 MB (73% of library bytes, 1.58 MB gzip; a static import ofapi,index,eval,config),9923.js1.58 MB (dev/build/eval, 355 modules),1802.js440 kB (adapters/install),8954.js227 kB (config/skills/schemas),2649.js191 kB (zod). Cold-cache compile ≈ 11.8 s of Rspack phases inside a 13.6 s process (builtin:swc-loaderon 297 files is the only loader; tsgo DTS 2.9 s in parallel). Flags: 8 × E1007 side-effects-only onnode:*externals (false-positive class), one Rspack top-level-await chunk-cycle warning. Pre-existing follow-ups, out of scope here: lazy-load the parser at its five import sites, minify that chunk (1.5 MB is pure re-indentation), decidezodbundling policy.Gates run on this branch state
pnpm install --frozen-lockfile,pnpm build,pnpm typecheck,pnpm lint,pnpm test:unit(3590 passed),pnpm test:route-unit(83),pnpm test:projection(172),pnpm test:integration:run(1072 passed, 33 skipped),pnpm test:packed(28 passed, 1 skipped),pnpm --filter './examples/*' check(all examples build and test on Rslib 1.0),pnpm docs:site:build(1906 pages, parity and dead-link checks green).Docs
docs/entry-conventions.md,docs/effect-conventions.md(parked@rslib/core0.23.2 pin retired),website/docs/{en,zh}/guide/authoring/package-entries.mdx,website/docs/{en,zh}/reference/configuration.mdx,website/docs/{en,zh}/reference/cli.mdx,examples/rsc-agent-runtime/README.md: the "two bundler engine copies" caveat becomes the one-engine statement, the URL/Worker preservation invariant is stated, andinspect --bundleris documented as lowering in production mode regardless ofNODE_ENV. Changeset:.changeset/rslib-1.md(agent-bundle,@agent-bundle/runtime,create-agent-bundle: patch).Merged
origin/mainafter #568, #570, #571 (as requested) and re-ran every gate on the merged head: build, typecheck, lint, unit (3637), integration (1073), packed (29), projection (172),lint:release(attw ×3 + declaration-import check),docs:site:build.packages/rsc-runtime's@rspack/coredevDependency is2.2.2, in lockstep with@rsbuild/core@2.2.3's~2.2.2; the lockfile carries one@rspack/core.Self-review
Reviewer:
change-risk-reviewer(gpt-5.6-sol-medium) could not start — its TraceDecay MCP dependency failed discovery — so per AGENTS.md the review ran as ageneralPurposesubagent on the same model (gpt-5.6-sol-medium), asked for concrete merge risks only againstorigin/main...HEAD, with eleven targeted checks (NODE_ENV restore, globalparser.urlside effects,rslib:new-urlrule name and hatch ordering, path-rewrite equivalence, bundledscaffold-cliownDirectory, changeset packages, en/zh parity, engine-guard platform fragility,buildCachescope, inspect snapshots, dead modules).Findings and dispositions:
src/build/rslib.tsinspectProductionConfig— should-fix: whenNODE_ENVwas unset and the inspection threw, the process was left atproduction. Fixed in 261ff7a: thefinallynow deletesNODE_ENVwhen it was unset (restores it otherwise), andtests/build.test.tsgainedleaves NODE_ENV unset when a failing inspection had set it. End state of a successfulbuildWithRslibis unchanged (rslib.build()setsproductionwhen it findsNODE_ENVunset, as 0.23 did).tests/self-contained-bundler-config.test.ts:74— should-fix: the "Node builtins only" predicate also acceptspnpapi. Dismissed:pnpapiis part of Rslib's ownnode-target external list (and webpack/Rspack'snodeexternals preset) — a virtual module that exists only inside a Yarn PnP runtime, names no package, and cannot be bundled. The framework adds nothing; the test's comment states the exception, and the packed emitted-JS walk confirms nopnpapiimport is emitted.All other checks: no risk found (the reviewer confirmed the
rslib:new-urlrule name against Rslib 1.0's dist, that usertools.bundlerChainhatches run before the invariant, that every rewritten candidate path equals the former URL resolution in bothdist/and.tslayouts, that@rspack/binding-*optional dependencies carryos/cpu/libcfilters so one binding installs, and that the lockfile carries one version each of Rspack/Rsbuild/Rslib).Second pass (same reviewer, after 261ff7a): no further findings; the
pnpapidismissal accepted.Codex review thread (P2,
website/docs/en/reference/configuration.mdx:190): the exportedAgentBundleToolsConfigTSDoc insrc/core/types.tsstill described two engine copies and would render into the generated API pages. Fixed: the TSDoc now states the one-engine reality and the engine-identity caveat; the two internal comments referring to the old "dual-engine reality" (src/build/meta.ts,src/build/rslib.ts) were updated in the same commit. Replied on the thread.