Differences when migrating to Rspack 1.x while keeping webpack configuration, and handling incompatible plugins
Rspack is a bundler written in Rust, designed to accept webpack's configuration schema as-is. This is why it is often mentioned as a middle-ground option for webpack-based projects where a full migration to Vite feels too risky.
This article is based on Rspack 1.x and covers what actually changes when migrating while keeping your webpack configuration, as well as how to handle incompatible plugins. Rspack 2.0 was officially released in April 2026, and installing @rspack/core without any version constraint today will give you 2.0. To follow this guide on 1.x, pin the version with something like @rspack/core@^1; most options and plugin APIs remain valid in 2.0 as well.
Processing Model Differences Between webpack and Rspack
webpack is written in JavaScript and runs on the Node.js event loop. The pipeline — dependency graph construction, code transformation, tree shaking, and chunk splitting — is fundamentally tied to a single thread, and thread-loader only parallelizes specific loader stages.
Rspack is written in Rust and runs most of that pipeline in multiple threads. webpack's HMR also does partial recompilation around changed modules, but Rspack reduces rebuild latency itself through a more precise incremental graph and native code execution.
Among published case studies, the Mews Rspack migration report reported a roughly 94% reduction in build time — from 3 minutes down to 10 seconds — in a large monorepo, and the Yelp Engineering Blog reported up to 80% reduction with a fully warmed cache. Results vary significantly by measurement conditions, so always re-validate against your own project.
Three Compatibility Layers
The compatibility Rspack advertises is easier to evaluate when broken down into three layers.
| Layer | Compatibility Scope | Notes |
|---|---|---|
| Config file | Most of entry, output, resolve, module.rules, optimization, etc. |
A small number of webpack-internal-only options are exceptions |
| Loaders | Most community loaders such as babel-loader, css-loader, sass-loader work |
JS-based loaders are slower than native alternatives |
| Plugins | Hook system including compiler.hooks, compilation.hooks is supported |
Incompatible when directly calling webpack internal APIs |
Migrating with Minimal Changes
Replacing Packages
npm uninstall webpack webpack-cli webpack-dev-server
npm install -D @rspack/core@^1 @rspack/cli@^1 @rspack/dev-server@^1Update the package.json scripts.
{
"scripts": {
"build": "rspack build",
"dev": "rspack serve"
}
}You can keep the config file named webpack.config.js or rename it to rspack.config.js — the schemas are essentially the same.
Identifying Incompatibilities by Attempting a Build
Rspack does not provide a separate diagnostic report tool. In practice, you simply run a build with your existing config and identify incompatible options and plugins from the warnings and error messages in the console.
npx @rspack/cli build --config webpack.config.jsCollecting the warnings that appear here will help you prioritize the replacement work that follows.
Swapping in Built-in Plugins
Rspack 1.x provides Rust-based built-in versions of commonly used webpack plugins.
const rspack = require('@rspack/core')
module.exports = {
plugins: [
new rspack.HtmlRspackPlugin({
template: './public/index.html',
}),
new rspack.CssExtractRspackPlugin(),
new rspack.CopyRspackPlugin({
patterns: [{ from: 'public' }],
}),
new rspack.DefinePlugin({
'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV),
}),
],
}The to field in CopyRspackPlugin is a path relative to output.path. If your output directory is already dist, setting to: 'dist' will copy files into dist/dist/. To keep files at the root, either omit to as shown above or set to: '.'.
From JS Loaders to Native Loaders
babel-loader works in Rspack, but the JavaScript execution overhead makes it slower than Rust-native transforms — this is the most common reason people feel the migration wasn't as fast as expected. Rspack recommends using the built-in builtin:swc-loader.
ts-loader is not fully incompatible, but it references webpack internal interfaces in some places, so full compatibility is not guaranteed. In practice, delegating TypeScript compilation to builtin:swc-loader and running type checking in a separate process is a reliable combination.
module.exports = {
module: {
rules: [
{
test: /\.[jt]sx?$/,
exclude: /node_modules/,
use: {
loader: 'builtin:swc-loader',
options: {
jsc: {
parser: { syntax: 'typescript', tsx: true },
transform: { react: { runtime: 'automatic' } },
},
},
},
},
],
},
}When You Have Custom Babel Plugins
If you need a Babel-specific transform like babel-plugin-styled-components, replacing it with builtin:swc-loader alone is not straightforward. There are two options.
First, if there is a corresponding implementation in the SWC plugin ecosystem, use that. For styled-components, you can attach @swc/plugin-styled-components to the SWC config.
// Conceptual example — attaching the styled-components plugin to the SWC config
{
loader: 'builtin:swc-loader',
options: {
jsc: {
experimental: {
plugins: [['@swc/plugin-styled-components', { displayName: true, ssr: true }]],
},
},
},
}Second, if no replacement exists, keep babel-loader chained afterward for only the files that need it. Splitting by filename pattern tends not to work well in real codebases; it is more practical to narrow the scope by specific paths (e.g., packages/legacy/**) or resource conditions.
Handling Incompatible Plugins
Where Incompatibilities Arise
The Rspack official plugin compatibility list specifies support status per individual plugin. Incompatibilities generally fall into two patterns.
Direct calls to webpack internal APIs. Plugins that use NormalModuleFactory internal interfaces or combine compilation.hooks.processAssets with webpack-specific utilities will not work as-is. Be wary of plugins that reach into internal objects via paths like compiler.webpack.NormalModule.
Assumptions about webpack internal object shapes. Even when Rspack emulates an API, if the internal object structure differs, failures can occur silently at runtime without any exception.
Replacement Paths for Major Plugins
| webpack Plugin | Rspack Replacement | Notes |
|---|---|---|
html-webpack-plugin |
rspack.HtmlRspackPlugin |
Built-in, Rust-based |
mini-css-extract-plugin |
rspack.CssExtractRspackPlugin |
Built-in |
copy-webpack-plugin |
rspack.CopyRspackPlugin |
Built-in |
fork-ts-checker-webpack-plugin |
@rspack-contrib/ts-checker-rspack-plugin |
Separate-process type checking |
webpack-bundle-analyzer |
@rsdoctor/rspack-plugin |
Bundle structure analysis |
unplugin-based Tools Only Need a Subpath Change
Tools from the unplugin ecosystem such as unplugin-icons and unplugin-auto-import work as-is by simply changing the import path to the /rspack subpath.
const Icons = require('unplugin-icons/rspack').default
module.exports = {
plugins: [
Icons({ compiler: 'jsx', jsx: 'react' }),
],
}Replacing fork-ts-checker
npm uninstall fork-ts-checker-webpack-plugin
npm install -D @rspack-contrib/ts-checker-rspack-pluginconst { TsCheckerRspackPlugin } = require('@rspack-contrib/ts-checker-rspack-plugin')
module.exports = {
plugins: [
new TsCheckerRspackPlugin({
typescript: { configFile: 'tsconfig.json' },
}),
],
}Points to Watch Out For
Module Federation
Rspack supports the Module Federation v2 spec (as of 2026). When consuming a remote built with webpack 5's original Module Federation (v1) from an Rspack host, a React duplicate instance warning may appear at runtime if the shared object shapes do not match. For attaching basic MF in Rspack 1.x, using the built-in container plugin is the officially documented approach.
const rspack = require('@rspack/core')
module.exports = {
plugins: [
new rspack.container.ModuleFederationPlugin({
name: 'host',
remotes: {
app1: 'app1@http://localhost:3001/remoteEntry.js',
},
shared: {
react: { singleton: true, requiredVersion: '^18.0.0' },
'react-dom': { singleton: true, requiredVersion: '^18.0.0' },
},
}),
],
}If you actively use MF v2 features, there is a separate path using @module-federation/enhanced/rspack, so check the official documentation.
CSS Extract Loader in SSR Setups
In SSR projects, it is common to conditionally remove the mini-css-extract-plugin loader from the server bundle. In Rspack, the corresponding loader reference changes to rspack.CssExtractRspackPlugin.loader, so you need to update this reference wherever you branch between server and client builds.
// Conceptual example — server/client branching
const isServer = process.env.BUILD_TARGET === 'server'
const rspack = require('@rspack/core')
module.exports = {
module: {
rules: [
{
test: /\.css$/,
use: isServer
? ['css-loader']
: [rspack.CssExtractRspackPlugin.loader, 'css-loader'],
},
],
},
plugins: [
...(isServer ? [] : [new rspack.CssExtractRspackPlugin()]),
],
}Angular Projects
Angular CLI uses Angular-specific plugins that depend heavily on webpack's internal APIs. While Rspack reproduces a significant portion of the webpack hook system, the common assessment from community reports is that it is difficult to seamlessly replace that layer as well. The difficulty is substantially higher than with React or Vue projects, so at this point it is better to either hold off on migrating Angular stacks or treat it as a separate experimental project.
Migration Flow
Having rough time estimates by project size makes scheduling conversations easier. A simple React + TypeScript SPA often finishes within half a day, while a monorepo with many custom loaders and plugins is realistically a one-to-two day effort. Actual time varies significantly depending on the number of custom Babel plugins, MF remotes, and whether SSR is involved.
Bundle Analysis and Verification
For post-migration verification, it is worth trying Rsdoctor instead of webpack-bundle-analyzer. It supports both webpack and Rspack and shows build bottlenecks alongside bundle composition.
npm install -D @rsdoctor/rspack-pluginconst { RsdoctorRspackPlugin } = require('@rsdoctor/rspack-plugin')
module.exports = {
plugins: [
process.env.RSDOCTOR && new RsdoctorRspackPlugin({
supports: { generateTileGraph: true },
}),
].filter(Boolean),
}RSDOCTOR=true rspack buildPositioning as of 2026
As mentioned earlier, Rspack 2.0 was released in April 2026. According to the official announcement, the bundler itself has been migrated to pure ESM, the dependency graph and install size of @rspack/dev-server have been significantly reduced, and performance improvements were published as "up to 100% faster" compared to 1.0 on certain benchmarks (see the original for measurement conditions).
The reason for referencing 1.x migration materials at this point is that many teams start from the 1.x stable release on their production codebases, validate the results, and then upgrade to 2.0. The safe path is to pin the version with @rspack/core@^1, migrate following the steps in this document, and then review the breaking changes list in the 2.0 release notes.
References
- Rspack Official Docs — Migrating from webpack
- Rspack Official Docs — Plugin Compatibility List
- Rspack Official Docs — Community Plugin Compatibility
- Rspack Official Docs — Module Federation
- Rspack Official Blog — Announcing v1.0
- Rspack Official Blog — Announcing v2.0
- Mews Engineering Blog — From webpack to Rspack
- Yelp Engineering Blog — From webpack to Rspack
- GitHub — ts-checker-rspack-plugin
- Rspack Official Docs — Rsdoctor Usage Guide
- Brian Birtles Blog — Lessons Learned Switching to Rspack