Skip to content

test: add dual-major Rspack compatibility validation#1404

Open
dannyhw wants to merge 12 commits into
feat/rspack-2-integrationfrom
feat/rspack-2-smoke-suite
Open

test: add dual-major Rspack compatibility validation#1404
dannyhw wants to merge 12 commits into
feat/rspack-2-integrationfrom
feat/rspack-2-smoke-suite

Conversation

@dannyhw

@dannyhw dannyhw commented Jul 6, 2026

Copy link
Copy Markdown
Collaborator

Summary

Combines the final Rspack 2 validation surfaces into one review unit:

This PR stays on the temporary integration base until its implementation prerequisites have merged into dannyhw/rspack-2-support; it will then retarget to the feature branch for final combined verification.

What changed

  • Pack Re.Pack and test it in isolated Rspack 1 and Rspack 2 fixture installs.
  • Guard against accidentally resolving the same Rspack major in both lanes.
  • Verify config routing, cache behavior, Node compatibility, and React Refresh source selection.
  • Make apps/tester-app the Rspack 2 validation surface.
  • Add standalone apps/tester-app-rspack1 with its own dependency graph.
  • Move the workspace catalog to Rspack 2 and normalize true-v2 integration output.
  • Add the dedicated compatibility-smoke CI lane and the headline Rspack 2 changeset.

Detailed tester-app review and device-validation history remains in #1403.

References

Validation

The component branches previously passed:

  • pnpm turbo run typecheck test --force — 17/17 tasks
  • built-dist smoke fixtures under Rspack 1 and Rspack 2
  • integration tests and tester-app bundle tests
  • pnpm lint:ci

The combined branch will run the full validation matrix before it merges into the feature branch.

@vercel

vercel Bot commented Jul 6, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
repack-website Ready Ready Preview, Comment Jul 26, 2026 8:39pm

Request Review

@changeset-bot

changeset-bot Bot commented Jul 6, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 9fe506d

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 6 packages
Name Type
@callstack/repack Minor
@callstack/repack-plugin-expo-modules Minor
@callstack/repack-plugin-nativewind Minor
@callstack/repack-plugin-reanimated Minor
@callstack/repack-dev-server Minor
@callstack/repack-init Minor

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

@dannyhw

dannyhw commented Jul 6, 2026

Copy link
Copy Markdown
Collaborator Author

codex review: The smoke suite does not cover the main Re.Pack require('@rspack/core') path it claims to validate.

In tests/rspack-compat/smoke.cjs, the dev build creates the compiler by requiring the fixture's @rspack/core directly and then using rspack.rspack ?? rspack:

  • tests/rspack-compat/smoke.cjs:151
  • tests/rspack-compat/smoke.cjs:152

The user-facing built command modules use Re.Pack's compiled static imports instead, for example packages/repack/src/commands/rspack/bundle.ts:1, which compiles to require("@rspack/core") and expects _core.rspack to exist. If the Rspack 2 CJS/ESM named-export shape or lazy command loading regresses, this smoke can still pass because it bypasses that Re.Pack command import path.

Please exercise the installed @callstack/repack/commands/rspack lazy command path, or at least require/load dist/commands/rspack/bundle.js and dist/commands/rspack/Compiler.js from the tarball-installed package in both fixtures.

@dannyhw

dannyhw commented Jul 6, 2026

Copy link
Copy Markdown
Collaborator Author

codex review: Repeated local runs can pass against stale installed Re.Pack code.

tests/rspack-compat/run.mjs:75 copies a fresh same-name tarball into each fixture, then tests/rspack-compat/run.mjs:81 runs pnpm install --no-frozen-lockfile. The fixture node_modules, lockfiles, tarball, and dist output are ignored and can persist between runs via tests/rspack-compat/.gitignore:4.

I verified pnpm behavior separately: replacing a same-name/same-version local file:./pkg.tgz and rerunning pnpm install --no-frozen-lockfile left the previously installed package in place. In this repo, a second full smoke run printed Already up to date for both fixtures. That undermines the runner's “fresh built dist” guarantee for local development and can hide regressions until CI happens to run from a clean checkout.

