Skip to content

feat: add rspack 2 types and dual-major test infrastructure#1398

Open
dannyhw wants to merge 4 commits into
feat/rspack-2-node-guardfrom
feat/rspack-2-types-test-infra
Open

feat: add rspack 2 types and dual-major test infrastructure#1398
dannyhw wants to merge 4 commits into
feat/rspack-2-node-guardfrom
feat/rspack-2-types-test-infra

Conversation

@dannyhw

@dannyhw dannyhw commented Jul 6, 2026

Copy link
Copy Markdown
Collaborator

Summary

Re.Pack builds and tests green against Rspack 2, with zero runtime behavior change for Rspack 1 / webpack users. This PR bumps @callstack/repack's @rspack/core devDependency to v2 for types and tests (the workspace catalog stays on v1), absorbs the type-level fallout, and adds the dual-major test infrastructure that every later PR in the stack relies on.

Stack position: PR 2c, stacked on PR 2b #1397 (feat/rspack-2-node-guard) ← #1394main. Reference implementation: #1393. Design doc: agent_context/rspackv2-jul2026/design.md.

What's inside

Rspack 2 devDependency + type fallout (no runtime change)

  • @rspack/core@^2.1.2 and @swc/helpers@^0.5.23 as packages/repack devDependencies — source now compiles against v2 types while remaining runtime-compatible with both majors.
  • SwcLoaderOptions became a union discriminated on detectSyntax in v2 — a local non-union SwcConfig alias in loaders/babelSwcLoader/options.ts keeps the spread-and-override option helpers working. builtin:swc-loader-only keys (rspackExperiments, transformImport, collectTypeScriptInfo, detectSyntax) are destructured off before calling the raw SWC transformSync (also more correct at runtime).
  • bundle.ts rest-destructures devServer off the config instead of delete — it isn't needed for bundling and is not assignable to v2's bundled DevServer type.
  • One documented cast (calling it out per our no-casts agreement): in start.ts, Re.Pack's devServer type augmentation and Rspack 2's bundled DevServer type are incompatible solely because each pulls proxy types from a different copy of http-proxy-middleware. devServer can't be stripped there — the dev-server flow reads it back from compiler.options. The cast carries its full reasoning in a comment; the genuine fix (aligning @callstack/repack-dev-server's proxy types with rspack's bundled ones) is possible future work.
  • RepackTargetPlugin narrows devServer before reading .hot (the key can be false in v2 types).
  • getRspackCacheConfig accessor in resetPersistentCache.ts reads the persistent cache config from both the Rspack 1 (experiments.cache) and Rspack 2 (top-level cache) locations, with derived cache types extended for the legacy location. Behavior-neutral — the user-facing warning for the legacy location lands in a later stack PR.

Dual-major Jest lane

@rspack/core v2 is ESM-only, which Jest's sandboxed CJS runtime cannot load (createRequire inside the sandbox is wrapped by Jest and loops back into the module registry). Tests now run through a custom test environment (jest.environment.js) that loads the real core outside the sandbox with Node's real module system and bridges it back via moduleNameMapper (jest.rspack-core-bridge.js, __esModule-marked so Babel import interop keeps named imports working).

The environment is parameterized on RSPACK_MAJOR (default 2): v2 via await import('@rspack/core'), v1 via plain require of the aliased "@rspack/core-v1": "npm:@rspack/core@^1.7.12" devDependency (CJS, so no reliance on cjs-module-lexer named-export synthesis). It exposes __RSPACK_MAJOR__ for major-gated assertions, and a permanent lane-guard test (src/__tests__/rspackTestLane.test.ts) asserts the loaded core's major matches the requested lane, so a silently-unwired lane can't pass. New script: test:rspack1 (cross-env RSPACK_MAJOR=1 jest).

