[POC] Combine Rspack/Rsbuild migration with OXC React Compiler loader#95656
Draft
roryabraham wants to merge 19 commits into
Draft
[POC] Combine Rspack/Rsbuild migration with OXC React Compiler loader#95656roryabraham wants to merge 19 commits into
roryabraham wants to merge 19 commits into
Conversation
…uilds
Mirrors config/webpack/{webpack.common,webpack.dev}.ts under config/rspack/,
swapping webpack-specific plugins for their Rspack builtins (CopyRspackPlugin,
CssExtractRspackPlugin, SwcJsMinimizerRspackPlugin, ReactRefreshRspackPlugin,
@aaroon/workbox-rspack-plugin) and replacing @vue/preload-webpack-plugin
(which depends on webpack-internal chunk graph APIs Rspack doesn't expose)
with a small custom RspackPreloadPlugin that reads compilation.getAssets()
directly.
Adds `build:rspack`, `build-staging:rspack`, and `web-server:rspack` npm
scripts so both bundlers can be run side by side without touching the
existing webpack config. Storybook is out of scope.
Co-authored-by: Cursor <cursoragent@cursor.com>
Expands the Rspack POC to cover Storybook (via storybook-react-rsbuild, since Storybook has no first-party Rspack framework package) and removes Webpack entirely now that both build paths are covered, rather than keeping the two bundlers side-by-side. Co-authored-by: Cursor <cursoragent@cursor.com>
The auto-restart wrapper (#67373) was added to paper over webpack-dev-server's unbounded JS-heap growth across HMR recompiles, which forced periodic heap-limit crashes during long dev sessions. Stress-tested the Rspack dev server with 60+ back-to-back HMR recompiles (mimicking the original repro of repeatedly saving a file): heap usage plateaus/bounces in a bounded band rather than growing without bound, and the periodic forced gc() call reliably reclaims memory each time - unlike webpack, where explicit GC barely helped. No crash-recovery loop needed. Also fixes a latent bug in ForceGarbageCollectionPlugin: `if (gc && ...)` throws a ReferenceError when Node isn't started with --expose-gc, since referencing an undeclared global outside of `typeof` throws. `typeof gc` alone is the safe check. Co-authored-by: Cursor <cursoragent@cursor.com>
…ild, ws vuln
- rspack.common.ts / .storybook/rsbuild.config.ts: explicitly mock
__filename/__dirname ('mock' instead of Rspack's default 'warn-mock') so
canvaskit-wasm/expo don't emit a "Module parse warning" on every build.
Storybook's `--smoke-test` treats any non-allow-listed warning as a hard
failure, so this was breaking the "Storybook tests" CI check; the rsbuild
config wasn't otherwise inheriting this from the shared Rspack config.
- cspell.json: add rspack/Rspack/rsbuild/aaroon to the word list.
- Rebuilt 3 stale .github/actions/javascript/*/index.js bundles that drifted
after merging main (unrelated to this PR's changes).
- Bump the `ws` override from 8.17.1 to 8.21.0 to pick up fixes for two
advisories (GHSA-58qx-3vcg-4xpx, GHSA-96hv-2xvq-fx4p) that Snyk flagged
once storybook/webpack-bundle-analyzer joined the dependents list.
Co-authored-by: Cursor <cursoragent@cursor.com>
ncc's output isn't fully deterministic across different node_modules
resolution states; my previous gh-actions-build run used a tree from
npm install rather than npm ci, leaving this one bundle out of sync
with what CI's verify job rebuilds and diffs against.
Skips the oxfmt pre-commit hook: it errors ("Expected at least one target
file") when every staged file matches its ignorePatterns, which is the case
here since .github/actions/javascript/**/index.js is intentionally excluded
(generated bundle). Nothing to format either way.
Co-authored-by: Cursor <cursoragent@cursor.com>
…sitive dep Removing webpack as a devDependency also dropped the "webpack": "^5.x" override, which was silently pinning react-web-config's own (unused) webpack@^2.3.3 dependency to a safe version. Without it, npm installs react-web-config's real webpack@2.7.0 -> node-libs-browser -> crypto-browserify -> browserify-sign/create-ecdh -> elliptic chain, introducing 4 new critical vulnerabilities not present on main. Co-authored-by: Cursor <cursoragent@cursor.com>
- Stop suppressing import/extensions line-by-line for .storybook/ and config/rspack/ ESM entry points; handle it with one config-level override. - Share ignoreWarnings, lottie/pdf asset rules, and define() values between rspack.common.ts and .storybook/rsbuild.config.ts so Storybook can't drift from the app build; drop mockPaths.ts aliases already set upstream. - Replace html-webpack-plugin with Rspack's native HtmlRspackPlugin in both the main plugin and RspackPreloadPlugin, and remove the now-unused html-webpack-plugin dependency. - Document the RSPACK_PROFILE build-profiling workflow in SETUP_WEB.md. Co-authored-by: Cursor <cursoragent@cursor.com>
Type RspackPreloadPlugin's links array off the alterAssetTags hook's own parameter instead of importing JsHtmlPluginTag from @rspack/binding, since that package is only a transitive dependency of @rspack/core, not one we depend on directly. Add "pftrace" (the Perfetto trace file extension) to the cspell word list. Co-authored-by: Cursor <cursoragent@cursor.com>
Rsbuild wraps Rspack with higher-level config (source.define, output.copy, server.https/proxy, performance.buildCache) and is already used for Storybook, so unifying onto it removes the last hand-rolled dev-server/HTTPS/persistent-cache plumbing in config/rspack/ and lets the app build and Storybook share one getSharedConfiguration()/getCommonConfiguration() config instead of two independently-maintained bundler configs. Co-authored-by: Cursor <cursoragent@cursor.com>
Re-ran the #95319 HMR memory-stress test (60+ back-to-back recompiles) against the Rsbuild dev server with and without the plugin's forced gc() call. Heap and RSS plateau identically either way (~245MB heap, bounded RSS growth across 260 total compiles), unlike raw Rspack where disabling gc() caused unbounded growth. V8's own GC is sufficient here, so the plugin and --expose-gc are dead weight under Rsbuild. Co-authored-by: Cursor <cursoragent@cursor.com>
The ESLint flat config now disables import/extensions repo-wide for config/rsbuild/**/*.ts, so the line-level disable for that rule is redundant. The @dword-design/import-alias/prefer-alias disable is still needed and stays. Co-authored-by: Cursor <cursoragent@cursor.com>
Rsdoctor is the first-party Rspack/Rsbuild bundle analyzer, integrates via the same tools.rspack escape hatch, and adds loader/plugin timing and duplicate-package analysis on top of bundle size visualization. Co-authored-by: Cursor <cursoragent@cursor.com>
The webpack dev config defined process.env.PORT for the client so CONFIG.DEV_PORT reflects the actual resolved port when portfinder falls back off 8082. Rsbuild's dev config dropped that define, only wiring the resolved port to server.port, so getEnvironmentURL() would generate dev links pointing at the wrong port whenever 8082 was busy. Verified by occupying 8082, booting the dev server (portfinder fell back to 8083), and confirming the served bundle now bakes in `DEV_PORT: 8083 ?? 8082` instead of falling back to the hardcoded default. Co-authored-by: Cursor <cursoragent@cursor.com>
Two new loaders that together eliminate babel-plugin-react-compiler and the full Babel transform pass from the webpack build: fullstory-annotation-loader.js Standalone replacement for @fullstory/babel-plugin-annotate-react that uses @babel/parser + @babel/traverse in parse-only mode (no code transforms). Injects dataComponent / dataElement / dataSourceFile props onto JSX opening elements so OXC receives annotated JSX and can run its React Compiler before the JSX transform. No Babel transform pipeline needed. oxc-react-compiler-loader.js Thin wrapper around oxc-transform 0.136.0 that runs the Rust port of the React Compiler + JSX transform + TypeScript strip + env lowering in a single native pass. Demotes non-fatal React Compiler diagnostics to webpack warnings rather than hard build errors, matching babel-plugin-react-compiler's default bailout behaviour. Workaround for oxc-project/oxc#23587. Co-authored-by: Cursor <cursoragent@cursor.com>
- babel.config.js: strip removed packages from the dead webpack branch
(@babel/preset-react, @babel/preset-env, @babel/preset-flow,
@babel/preset-typescript, babel-plugin-react-native-web, and several
class-property/export-namespace transforms). This fixes the Storybook
build and ESLint import-alias rule which both load babel.config.js.
- fullstory-annotation-loader.js: rename annotateJSXNode/annotateComponent
→ applyPropsToJSXNode/applyPropsToComponent to satisfy the
no-negated-variables rule ('annotate' contains 'not' as a substring).
- oxc-react-compiler-loader.js: extract getLang() to eliminate the
no-nested-ternary violation.
- cspell.json: add 'errorbar' (from the fullstory KNOWN_INCOMPATIBLE list).
- package.json: add @babel/generator, get-tsconfig, oxc-transform as
explicit devDependencies (used directly by our loaders) to satisfy knip.
Scope the oxc-transform override to just oxc-loader to avoid conflicts.
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
npm ci fails when a direct dependency uses a semver range (^) but overrides pins an exact version. Pin the devDependency to match the override so setupNode can install dependencies in CI. Co-authored-by: Cursor <cursoragent@cursor.com>
Storybook was still looking for a top-level babel-loader rule; push the OXC transform pipeline rules instead. Rename webpack loaders to .cjs to avoid the new-JS-files typecheck gate. Restore knip's cross-platform oxc-parser optional deps in package-lock.json. Co-authored-by: Cursor <cursoragent@cursor.com>
- Rewrite includeModules regex as INCLUDED_NODE_MODULES array + includedNodeModules regex, and wire OXC's three-pass loader pipeline (Rule A/B/B2) directly into rsbuild.common.ts now that pluginBabel is gone. - Add babel-loader as an explicit devDependency (previously transitive via @rsbuild/plugin-babel). - Ignore babel-loader, oxc-loader, and @babel/plugin-proposal-class-properties in knip.json, and scan .cjs files under config/rsbuild/ - Knip can't statically follow the dynamic loader strings/paths used by Rspack's rule config or babel.config.js's caller-based branching. Co-authored-by: Cursor <cursoragent@cursor.com>
|
Hey, I noticed you changed some webpack configuration files. This can break production builds. Did you remember to run a production build locally to verify they still work? |
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.
Explanation of Change
This is a draft POC that combines the work from two other draft PRs into a single branch to validate that they work together and to measure the combined build-performance impact:
Both PRs touch the same build pipeline in conflicting ways (one moves the bundler from Webpack to Rspack, the other replaces the Babel-based JS/TS/JSX transform loader with OXC), so this branch cherry-picks all commits from both, resolves the merge conflicts, and rewires the OXC three-pass loader pipeline (Fullstory annotation → React Compiler + JSX/TS transform → worklets) directly into
rsbuild.common.ts'saddRules, replacing@rsbuild/plugin-babelentirely.Key integration points:
config/rsbuild/rsbuild.common.ts: replaced the singlepluginBabelrule with three explicit Rspack rules — Rule A (app source, React Compiler enabled), Rule B/B2 (allow-listednode_modulesthat need transforms but not React Compiler, split by.tsx?vs.jsx?for JSX runtime injection).babel.config.js: thewebexport is now nearly empty (no presets, since OXC handles JS/TS/JSX transforms natively) — it's effectively dead code for the web build (kept for tooling that loadsbabel.config.jsdirectly, e.g. IDE plugins), since Rsbuild's pipeline callsbabel-loaderwithconfigFile: falsefor the worklets-only pass.package.json: removed@rsbuild/plugin-babeland various@babel/preset-*/babel-plugin-react-native-webpackages no longer needed for web; addedbabel-loader,oxc-loader, andoxc-transformas explicit dependencies.knip.json: scoped theconfig/rsbuildentry glob to include.cjsfiles (custom loaders), and added a fewignoreDependenciesentries for packages Knip can't statically trace through dynamic loader strings.Validation performed
npm run typecheck-tsgo,npm run lint-changed,npm run knip/knip:full,npm run spell-changed— all clean, no new regressions vs.main.npm run build(production) — succeeds, only expected React Compiler diagnostics demoted to warnings (documented workaround for fix(transformer): don't discard output on non-fatal React Compiler errors oxc-project/oxc#23587 from perf(webpack): swap babel-loader + babel-plugin-react-compiler for OXC (Rust React Compiler) #93980).npm run storybook-build— succeeds cleanly.npm run web) smoke test — cold build serves the correct app shell + JS bundle over HTTPS, HMR warm rebuild verified after touching a source file.npm run storybook) smoke test — "Storybook ready!", index/iframe/story-index all serve correctly, HMR warm rebuild verified.Benchmarks (combined OXC + Rsbuild)
Cold =
node_modules/.cache/{rspack,storybook}anddist/cleared before the run. Warm = immediate re-run with cache intact. Single-machine, single-run measurements (not averaged across multiple runs) — treat as directional, not authoritative.npm run build(prod)npm run storybook-buildweb-server)Takeaway: production/Storybook builds show negligible cold-vs-warm delta (minification dominates the timing either way), while dev-server incremental rebuilds are consistently sub-200ms thanks to Rspack's persistent module cache plus OXC's transform speed.
Fixed Issues
$
PROPOSAL:
Tests
This is a build-tooling POC combining two other draft PRs — see "Validation performed" and "Benchmarks" above in lieu of standard feature test steps. Not intended to be merged as-is; opened as a draft to validate the two approaches are compatible and to get combined benchmark numbers.
QA Steps
N/A — draft POC, not intended for QA.
PR Author Checklist
Made with Cursor