build(frontend): upgrade rsbuild to 2.2.0 - #2613
Conversation
malinskibeniamin
left a comment
There was a problem hiding this comment.
Automated /review: 9 finding(s).
These findings could not be anchored to a changed line:
- P1
frontend/bun.lock:400— Duplicate@rspack/coreinstance for module federation.
@rsbuild/core@2.2.0-beta.2 resolves @rspack/core@2.2.0-rc.0, but this lock now nests a second copy: @module-federation/rspack/@rspack/core → 2.0.6 (and the same for @rsdoctor/rspack-plugin / @rsdoctor/types). Before this change everything deduped on 2.0.6.
@module-federation/rspack imports @rspack/core directly, so its plugin classes and hook taps come from a different rspack instance — and a different native binding — than the compiler that runs them. Console ships embedded.js as a federated remote consumed by Cloud UI, so this sits on the critical output path, and it also pulls a second rspack native binary into the install.
Correction: align the versions — either hold @rsbuild/core on the 2.0.x line until @module-federation/rsbuild-plugin supports rspack 2.2, or add an override so a single @rspack/core resolves for all consumers.
Verify: cd frontend && bun install && bun pm ls | grep rspack (expect exactly one @rspack/core and one @rspack/binding), then bun run build and load embedded.js from a federation host.
- P2
frontend/package.json:788— A beta build toolchain is pinned for release builds, with a floating prerelease range underneath.
@rsbuild/core: 2.2.0-beta.2 declares @rspack/core: ~2.2.0-beta.1, which the lock satisfied with @rspack/core@2.2.0-rc.0 — a different prerelease than the one named. That range keeps floating across prereleases (beta.3, rc.1, …) on any lock refresh, so the bundler that produces the shipped artifact can change without a reviewed diff, and prereleases carry no compatibility guarantee.
Correction: if the 2.2 features are needed now, state that in the commit body and add an exact override for @rspack/core so the prerelease cannot drift; otherwise wait for @rsbuild/core@2.2.0 stable.
Verify: cd frontend && bun install --frozen-lockfile && bun pm ls | grep '@rspack/core' — the resolved version should equal the pinned one on a clean install.
| reuseExistingChunk: true, | ||
| }, | ||
| monaco: { | ||
| test: /[\\/]node_modules[\\/]monaco-editor[\\/]/, |
There was a problem hiding this comment.
Priority: P1
The Monaco enforce cache group can break Monaco's web workers.
MonacoWebpackPlugin (configured at line 193 with filename: 'static/js/[name].worker.js') emits separate entrypoints whose modules come from node_modules/monaco-editor. This cache group's test matches exactly those modules, and with the new top-level chunks: 'all' plus enforce: true, the worker entrypoints' own code becomes eligible to be hoisted into the shared lib-monaco-editor chunk.
A worker entry that depends on a second emitted file has no way to load it — the plugin points the worker URL at a single *.worker.js. The consequence is that yaml/json/typescript/protobuf language services fail at runtime (validation, completion, hover) while the editor itself still renders, so this fails quietly.
Correction: scope the cache group away from worker entries, e.g. add chunks: (chunk) => !chunk.name?.endsWith('worker') to the monaco group (and confirm the same for legacyUi), or drop enforce so size heuristics do not force the extraction.
Verify: cd frontend && bun run build, then check each build/static/js/*.worker.js is self-contained (no __webpack_require__.e / chunk-load calls) and open a topic's YAML/JSON editor in the built app with the console open — no worker load errors.
Automated /review.
| config.experiments = { | ||
| ...config.experiments, | ||
| lazyBarrel: false, | ||
| ...(isProd ? { fasterModuleConcatenation: true } : {}), |
There was a problem hiding this comment.
Priority: P2
lazyBarrel: false was dropped here without justification.
This line replaces the previous explicit lazyBarrel: false in config.experiments. Removing the opt-out re-enables Rspack's barrel-file optimization, which changes when re-export barrels' modules are evaluated and therefore the ordering of import side effects. This config depends on side-effect-sensitive setup (Monaco worker registration, the react-onclickoutside and date-fns-tz shims aliased just below, CSS imports), and the commit is titled purely as a version upgrade — so the behavior change is invisible to anyone reading the log.
Correction: restore lazyBarrel: false, or keep it removed and record in the commit body why the flag was originally set and what evidence shows enabling it is safe now.
Verify: cd frontend && bun run build and compare the emitted chunk graph against master, then smoke-test the app for missing side effects (Monaco workers, date formatting, datepicker shim).
Automated /review.
|
|
||
| import { fileURLToPath } from 'node:url'; | ||
|
|
||
| vi.mock('@module-federation/rsbuild-plugin', () => ({ |
There was a problem hiding this comment.
Priority: P2
Mocking pluginModuleFederation means this test does not describe the production config.
The real Module Federation plugin mutates optimization (including splitChunks) and injects the container/remote entries. Stubbing it out to a no-op setup means the asserted optimization.splitChunks shape and the serializedConfig string checks below are taken from a pipeline that never ships — the test can stay green while the actual production config differs on exactly the surface this PR changes.
Correction: either drop the mock and let the real plugin run during inspectConfig (it needs no network), or narrow the test's claims to the values this config file sets directly and assert the federation-influenced shape in the integration/build suite instead.
Verify: remove the vi.mock block and re-run the file; if the assertions still hold, the mock was unnecessary — if they change, the mock was hiding the real config.
Automated /review.
| fasterModuleConcatenation: true, | ||
| nativeWatcher: true, | ||
| }); | ||
| expect(rspackConfig?.experiments).not.toHaveProperty('lazyBarrel'); |
There was a problem hiding this comment.
Priority: P3
not.toHaveProperty('lazyBarrel') is a change detector, not a behavior assertion.
It asserts the absence of an upstream default rather than anything about the build output, so it will fail whenever Rspack begins materializing lazyBarrel in the resolved experiments — an unrelated upgrade breaks the test with no product defect. Prefer asserting the properties this config intentionally sets and leave upstream defaults unconstrained.
Automated /review.
| import rsbuildConfig from '../rsbuild.config'; | ||
|
|
||
| describe('Rsbuild production config', () => { | ||
| it('uses the optimized compiler and chunking pipeline', async () => { |
There was a problem hiding this comment.
Priority: P3
Repo convention is test() rather than it() for test cases (see the project test guidance and neighbouring files under frontend/tests/). Rename for consistency.
Automated /review.
|
|
||
| describe('Rsbuild production config', () => { | ||
| it('uses the optimized compiler and chunking pipeline', async () => { | ||
| const rsbuild = await createRsbuild({ |
There was a problem hiding this comment.
Priority: P3
A full createRsbuild() + inspectConfig() run now lives in the fast unit project.
vitest.config.unit.mts includes tests/**/*.test.ts with environment: 'node', so this case joins the pure unit suite while depending on loadEnv, plugin resolution, the TanStack Router plugin, and the real filesystem/cwd. That makes the unit suite slower and environment-sensitive.
Correction: move this to the integration config (or a dedicated build-config project) so unit runs stay hermetic and quick.
Verify: cd frontend && bun run test:unit and compare suite duration against master.
Automated /review.
| preset: 'default', | ||
| chunks: 'all', | ||
| // Cap asynchronous chunks without imposing a size limit on initial chunks. | ||
| maxAsyncSize: 512 * 1024, |
There was a problem hiding this comment.
Priority: P3
maxAsyncSize: 512 * 1024 has no supporting measurement. The comment explains what the cap does but not why 512 KB is the right threshold — capping every async chunk trades request count against payload size, and the effect depends on this app's route-split shape. Recording the before/after bundle report in the commit body (the config already supports RSDOCTOR=1) would make the number reviewable and safe to revisit later.
Automated /review.
f20a76e to
8df6382
Compare
Proven impact
Value proven: The upgraded compiler pipeline uses less build CPU and separates the former oversized mixed vendor chunk without increasing total JavaScript output.
Method: five
bun run build-local-testruns each for exact merge-based146e9809and candidateb5e10815con the same Apple Silicon development machine with fixed build environment values. Bundle measurements use emitted production assets. Against the pre-experiment PR commit, experiment opt-ins changed median CPU by +1.2%, within run noise, and left emitted JavaScript size unchanged; no separate experiment-only performance gain is claimed.Summary
fasterModuleConcatenation, which Rspack removed before GA after reverting the underlying optimization.Why
Adopt Rsbuild 2.2's GA compiler, watcher, forward defaults, and bundling improvements while reducing build overhead and the worst emitted chunk size. Application behavior and backend integrations are out of scope.
Surface review skipped: build-only change; no rendered UI changed.
Experiment scope
asyncWebAssembly: true: current Rspack default, pinned explicitly for the supported async Wasm pipeline.futureDefaults: true: opts into the next-major defaults now so incompatibilities fail during deliberate upgrades.nativeWatcher: true: uses Rust file watching for development rebuilds.pureFunctions: isProd: enables cross-module pure-function analysis only where production tree shaking applies.sourceImport: true: enabled by Rsbuild 2.2 and protected by the config contract.buildHttpbecause Console has no remote URL imports and enabling network-backed builds would expand the supply-chain surface.deferImportbecause Console uses no deferred-import proposal syntax.runtimeMode: 'rspack'because it is still under development and targets ESMmodern-moduleoutput, while Console uses Module Federation's current runtime.useInputFileSystembecause Console does not replace Rspack's input filesystem.Commits
8df6382bbbuild(frontend): upgrade rsbuild to 2.2b5e10815cbuild(frontend): enable rspack experimentsReviewer guide
Start with
frontend/rsbuild.config.ts, then review dependency intent infrontend/package.json, the contract infrontend/tests/rsbuild-config.test.ts, and finally the generated lockfiles.Dogfood evidence
bun run start -- --port 57086,bun x rsbuild preview --port 57087, browser routes, and emitted asset/federation HTTP endpoints./overviewand/topics; opened an invalid deep link; exercised missing-backend states; fetched large vendor chunks, workers, and Module Federation artifacts.Dependency upgrade path
@rsbuild/core@2.2.0; React2.1.0; Sass2.0.1; SVGR2.0.5; Tailwind CSS2.0.3; YAML2.0.0; Node polyfill remains latest at1.4.6. Removed@rsbuild/plugin-babel,babel-plugin-react-compiler, andreact-compiler-runtime.2.2.0; the lockfiles resolve@rspack/core@2.2.0. Official plugin upgrades use their current releases.Test plan
bun install --frozen-lockfile --ignore-scriptsbun run type:checkbun run lint:check:file rsbuild.config.ts tests/rsbuild-config.test.tsbun run build-local-testbenchmark runs