Please delete each fixture's install state before install, force reinstall, or otherwise make the tarball reference content-unique per run.

@dannyhw

dannyhw commented Jul 6, 2026

Copy link
Copy Markdown
Collaborator Author

codex review: The legacy cache warning assertion is testing the wrong layer and accepts the wrong Rspack 1 behavior.

tests/rspack-compat/smoke.cjs:123 calls warnLegacyRspackCacheConfig directly for both fixture majors, and tests/rspack-compat/smoke.cjs:130 expects the warning to fire. The design intent is specifically a Rspack 2 migration warning, and the actual command paths gate it behind isRspack2(...) in packages/repack/src/commands/rspack/bundle.ts:42 and packages/repack/src/commands/rspack/start.ts:64.

As written, the smoke would not catch a regression where Rspack 1 users start seeing the Rspack 2 cache warning; it currently normalizes that behavior. Please assert this through the command-level path, or split expectations so Rspack 1 verifies no warning while Rspack 2 verifies one warning and no mutation.

@dannyhw

dannyhw commented Jul 6, 2026

Copy link
Copy Markdown
Collaborator Author

Addressed the review points in ebbc2d3; one is rebutted below with repro evidence.

1. "The smoke suite does not cover the main Re.Pack require('@rspack/core') path it claims to validate."

Correct — the dev build went through fixtureRequire('@rspack/core') with a rspack.rspack ?? rspack fallback, so the compiled commands' eager require("@rspack/core") / _core.rspack path (the require(esm) surface under Rspack 2) was bypassed. Fixed in ebbc2d3:

  • New section 4 loads dist/commands/rspack/{bundle,start,Compiler}.js from the tarball-installed package in both fixtures (these compile to an eager require("@rspack/core")), loads the lazy @callstack/repack/commands/rspack entry and asserts it exposes the bundle/start descriptors, and asserts typeof rspack.rspack === 'function' on the copy repack itself resolves.
  • The dev-build compiler is now created through that same rspack named export, with the ?? fallback removed so a missing export can no longer be masked.

Sabotage-verified: rewriting the installed bundle.js to require a bogus specifier flips 4b to FAIL (MODULE_NOT_FOUND).

2. "Repeated local runs can pass against stale installed Re.Pack code."

This does not reproduce with the toolchain in use (pnpm 10.x). Two checks:

  • Minimal repro: install file:./pkg.tgz (name/version fixed), replace the tarball with different content, rerun pnpm install --no-frozen-lockfile — pnpm re-resolves the tarball (it hashes file: tarballs during resolution on every install), reports downloaded 1, added 1, and require() returns the new content.
  • End-to-end in this suite: appended a marker export to packages/repack/src/commands/rspack/ensureNodeCompat.ts, reran run.mjs rspack-1 — pnpm reinstalled the changed tarball and the marker is present in fixtures/rspack-1/node_modules/@callstack/repack/dist/.

