1. Why We Migrated
We maintain a large, multi-entrypoint Single Page Application (SPA) with a custom router and integrated micro-frontends via Module Federation. With over 11 independent entry bundles and full CI pipelines, build performance had become a major bottleneck — especially with Webpack.
Our main goal: dramatically reduce build time without touching bundle size or runtime behavior. Rspack promised faster builds with familiar Webpack compatibility, and it delivered — but not without a few surprises.
2. Migration Plan and Scope
We opted for an all-at-once migration rather than phasing. This included:
- Replacing Webpack with Rspack in all configs.
- Moving from
webpack.config.js
to an array-basedrspack.config.js
. - Updating all build/development scripts to use Rspack CLI.
- Ensuring all CI/CD pipelines, microfrontends, and chunk-loading logic remained intact.
Rspack supports exporting an array of configs. Here's our rspack.config.js
:
module.exports = [
require('./rspack/agency-admin.config.js'),
require('./rspack/surveys.config.js'),
require('./rspack/server.config.js'),
...
];
Each config builds independently in parallel — ideal for our multi-app setup.
3. Config Refactor: Before and After
Our configs are modular: each specific app config extends a shared base.config.js
and webapp.config.js
. Here's how we structured a few key parts.
✅ Example: agency-admin.config.js
Highlights
output: {
filename: DEVELOPMENT
? 'scripts/engage/[name].js'
: 'scripts/engage/[name]/[contenthash].js',
chunkFilename: DEVELOPMENT
? 'scripts/engage/[name].chunk.js'
: 'scripts/engage/[name].chunk/[contenthash].js',
}
-
Module Federation was preserved using Rspack’s
ModuleFederationPluginV1
. - Asset stats were written using our custom
AssetManifestPlugin
.
✅ Server Config (server.config.js
)
- Uses
target: 'node'
-
externals
configured withwebpack-node-externals
- Critical remotes loaded dynamically via module federation
4. Plugin & Loader Compatibility
Most Webpack loaders worked out of the box with minimal rewiring. However:
🛠️ Custom Plugin: AssetManifestPlugin
We retained a hand-written plugin that generates a stats manifest JSON:
compiler.hooks.done.tapPromise(pluginName, async (stats) => {
const statsJson = stats.toJson({ entrypoints: true, chunks: true, ... });
await writeFile(filepath, JSON.stringify(statsJson, null, 2));
});
No changes were required to make it work with Rspack — a good sign of API stability.
5. Performance Gains
Metric | Webpack | Rspack | Gain |
---|---|---|---|
Full Build | 1032.47 s | 312.92 s | ~70% ↓ |
The improvement was immediate, both locally and on CI (Jenkins). No changes were required in CI jobs — we simply swapped out the CLI in package.json
.
6. Gotchas
⚠️ chunkhash
Collision in S3 Artifacts
- We store all built assets in S3 using
[chunkhash]
filenames. - After the switch to Rspack, a few files generated the same chunkhash as previous Webpack builds — but with incompatible runtime formats.
- Because we don't delete old artifacts, our CDN served a Webpack-built file instead of the new one.
✅ Fix:
Switched from:
filename: '[name].[chunkhash].js'
To:
filename: '[name].[contenthash].js'
This made the hashing content-aware and eliminated the risk of mismatched builds.
⚠️ ContextReplacementPlugin
Shim Removed
During our initial POC, Rspack lacked support for ContextReplacementPlugin
, which we relied on in Webpack. We wrote a custom shim to mimic it.
By the time we fully migrated, Rspack had added support, allowing us to delete the shim and simplify the config.
⚠️ Runtime Global Clashes
Rspack does not default chunkLoadingGlobal
like Webpack. Without setting this explicitly, chunk runtime globals could clash — especially important in our microfrontend setup.
output: {
chunkLoadingGlobal: 'rspackChunkLoadingGlobal',
}
7. Testing, Tooling, and Integration
- No changes were needed to our Flow, Jest, or E2E tests.
- Linting worked fine with
eslint-import-resolver-webpack
pointing torspack.config.js
. - Tools like Sentry sourcemaps, Webpack Bundle Analyzer, and custom loaders worked without modification.
CI (Jenkins) required no pipeline changes. A simple script swap from webpack
to rspack
did the job.
8. Final Thoughts
This was a high-impact migration that paid off in less than a week. Here’s what worked for us:
✅ Migrating all apps at once to avoid hybrid states
✅ Ensuring S3 hash collisions were handled early
✅ Relying on plugin parity as Rspack matured
9. What's Next?
- Investigating
Rsdoctor
for further cold build diagnostics - Possibly rewriting one legacy plugin using Rspack’s newer APIs
- We currently use Babel to strip Flow types and rely on multiple Babel plugins. Moving to SWC would’ve touched a large surface area — so we deferred it.
- Similarly, we stayed with our PostCSS plugin stack for CSS processing and didn’t switch to Lightning CSS, prioritizing migration safety over marginal gains.
Rspack is ready for real-world use — and for performance-obsessed frontend teams, it’s already a game changer.
Should You Migrate?
Here’s how we’d answer it:
- ✅ Are your Webpack build times >10 minutes?
- ✅ Do you have multiple independent apps or entrypoints?
- ✅ Are you not relying on niche Webpack plugins with no Rspack equivalent?
- ✅ Do you want a faster build with minimal rewrite risk?
If so, Rspack is a strong candidate. You don’t need to switch to SWC or Lightning CSS. We didn’t — and it still paid off.
Thanks for Reading
If this migration helped you, or if you’re considering a similar move, we’d love to hear from you. Feel free to drop questions or share your experience in the comments.
Interested in how our design system tooling or MCP integrations evolve from here? Follow along or check the repo links soon. ✌️
Top comments (0)