feat: restructure react refresh support for rspack 2#1402
Conversation
DevelopmentPlugin now splits on the detected rspack major: - rspack >= 2 applies the official @rspack/plugin-react-refresh (v2) with injectEntry: false (Re.Pack controls entry placement per entrypoint), forceEnable: true, and Re.Pack's own react-refresh-loader. The package is ESM-only, so it is loaded lazily inside the rspack-2 branch and resolved from the project context with an actionable error when missing. - webpack and rspack 1 keep the existing manual wiring, now pointed at client runtime files vendored from @rspack/plugin-react-refresh@2.0.2 (MIT) into the package-root vendor/react-refresh/ directory, shipped as-is via package.json#files and excluded from biome. The @rspack/plugin-react-refresh@1.0.0 dependency is removed (its module-load-time deprecated_runtimePaths access crashes under the v2 plugin); the v2 plugin becomes an optional peer dependency instead, so rspack 2 users install it (plus react-refresh) in their project.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
🦋 Changeset detectedLatest commit: 05b9027 The changes in this PR will be included in the next version bump. This PR includes changesets to release 6 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 |
…oader rule The manual React Refresh rule only excluded node_modules, so in workspace (symlinked) installs the vendored runtime files under packages/repack/vendor/react-refresh matched the rule and were processed by the react-refresh loader themselves. Mirror the upstream plugin's self-exclusion by also excluding the three vendored file paths. The official plugin path (Rspack 2) already appends these exclusions internally.
dannyhw
left a comment
There was a problem hiding this comment.
codex review: Posting the actionable findings from the latest re-review.
| // where require() of an ESM module is supported | ||
| const { ReactRefreshRspackPlugin } = require(pluginPath); | ||
|
|
||
| new ReactRefreshRspackPlugin({ |
There was a problem hiding this comment.
codex review: Superseded by the corrected reply below; the first version lost inline identifiers during shell quoting.
There was a problem hiding this comment.
codex review: Correction to the previous comment: the stripped identifiers were ReactRefreshRspackPlugin and @rspack/plugin-react-refresh. The requested coverage should prove that the Rspack 2 branch applies ReactRefreshRspackPlugin with Re.Pack's loader and injected entry path; webpack/Rspack 1 use the vendored runtime without evaluating @rspack/plugin-react-refresh; and Rspack 2 reports the intended actionable error when that optional peer is absent.
| "peerDependencies": { | ||
| "@module-federation/enhanced": ">=0.6.10", | ||
| "@rspack/core": ">=1", | ||
| "@rspack/plugin-react-refresh": "^2.0.0", |
There was a problem hiding this comment.
codex review: Superseded by the corrected reply below; the first version lost the package name during shell quoting.
There was a problem hiding this comment.
codex review: Correction to the previous comment: the stripped package name was @rspack/plugin-react-refresh. This PR makes @rspack/plugin-react-refresh an optional peer, but the current latest docs still say the plugin is shipped with Re.Pack and configured automatically. Please update the user-facing docs/install guidance in this PR or in the same stack so the package contract and docs agree.
Part of the Rspack 2 support stack (reference implementation: #1393, design doc:
agent_context/rspackv2-jul2026/design.md).Restructures React Refresh so
DevelopmentPluginworks under both Rspack majors and webpack. This is the only PR in the stack that touchespackages/repack/package.json's dependency surface.Design: split on the rspack major
@rspack/plugin-react-refresh@1cannot stay: its module-load-timedeprecated_runtimePathsaccess hard-crashes when v2 of the plugin is installed, and depending on v1 while needing^2as a peer is unresolvable (same package name). Per the design doc's React Refresh decision (maintainer-validated in the #1393 review — vendoring the client files is fine,deprecated_runtimePathswas only ever a convenience):@rspack/plugin-react-refreshv2 with its integrator options —injectEntry: false(Re.Pack controls entry placement per entrypoint),forceEnable: true(DevelopmentPlugin already gates ondevServer.hot), andreactRefreshLoaderpointed at Re.Pack's React Native-awarereact-refresh-loader. The package is ESM-only (named export only), so it isrequire()d lazily inside the rspack-2 branch — it is never evaluated for webpack/Rspack 1 users — and resolved from the project context with an actionable error when missing.@rspack/plugin-react-refresh@2.0.2(MIT) instead of the removed v1 dependency.The behavioral fork is a single ternary on
getRspackMajorVersionFromCompiler(compiler); everything else is extraction. A per-bundler plugin split (floated in the #1393 review) is not warranted for one ~10-line fork.Vendor directory placement (review feedback)
The vendored files live at package-root
packages/repack/vendor/react-refresh/— outsidesrc/, per the review ask. They are plain JS client-runtime modules bundled by the user's compiler, so they ship as-is viapackage.json#files(no babel processing) and are excluded from biome.LICENSEin the directory carries the upstream MIT license plus a provenance note: the files are verbatim upstream v2.0.2 apart from provenance headers and repo formatting (no functional edits; re-derivation instructions for future bumps included).Footprint check (review feedback: minimize the DevelopmentPlugin diff)
Verified with
git diff --color-moved=dimmed-zebra --color-moved-ws=allow-indentation-change origin/main...HEAD -- packages/repack/src/plugins/DevelopmentPlugin.ts: the manual wiring (setupManualReactRefresh) registers as a move of the old inline block (193 moved lines). The only non-moved lines inside it are the three intentional deltas:ReactRefreshPlugin.deprecated_runtimePathsdestructure →require.resolve('../../vendor/react-refresh/*.js')(resolves identically fromsrc/pluginsanddist/plugins).__react_refresh_error_overlay__: false+__react_refresh_socket__: false→__reload_on_runtime_errors__: false— the v2.0.2 client files dropped the overlay/socket knobs and read the new flag instead. Value semantics unchanged: all web-oriented behaviors stay off; RN surfaces errors via LogBox.include:→test:with the same regex (both match the resource path here;testis the semantically correct key).Two non-substantive accompaniments, listed for completeness rather than hidden: the loader-rule regexes are hoisted into module constants (
REACT_REFRESH_LOADER_TEST/REACT_REFRESH_LOADER_EXCLUDE, identical literal values) because both branches now share them, and comments were updated around the moved block. Everything else in the file diff is genuinely new code for the rspack-2 branch (resolveReactRefreshPluginRequest,setupOfficialReactRefresh).Dependency changes
@rspack/plugin-react-refresh1.0.0^2.0.0@rspack/plugin-react-refresh(devDep)^2.0.2package.json#files"vendor"What users must do:
@rspack/plugin-react-refresh@^2andreact-refresh(pnpm-strict layouts don't hoist Re.Pack'sreact-refreshcopy into the official plugin's resolution scope).ProvidePlugin×2 +DefinePlugin+ loader rule + entry), same values modulo the enumerated define-flag rename that the v2.0.2 client files require.Stack position
Parallel set, based on #1398 (types + dual-major test infra) ← #1397 (Node guard) ← #1394 (version helpers). No shared files with the other parallel-set PRs (#1399, #1400, #1401) apart from uniquely-named changesets; this is the only one touching
packages/repack/package.jsonand the lockfile.Validation
pnpm turbo run typecheck test --force: 17/17 tasks green (includes tester-app bundle/start tests, which exercise the dev server under both the rspack and webpack lanes).packages/repack:pnpm test(v2 lane) andpnpm test:rspack1(v1 lane) — 282/282 each.pnpm lint:ci: clean (vendored files excluded as intended).tester-appwithUSE_WEBPACK=1and fetched the served dev bundle — it contains all three vendored runtime modules (vendor/react-refresh/reactRefresh.js×90 refs,reactRefreshEntry.js×3,refreshUtils.js×3) and zero references to@rspack/plugin-react-refresh.@rspack/plugin-react-refresh@2.0.2client/files from the registry: identical apart from provenance headers and formatting (no functional deltas).