Already up to date on a second run means the freshly packed tarball is byte-identical to the previous one (pnpm pack normalizes mtimes, and the dist didn't change) — in that case the installed copy already equals the fresh build, so skipping the reinstall is correct, not stale. If dist changes, the tarball integrity changes and pnpm reinstalls.

3. "The legacy cache warning assertion is testing the wrong layer and accepts the wrong Rspack 1 behavior."

Agreed — calling the helper directly under both majors normalized "Rspack 1 warns" as passing behavior. Fixed in ebbc2d3 using the split option: section 2 now loads isRspack2 from the installed dist, asserts it reports the fixture's actual installed major (a real dual-major signal — it exercises the require.resolve paths logic against the fixture layout), and mirrors the command wiring: under Rspack 1 the gate must be false and zero warnings are observed; under Rspack 2 the gate must be true and the helper warns exactly once while mutating nothing. Sabotage-verified: patching the installed rspackVersion.js to report 2.0.0 in the rspack-1 fixture fails both 2b (gate misdetects) and 2c (warning fired under Rspack 1). Driving the assertion through an actual bundle command invocation would require the full config/loader chain in the fixtures (out of scope for wiring-level smoke per the suite README); the composed gate + helper coverage plus loading bundle.js/start.js from point 1 covers the regression described.

Validation: full suite green on both fixtures (15 assertions each), pnpm turbo run typecheck --force and pnpm lint:ci clean.

@dannyhw

dannyhw commented Jul 6, 2026

Copy link
Copy Markdown
Collaborator Author

codex review: Update after re-checking ebbc2d3.

  • The compiled command require-path finding is resolved. The smoke now loads @callstack/repack/commands/rspack, loads dist/commands/rspack/{bundle,start,Compiler}.js from the tarball-installed package, asserts the rspack named export shape, and creates the dev-build compiler through rspack.rspack without the previous fallback.
  • The legacy cache warning finding is resolved. The smoke now composes isRspack2(FIXTURE) with warnLegacyRspackCacheConfig, expecting zero warnings under Rspack 1 and exactly one warning under Rspack 2 while preserving the no-mutation check.
  • I withdraw the stale local tarball-install finding for the repo/CI toolchain. My earlier repro used the global pnpm 11.7.0 in this environment; with corepack pnpm 10.32.1, matching the repository packageManager and CI setup, replacing a same-name/same-version file:./pkg.tgz re-resolves and installs the changed tarball correctly.

There are no inline review threads to resolve.

dannyhw added 8 commits July 6, 2026 21:31
Formalize the built-dist verification harness from the Rspack 2 support
effort into a committed suite at tests/rspack-compat. The suite builds and
packs @callstack/repack, installs the tarball into two standalone fixtures
(one per @rspack/core major) and asserts the dual-major behavior matrix
against the real dist: config routing, warn-only legacy cache handling,
the Node compatibility guard, and a full dev build with HMR + React
Refresh wired from the correct source per major.

The fixtures live outside the pnpm workspace (own workspace roots,
tarball-installed repack) because in-workspace repack's @rspack/core@^2
devDependency shadows any v1 pin - a lane guard aborts the run if the
resolved major or install layout ever stops matching the fixture's intent.

Also adds a dedicated CI lane for the suite and the headline minor
changeset for Rspack 2 support.
…warning assertions by major

- Load dist/commands/rspack/{bundle,start,Compiler}.js and the lazy
  @callstack/repack/commands/rspack entry from the tarball-installed
  package, and assert the rspack named export shape - this exercises the
  eager require('@rspack/core') the compiled commands ship with
  (require(esm) under Rspack 2), which the DevelopmentPlugin build alone
  bypassed.
- Create the dev-build compiler through the same rspack named export the
  compiled call sites use, dropping the '?? rspack' fallback that could
  mask a missing export.
- Compose the legacy-cache warning assertions with the isRspack2 gate the
  commands use: assert the gate reads the fixture's installed major,
  Rspack 1 sees no warning, Rspack 2 warns exactly once and mutates
  nothing.
- apps/tester-app becomes the Rspack 2 example: the manifest now pins the
  workspace default catalog and declares @rspack/plugin-react-refresh plus
  a direct react-refresh, matching what in-workspace apps actually run
- apps/tester-app-rspack1 is a standalone Rspack 1 lab outside the pnpm
  workspace (negation glob + own workspace root), installing repack from a
  packed tarball so resolution behaves like a real user project
- flip the workspace default catalog to @rspack/core@^2.1.2,
  @swc/helpers@^0.5.23 and @rspack/plugin-react-refresh@^2.0.2; both
  federation apps declare the refresh plugin like any real v2 app
- adapt tests/integration NativeEntryPlugin coverage to Rspack 2 output
  formats (version-agnostic runtime markers, shorthand module factories,
  relaxed runtime-module order assertion) and harden bundle normalization
  against ANSI codes and URL-encoded machine paths; regenerate snapshots
@rspack/plugin-react-refresh has a non-optional react-refresh peer; with
autoInstallPeers disabled these manifests relied on another workspace
importer to satisfy it. Match tester-app so each app is self-contained.
Containment alone would not catch a regression that moves the embed
runtime after __webpack_require__.x() is invoked. Assert each runtime
chain against the startup call independently so the polyfill-vs-embed
relative order stays flexible across Rspack majors.
The app's lockfile is intentionally untracked (it pins the locally
packed tarball, which changes on every pack-repack), so a frozen
install can never succeed. Set frozen-lockfile=false in a local .npmrc
and document it, so plain pnpm install behaves the same everywhere.
The delegated ModuleFederationPluginV1 does not use
@module-federation/runtime-tools under either rspack major; only the
enhanced MF 1.5 plugin resolves it and that plugin raises its own
install error. See the evidence on #1400 (closed).
…e locations