CI lanes

  • Tests [Rspack 1]: unit suite under Rspack 1 on every PR (pnpm turbo run test:rspack1).
  • Tests [Windows]: windows-latest lane running the repack unit suite under both majors — the require(esm) loading path of @rspack/core@2 is Windows-sensitive.
  • Main-branch Node matrix: the Node 18/20 lanes run with RSPACK_MAJOR=1 — repack's Jest environment defaults to the v2 devDep, and Rspack 2 requires Node ^20.19.0 || >=22.12.0.
  • Added permissions: contents: read to both touched workflows (flagged by code scanning on feat: add rspack version detection helpers #1394).

Pre-existing bug fix: resolveProjectPath on Windows (found by the Windows lane)

Standing up the Windows CI lane surfaced a real pre-existing product bug: resolveProjectPath composed its result via string replacement + path.join, and when the [projectRoot^N] up-level navigation reached the filesystem root, the bare-root-plus-concatenation produced a double-separator prefix that Windows resolves as a UNC path (e.g. [projectRoot^2]/shared/utils.js with rootDir /project/root resolved to \\shared\utils.js\). Fixed at the root cause by resolving segment-by-segment with path.resolve, with a regression test for the root-collapse case; existing test expectations are made platform-agnostic (resolved paths carry a drive letter and win32 separators on Windows). Split into its own commit. HermesBytecodePlugin test expectations got the same platform-agnostic treatment (test-only).

Design decision: plain import, no project-context resolution

@rspack/core is loaded with a plain import — shipped code carries no project-context resolution or workspace-aware workarounds. The consequence is monorepo-only: in-workspace apps resolve repack's own devDep (v2) instead of their manifest's pin. That is accepted and handled at the fixture level (standalone, tarball-installed test apps in a later stack PR) rather than in published code — see the design doc for the reasoning.

Same consequence for the v1 unit lane's honest scope: version-detection paths still resolve repack's own v2 devDep in-workspace, so the v1 lane exercises the real v1 core object and compilation surface, not version-routing logic.

Validation

All run at the repo root unless noted:

Command Result
pnpm turbo run typecheck test --force 17/17 tasks green
pnpm test (in packages/repack) 282 passed, 30 suites — Rspack 2.1.2, incl. lane guard
pnpm test:rspack1 (in packages/repack) 282 passed, 30 suites — Rspack 1.7.12, incl. lane guard
pnpm lint:ci clean

Changeset

Intentionally no changeset: this PR is behavior-neutral for users (devDeps, types, test/CI infrastructure). The user-visible Node-version error message ships with PR 2b (#1397) and carries the changeset there.

dannyhw added 2 commits July 6, 2026 19:20
Bump @callstack/repack's @rspack/core devDependency to ^2.1.2 (the
workspace catalog stays on v1) and absorb the type-level fallout with
zero runtime behavior change for Rspack 1 / webpack users:

- SwcLoaderOptions became a union discriminated on detectSyntax in v2 -
  introduce a local non-union SwcConfig alias and split builtin:swc-loader
  only options off before calling the raw swc transformSync (also more
  correct at runtime)
- narrow devServer before reading .hot in RepackTargetPlugin (the key can
  be false in v2 types)
- rest-destructure devServer off the config in the bundle command instead
  of delete (not assignable to v2's bundled DevServer type, and unused
  for bundling)
- one documented cast in the start command: Re.Pack's devServer type
  augmentation and Rspack 2's bundled DevServer type are incompatible
  solely because each pulls proxy types from a different copy of
  http-proxy-middleware, and devServer cannot be stripped there because
  the dev server flow reads it back from compiler.options
- getRspackCacheConfig accessor reading the persistent cache config from
  both the Rspack 1 (experiments.cache) and Rspack 2 (top-level cache)
  locations, with derived cache types extended for the legacy location

@rspack/core v2 is ESM-only which Jest's sandboxed CJS runtime cannot
load, so tests now run through a custom test environment that loads the
real core outside the sandbox and bridges it back via moduleNameMapper.
The environment is parameterized on RSPACK_MAJOR (v2 via import(), v1
via require of the aliased @rspack/core-v1 devDependency), exposes
__RSPACK_MAJOR__ for major-gated assertions, and a permanent lane-guard
test asserts the loaded major matches the requested lane.

CI gains a Rspack 1 unit-suite lane, a Windows lane running the suite
under both majors (the require(esm) loading path is Windows-sensitive),
and the main-branch Node 18/20 matrix lanes run with RSPACK_MAJOR=1
since Rspack 2 requires Node ^20.19.0 || >=22.12.0. HermesBytecodePlugin
test expectations are made platform-agnostic for the Windows lane.
…ndows

resolveProjectPath composed the result via string replacement plus
path.join: joining enough ../ segments onto rootDir to reach the
filesystem root yields a bare root ending in a separator, and
concatenating the rest of the path after it produced a double-separator
prefix that Windows path resolution interprets as a UNC path (e.g.
[projectRoot^2]/shared/utils.js with rootDir /project/root resolved to
\\shared\utils.js\ instead of \shared\utils.js).

Resolve segment-by-segment with path.resolve instead, add a regression
test for the root-collapse case, and make the existing expectations
platform-agnostic (resolved paths carry a drive letter and win32
separators on Windows).
@changeset-bot

changeset-bot Bot commented Jul 6, 2026

Copy link
Copy Markdown

⚠️ No Changeset found

Latest commit: 18e54f4

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@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 7, 2026 10:10pm

Request Review

Comment thread packages/repack/src/commands/common/resetPersistentCache.ts Outdated
getRspackCacheConfig preferred experiments.cache over the top-level
cache option, but a merged or dual-major config can carry both keys
(e.g. the experimental cache defaults set experiments.cache while a
user config sets top-level cache). Which location is active depends on
the installed Rspack major, so --reset-cache now collects both
locations instead of picking one.

@dannyhw dannyhw left a comment

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: posting two actionable findings from the re-review.

Comment thread packages/repack/src/commands/common/resolveProjectPath.ts Outdated
Comment thread packages/repack/src/loaders/babelSwcLoader/babelSwcLoader.ts
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