You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Re.Pack ships two parallel command trees — packages/repack/src/commands/rspack/ and packages/repack/src/commands/webpack/ — each with its own start, bundle, Compiler and command registry, exposed as separate entrypoints (@callstack/repack/commands/rspack and @callstack/repack/commands/webpack). The config pipeline is already shared in commands/common/, and plugins/loaders are shared with a few isRspackCompiler() branches. The commands are the last unshared piece — so features land on one side only, and behaviour has drifted in user-visible ways.
Part of #1414 (Roadmap to V6), which lists "unify webpack and rspack start/bundle commands".
Duplication
rspack/index.ts and webpack/index.ts are byte-identical. Both also register webpack-bundle / webpack-start aliases, so the rspack entrypoint installs commands named webpack-*.
bundle.ts and start.ts are copy-paste apart from the bundler literals, experiments.cache vs cache, the Rspack-only setupRspackEnvironment / RSPACK_PROFILE blocks, and the compiler/dev-server wiring (see below).
CompilerAsset is defined in both rspack/types.ts and webpack/types.ts.
defineRspackConfig / defineWebpackConfig are the same identity function with parallel overload chains and type families.
templates/rspack.config.mjs and templates/webpack.config.mjs differ only in doc comments, the define*Config call, and parallel: true on the minimizer (same for the .cjs pair). packages/init downloads one or the other from the repo at init time (createBundlerConfig.ts).
Behavioural divergence
The real cost — the two commands behave differently:
Execution model. Rspack: one in-process MultiCompiler, watching all platforms eagerly from compiler.start(). Webpack: a worker_threads worker spawned lazily per platform on the first asset request. Most of the rest follows from this.
api.getPlatforms() returns all configured platforms on Rspack, but Object.keys(compiler.workers) on webpack — an empty list until a platform's first bundle request.
getSource: the Rspack compiler handles absolute paths (path.isAbsolute check); the webpack one unconditionally joins with rootDir, breaking symbolication for absolute frame paths.
Webpack builds the config twice:start.ts calls makeCompilerConfig (for devServer and cache paths), then each worker calls it again in CompilerWorker.ts.
isCompilationInProgress is one global boolean on Rspack, a per-platform record on webpack, with slightly different "not found in compilation assets" errors.
Progress/build-time reporting flows through ProgressPlugin + the done hook on Rspack, vs worker messages on webpack (which drops reporter progress above 99% in favour of the done message).
watchOptions: Rspack applies compilers[0].options.watchOptions to the whole MultiCompiler (per-platform values are ignored); each webpack worker uses its own config's.
adb reverse runs from the MultiCompilerwatchRun hook on Rspack, and from the watchRun delegate handler in start.ts on webpack.
Config discovery:DEFAULT_RSPACK_CONFIG_LOCATIONS has 6 entries; DEFAULT_WEBPACK_CONFIG_LOCATIONS has 10 (including .webpack/ variants).
Proposed direction
Collapse the two trees into one commands/ implementation parameterised by bundler: 'rspack' | 'webpack', with a small adapter for the compiler factory, ProgressPlugin, cache accessor and profiling. The config layer already threads this parameter everywhere.
Pick one execution model for the dev server and share the Compiler:
in-process MultiCompiler for both — simplest, but webpack's CPU-bound compilation for multiple platforms then shares a single thread;
worker-per-platform for both — keeps parallelism, but adds IPC and asset copying for Rspack, which is natively parallel already;
one Compiler with a pluggable execution strategy — in-process for Rspack, workers for webpack.
Either way, the dev-server delegate, asset cache, progress and pending-resolver logic should exist once.
Single defineConfig implementation; keep defineRspackConfig / defineWebpackConfig as aliases.
One @callstack/repack/commands entrypoint that detects the bundler, keeping the per-bundler entrypoints as explicit overrides.
Fix the divergences above as part of the merge, rather than porting them across.
Scope
Bundler adapter; merge bundle.ts and start.ts into single implementations with one dev-server delegate
Unify Compiler: decide the execution model, share asset cache / progress / resolver logic, single CompilerAsset
Stop rebuilding the config inside CompilerWorker (pass the resolved config, or drop the worker path)
Fix getSource absolute-path handling and api.getPlatforms()
Merge the defineConfig implementations and the command registries; decide whether the webpack-* aliases survive v6
Bundler-agnostic @callstack/repack/commands entrypoint with detection
Decide --max-workers semantics for webpack; unify config file discovery
Collapse the templates/ pairs and update packages/init (createBundlerConfig.ts, modifyReactNativeConfig.ts)
Keep the describe.each bundler matrix in apps/tester-app/__tests__ green through the merge; add coverage for the divergences above
Docs: write getting-started/bundlers.md ([Docs] Getting Started: Rspack & webpack #917) against the unified surface, update the migration guides that reference commands/<bundler>, note the entrypoint change in the v5 → v6 guide
Open questions
Which execution model wins? This is the one decision that shapes the rest of the work.
Does v6 keep webpack fully co-equal, or best-effort with Rspack as the default? (Roadmap to V6 #1414 targets Rspack 2+.) The answer decides whether the feature gaps get closed or documented.
Is a single repack.config.* filename worth the migration churn, given both existing names are established?
Motivation
Re.Pack ships two parallel command trees —
packages/repack/src/commands/rspack/andpackages/repack/src/commands/webpack/— each with its ownstart,bundle,Compilerand command registry, exposed as separate entrypoints (@callstack/repack/commands/rspackand@callstack/repack/commands/webpack). The config pipeline is already shared incommands/common/, and plugins/loaders are shared with a fewisRspackCompiler()branches. The commands are the last unshared piece — so features land on one side only, and behaviour has drifted in user-visible ways.Part of #1414 (Roadmap to V6), which lists "unify webpack and rspack start/bundle commands".
Duplication
rspack/index.tsandwebpack/index.tsare byte-identical. Both also registerwebpack-bundle/webpack-startaliases, so the rspack entrypoint installs commands namedwebpack-*.bundle.tsandstart.tsare copy-paste apart from the bundler literals,experiments.cachevscache, the Rspack-onlysetupRspackEnvironment/RSPACK_PROFILEblocks, and the compiler/dev-server wiring (see below).CompilerAssetis defined in bothrspack/types.tsandwebpack/types.ts.defineRspackConfig/defineWebpackConfigare the same identity function with parallel overload chains and type families.templates/rspack.config.mjsandtemplates/webpack.config.mjsdiffer only in doc comments, thedefine*Configcall, andparallel: trueon the minimizer (same for the.cjspair).packages/initdownloads one or the other from the repo at init time (createBundlerConfig.ts).Behavioural divergence
The real cost — the two commands behave differently:
MultiCompiler, watching all platforms eagerly fromcompiler.start(). Webpack: aworker_threadsworker spawned lazily per platform on the first asset request. Most of the rest follows from this.api.getPlatforms()returns all configured platforms on Rspack, butObject.keys(compiler.workers)on webpack — an empty list until a platform's first bundle request.getSource: the Rspack compiler handles absolute paths (path.isAbsolutecheck); the webpack one unconditionally joins withrootDir, breaking symbolication for absolute frame paths.start.tscallsmakeCompilerConfig(fordevServerand cache paths), then each worker calls it again inCompilerWorker.ts.isCompilationInProgressis one global boolean on Rspack, a per-platform record on webpack, with slightly different "not found in compilation assets" errors.ProgressPlugin+ thedonehook on Rspack, vs worker messages on webpack (which drops reporter progress above 99% in favour of thedonemessage).watchOptions: Rspack appliescompilers[0].options.watchOptionsto the wholeMultiCompiler(per-platform values are ignored); each webpack worker uses its own config's.MultiCompilerwatchRunhook on Rspack, and from thewatchRundelegate handler instart.tson webpack.--max-workers(documented "(Rspack only)"),experiments.parallelLoader,RSPACK_PROFILEtracing.DEFAULT_RSPACK_CONFIG_LOCATIONShas 6 entries;DEFAULT_WEBPACK_CONFIG_LOCATIONShas 10 (including.webpack/variants).Proposed direction
Collapse the two trees into one
commands/implementation parameterised bybundler: 'rspack' | 'webpack', with a small adapter for the compiler factory,ProgressPlugin, cache accessor and profiling. The config layer already threads this parameter everywhere.Pick one execution model for the dev server and share the
Compiler:MultiCompilerfor both — simplest, but webpack's CPU-bound compilation for multiple platforms then shares a single thread;Compilerwith a pluggable execution strategy — in-process for Rspack, workers for webpack.Either way, the dev-server delegate, asset cache, progress and pending-resolver logic should exist once.
Single
defineConfigimplementation; keepdefineRspackConfig/defineWebpackConfigas aliases.One
@callstack/repack/commandsentrypoint that detects the bundler, keeping the per-bundler entrypoints as explicit overrides.Fix the divergences above as part of the merge, rather than porting them across.
Scope
bundle.tsandstart.tsinto single implementations with one dev-server delegateCompiler: decide the execution model, share asset cache / progress / resolver logic, singleCompilerAssetCompilerWorker(pass the resolved config, or drop the worker path)getSourceabsolute-path handling andapi.getPlatforms()defineConfigimplementations and the command registries; decide whether thewebpack-*aliases survive v6@callstack/repack/commandsentrypoint with detection--max-workerssemantics for webpack; unify config file discoverytemplates/pairs and updatepackages/init(createBundlerConfig.ts,modifyReactNativeConfig.ts)describe.eachbundler matrix inapps/tester-app/__tests__green through the merge; add coverage for the divergences abovegetting-started/bundlers.md([Docs] Getting Started: Rspack & webpack #917) against the unified surface, update the migration guides that referencecommands/<bundler>, note the entrypoint change in the v5 → v6 guideOpen questions
repack.config.*filename worth the migration churn, given both existing names are established?