The cache accessor was renamed and now collects both the legacy and
top-level locations (see the fix on the types/test-infra branch); the
smoke assertions follow, plus a regression guard that both locations are
collected when both keys are set.
// fixture's actual install, then mirror the command wiring: Rspack 1
// users must see no warning, Rspack 2 users exactly one - and the config
// is never mutated.
const gated = isRspack2(FIXTURE);

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

codex review: This still tests the cache-warning command behavior by composing helpers directly instead of exercising the user-facing command path. If bundle.ts or start.ts stopped calling warnLegacyRspackCacheConfig behind the isRspack2(cliConfig.root) gate, this smoke would remain green because it calls isRspack2(FIXTURE) and warnLegacyRspackCacheConfig([legacyCfg]) itself. The same pattern applies to the lazy command entry below: the suite checks descriptors and module loadability, but never invokes the installed command func wrappers. Please drive at least one minimal bundle/start command invocation through @callstack/repack/commands/rspack, or add an assertion that fails if the lazy wrappers stop calling the guard and the commands stop calling the warning helper.

);

try {
run('pnpm', ['install', '--no-frozen-lockfile'], { cwd: fixtureDir });

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

codex review: This runner shells out to the first pnpm on PATH, so local runs are not guaranteed to use the repository package manager version. In this worktree, running the documented command through corepack pnpm 10.32.1 still made these child installs run with global pnpm 11.7.0; under that pnpm, replacing a same-name/same-version file:./pkg.tgz and rerunning pnpm install --no-frozen-lockfile left the old installed package in place. Because this suite copies a fresh tarball to the same callstack-repack.tgz path on every run, the stale-tarball false positive can still happen outside CI. Please invoke the same package manager executable that launched the script, force reinstall/delete fixture install state, or make the tarball reference content-unique per run.

const { ensureNodeSupportsRspack } = repackRequire(
path.join(REPACK, 'dist/commands/rspack/ensureNodeCompat.js')
);
try {

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

codex review: This only proves the guard does not throw on the current supported Node version. It would still pass if ensureNodeSupportsRspack() became a no-op, if the Rspack 2 semver range regressed, or if the lazy command wrappers stopped calling the guard before loading @rspack/core. Since the changeset promises a clear error below ^20.19.0 || >=22.12.0, please add a negative check for an unsupported Rspack 2 Node version, for example by spawning the installed dist with process.versions.node overridden to 20.18.0/22.11.0, while also asserting Rspack 1 remains allowed.

# Marks this directory as its own workspace root, so pnpm treats it as a
# standalone project instead of a member of the repository workspace.
# See ../../README.md for why the fixtures must stay outside the workspace.
packages: []

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

codex review: These standalone fixture workspaces do not carry over the repo’s autoInstallPeers: false policy, so generated fixture installs default to autoInstallPeers: true. That makes the smoke less strict than this repo’s package-manager policy and can mask accidental imports of optional peers: in my run, Re.Pack resolved auto-installed peers like webpack / @babel/core from the fixture virtual store even though the fixture manifests do not declare them. The current smoke still passes if I reinstall with --config.auto-install-peers=false, so please set that policy in both fixture pnpm-workspace.yaml files or pass it during install to keep future regressions from being hidden.

@dannyhw dannyhw changed the title test: add dual-major rspack compatibility smoke suite test: add dual-major Rspack compatibility validation Jul 26, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant