Rspack, a Rust bundler that matches the webpack API
Rspack is a bundler with a Rust core whose configuration deliberately mirrors webpack 5. Version 2.1.10 shipped on 13 August 2026, the license is MIT, and the project is developed by ByteDance. Compatibility is high but not complete, and the gaps are exactly what decides whether a migration takes a day or several weeks.
What it is and where it came from
A bundler takes a project's source modules, builds a dependency graph from them, and emits output files for the browser or for Node. Rspack does exactly what webpack does, except the graph, the transforms, and the minification happen in native code. The configuration, plugin, and loader layer stays in JavaScript, so from the config author's point of view an rspack.config.mjs file looks like the webpack.config.js it replaced.
The reason the project exists is stated plainly in the documentation. At ByteDance, large monolithic applications took ten minutes to build, in extreme cases half an hour, and a cold start of the development server took several minutes. The team decided the acceptable threshold for the dev command was on the order of ten to fifteen seconds, and instead of designing a new configuration interface, rewrote the core underneath the existing one. That is what separates Rspack from Turbopack, which started over with a new architecture and new configuration.
The timeline matters when judging maturity. The first npm package 0.0.1 is dated 6 April 2022. Version 1.0 arrived on 28 August 2024, and only from that point does the documentation call it production ready. Version 2.0 shipped on 22 April 2026, the current stable line is 2.1.x, and the registry already carries an rc tag pointing at 2.2.0-rc.0 from 21 August 2026. Anyone who cannot move to version 2 yet has a separate latest-v1 tag pointing at 1.7.12.
The web-infra-dev/rspack repository has roughly 12.9 thousand stars, 841 forks, and 139 open issues. For a sense of usage scale, @rspack/core records about 7.2 million weekly downloads against webpack's roughly 46.4 million. Read those numbers carefully, because npm downloads largely come from transitive dependencies in continuous integration pipelines rather than from a count of projects.
Version, license, and how it is distributed
I checked the license in three places and the result is consistent, which is rare enough during a dependency audit to be worth stating. The LICENSE file on the main branch carries MIT text with the notice Copyright (c) 2022-present Bytedance Inc and its affiliates. The license field in the npm registry for @rspack/core@2.1.10 reads MIT. The published tarball weighs about 361 kilobytes, contains 322 files including package/LICENSE with the same MIT text, and real code under dist. This is not a placeholder package reserving a name.
One thing deserves attention from a license scanner. The compiled directory inside the tarball vendors several dependencies, each with its own license file: @rspack/lite-tapable, @swc/types, connect-next, http-proxy-middleware, tinypool, watchpack, and webpack-sources. A scanner that reads only the top-level package.json will not see them, even though they ship in the artifact.
How the binaries are distributed can break installation in a locked-down environment. @rspack/core itself has exactly one regular dependency, @rspack/binding at the same version, plus two peer dependencies: @swc/helpers in the range ^0.5.23 and @module-federation/runtime-tools in the range ^0.24.1 || ^2.0.0. The whole native machinery sits in @rspack/binding, which declares 14 optional dependencies: thirteen per-platform binary packages and one WebAssembly build.
| Platform family | Variants published as separate packages |
|---|---|
| macOS | darwin-x64, darwin-arm64 |
| Linux glibc | linux-x64-gnu, linux-arm64-gnu, linux-riscv64-gnu, linux-ppc64-gnu, linux-s390x-gnu |
| Linux musl | linux-x64-musl, linux-arm64-musl, linux-riscv64-musl |
| Windows | win32-x64-msvc, win32-arm64-msvc, win32-ia32-msvc |
| Fallback | wasm32-wasi |
Installing with a flag that skips optional dependencies will not give you a working bundler, because you end up with the JavaScript layer and no core. On a platform outside the list, the documentation says to install @rspack/binding-wasm32-wasi manually, which is slower but portable. If your company runs an npm mirror that filters optional packages, unblock them before the first run, or diagnosing the failure will take longer than the migration itself.
The engines field requires ^20.19.0 || >=22.12.0. Node 18 was dropped in version 2.0, and it is a hard rejection rather than a warning. The documentation also lists Deno and Bun as supported runtimes.
What version 2.0 changed
The major bump from 22 April 2026 carries a long list of breaking changes. Below are the ones that actually touch existing configurations, taken from the release notes rather than from memory.
The package moved to pure ESM. The type field in package.json is module and the CommonJS build was removed. The exports map exposes only four entries: ., ./hot/*, ./hot/*.js, ./package.json, and ./module. A plugin or build script that did require('@rspack/core') will stop working and needs rewriting as an import. This is the most common source of surprise after a version bump.
CSS handling is on by default, so a project that previously relied on an explicit experimental switch gets different behavior with no config change. The experiments.rspackFuture section is gone and the bundlerInfo option moved into output. The incremental option was promoted from experiments to the top level and, importantly, is independent of cache, so cache: false does not turn it off.
Defaults changed in ways that are easy to miss: chunkLoadingGlobal is now rspackChunk, hotUpdateGlobal is rspackHotUpdate, the default trustedTypes policy name is rspack, resolve.roots starts as an empty array, exportsPresence now errors by default, the .wasm extension was dropped from the default JavaScript extension list, and the webpack import condition was removed from the default CSS conditions. The default value of devtool changed as well.
Several things were removed without a drop-in replacement: experiments.SubResourceIntegrityPlugin, experiments.parallelLoader, experiments.lazyCompilationMiddleware, experiments.outputModule, output.charset, the profile and stats.profile options, the deprecated WarnCaseSensitiveModulesPlugin, the sri option on HtmlRspackPlugin, the getHooks method on plugins, and optimization.removeAvailableModules. The built-in JavaScript compiler stopped reading .swcrc. ProgressPlugin now receives a single structured info object instead of a list of arguments. @rspack/dev-server became an optional peer dependency, so you have to install it deliberately.
One caveat belongs here. During work on 2.0 a change landed that enabled verbatimModuleSyntax in builtin:swc-loader by default, but it was reverted before release. Anyone reading the raw change list will see both entries and may draw the wrong conclusion.
Where webpack compatibility ends
This is the part you read such an article for. The Rspack documentation states that among the fifty most downloaded webpack plugins, more than 85 percent either work or have an alternative. The compatibility table in the repository lists 56 entries and breaks down like this:
| Status in the compatibility table | Plugin count | What it means in practice |
|---|---|---|
| Compatible | 28 | Works unchanged, leave it in the config |
| Included | 5 | The core took the feature over, drop the plugin |
| Alternative | 12 | Swap the package for an Rspack counterpart |
| Partially compatible | 3 | Works with caveats, usually needs another plugin |
| Incompatible | 8 | No support, marked as to be implemented |
The eight entries marked incompatible are critters-webpack-plugin, @ngtools/webpack, @storybook/react-docgen-typescript-plugin, last-call-webpack-plugin, git-revision-webpack-plugin, @cypress/webpack-preprocessor, @intlify/unplugin-vue-i18n, and webpack-remove-empty-scripts. That list says more than a compatibility percentage. An Angular project relying on @ngtools/webpack will not move to Rspack by swapping the bundler. A project that generates React component docs in Storybook loses the plugin that extracts types. An end-to-end suite using the webpack preprocessor in Cypress needs a different path.
The three partially compatible entries are webpack-assets-manifest, add-asset-html-webpack-plugin, and html-webpack-harddisk-plugin, where the last two require html-webpack-plugin, which is itself compatible.
Configuration compatibility has holes too. Rspack does not support resolve.plugins, so tsconfig-paths-webpack-plugin is replaced by the resolve.tsConfig field. The command line interface rejects --progress, --color, --bail, and --output-pathinfo, and leaving them in produces an Unknown option error before the configuration is even loaded. The equivalent of --color is set through stats.colors.
The largest structural difference concerns caching. The option shape differs and copying it across one to one will not work.
// rspack.config.mjs
import path from 'node:path'
export default {
cache: {
// webpack would use type: 'filesystem'
type: 'persistent',
// an object with keys in webpack, a flat array of paths here
buildDependencies: [
path.join(import.meta.dirname, 'rspack.config.mjs'),
path.join(import.meta.dirname, 'package.json'),
path.join(import.meta.dirname, 'tsconfig.json')
],
name: 'client',
version: '1',
// this was a top-level snapshot field in webpack
snapshot: {
immutablePaths: [path.join(import.meta.dirname, 'constant')],
managedPaths: [path.join(import.meta.dirname, 'node_modules')],
unmanagedPaths: []
},
storage: {
type: 'filesystem',
// equivalent of webpack's cache.cacheDirectory
directory: path.join(import.meta.dirname, 'node_modules/.cache/test'),
// equivalent of webpack's cache.cacheLocation
location: path.join(import.meta.dirname, 'node_modules/.cache/test/client')
}
},
// a separate decision, cache: false does not disable it
incremental: true
}The values cache: false, cache: true, and cache: { type: 'memory' } carry over directly. Only on-disk caching needs rewriting.
Loaders fare better than plugins. The documentation claims compatibility with almost all community loaders and that matches practice, because the loader interface is simpler than the plugin interface. The exception is vue-loader for Vue 3, replaced by the rspack-vue-loader package with the experimentalInlineMatchResource option enabled. A separate category covers loaders worth dropping because the core does the job faster.
// rspack.config.mjs
const isDev = process.env.NODE_ENV === 'development'
export default {
module: {
rules: [
{
test: /\.(?:js|mjs|jsx|ts|tsx)$/,
exclude: [/[\\/]node_modules[\\/]/],
use: [
{
// instead of babel-loader with the typescript and react presets
loader: 'builtin:swc-loader',
options: {
detectSyntax: 'auto',
jsc: {
transform: {
react: {
runtime: 'automatic',
development: isDev,
refresh: isDev
}
}
}
}
}
]
},
// instead of file-loader
{ test: /\.(png|jpe?g|gif)$/i, type: 'asset/resource' },
// instead of raw-loader
{ test: /^BUILD_ID$/, type: 'asset/source' }
]
}
}If you use the external swc-loader, only the loader name changes to builtin:swc-loader and the options stay identical. For unplugin family packages that publish a separate entry point per bundler, switch the import from /webpack to /rspack, for example unplugin-auto-import/rspack.
Webpack's built-in plugins have counterparts in Rspack under the same names and parameters, exposed through the rspack export. A handful carry their own name.
// rspack.config.mjs
import { rspack } from '@rspack/core'
export default {
plugins: [
new rspack.DefinePlugin({ 'process.env.NODE_ENV': JSON.stringify('production') }),
// instead of copy-webpack-plugin
new rspack.CopyRspackPlugin({ patterns: [{ from: 'public' }] }),
// instead of mini-css-extract-plugin
new rspack.CssExtractRspackPlugin({ filename: '[name].[contenthash].css' }),
new rspack.HtmlRspackPlugin({ template: './src/index.html' })
],
optimization: {
minimizer: [
// instead of terser-webpack-plugin
new rspack.SwcJsMinimizerRspackPlugin(),
// instead of css-minimizer-webpack-plugin
new rspack.LightningCssMinimizerRspackPlugin()
]
}
}Setting optimization.minimizer explicitly disables Rspack's default minimizers, so keep both entries on the list, one for JavaScript and one for CSS. Forgetting the second is a common cause of stylesheets suddenly growing after a migration.
Beyond plugins there are helper packages to swap.
| Webpack package | Rspack replacement |
|---|---|
webpack | @rspack/core |
webpack-cli | @rspack/cli |
webpack-dev-server | @rspack/dev-server |
webpack-dev-middleware | @rspack/dev-middleware |
webpack-chain | rspack-chain |
webpack-merge | rspack-merge |
Rsbuild, Rslib, Rspress, and Rsdoctor
The same team ships several tools sharing a prefix and people mix them up constantly. The distinction is simple once you learn it.
@rspack/core at version 2.1.10 is the bundler itself, with no opinion about how a project should look. The configuration is full and long, exactly as in webpack. @rsbuild/core at version 2.1.13 from 13 August 2026 is a layer on top of Rspack with ready-made defaults, handling TypeScript, CSS, images, and a dev server without writing rules. The Rspack documentation explicitly recommends Rsbuild for new projects and bare Rspack for cases that need control over every part of the graph. That is the practical advice missing from most writing about Rspack.
@rslib/core at version 0.23.2 from 3 July 2026 builds libraries rather than applications and describes itself as an Rsbuild-based tool. A version number below one signals that its interface may still shift. Rspress is a documentation generator, and here the package name is a trap: rspress in the npm registry sits at 1.47.2 with a description stating outright that it is the deprecated version 1 command line tool, while the current version 2 lives under @rspress/core at 2.0.19. Rsdoctor is a build analyzer, wired into Rspack through @rsdoctor/rspack-plugin at version 1.6.3. All of these packages are MIT licensed.
# a new project, the recommended path for most cases
npx -y create-rsbuild --dir my-app --template react
# a new project on bare Rspack, when full configuration is needed
npx -y create-rspack --dir my-app --template react
# migrating an existing webpack project
npm add -D @rspack/core @rspack/cli @rspack/dev-server
# check what in the project still imports webpack
grep -rn "require('webpack" src build scripts
# build analysis
npm add -D @rsdoctor/rspack-pluginThe package.json scripts change mechanically, with the caveat about unsupported command line options.
{
"scripts": {
"dev": "rspack dev",
"build": "rspack build --mode production",
"preview": "rspack preview"
}
}Rspack, Vite, and esbuild as three different models
A comparison only makes sense if it starts from the working model, because that drives the outcome rather than the implementation language.
Vite does not bundle in development mode. It serves native modules to the browser and transforms individual files on demand, so startup time is largely independent of project size. Rspack always bundles, exactly as webpack does, only in native code and with incremental recompilation on module replacement. The consequence is that on a very large application Vite starts faster, while Rspack gives identical behavior in development and production, without a class of bugs that only appear after a build.
esbuild is a different case. It is faster than both, but its feature set is narrower: the Rspack documentation points at the absence of hot module replacement and the lack of an equivalent to optimization.splitChunks. In practice esbuild tends to be a building block inside a larger tool rather than a standalone application bundler with elaborate chunk splitting.
| Criterion | Rspack | Vite | esbuild |
|---|---|---|---|
| Development mode | incremental bundling | native modules, no bundling | bundling |
| Configuration | webpack 5 compatible | its own, Rollup based | its own, minimal |
| Hot module replacement | yes | yes | none |
| Chunk splitting | full, as in webpack | through Rollup or Rolldown | limited |
| Core language | Rust | JavaScript plus Rust in Rolldown | Go |
| Weekly downloads | about 7.2M | about 143M | about 226M |
The download figures in the last row are heavily skewed by transitive dependencies, esbuild is pulled in by dozens of tools and Vite by frameworks. Do not read them as popularity among humans.
The selection criterion is therefore fairly sharp. Do you have an existing webpack configuration you do not want to rewrite and care about build time? Rspack. Are you starting from scratch with no legacy commitments? Vite or Rsbuild, depending on which side of the ecosystem you prefer. Are you building a small library or tool where raw speed matters and chunk splitting does not exist? esbuild.
Common migration mistakes
A few traps repeat in nearly every migration and they share one root cause, the assumption that compatible configuration means everything is compatible.
The first is leaving command line options in place. A script with --progress --color stops working with an Unknown option message before anyone looks at the configuration, and the message does not hint that bundler compatibility is the issue.
The second is copying cache one to one. The value type: 'filesystem' does not exist in Rspack, buildDependencies has a different shape, and snapshot moved. On top of that incremental is independent of cache, so trying to disable every reuse mechanism through cache: false alone will not fully disable anything.
The third is require in tooling code. The package has been pure ESM since version 2.0, so a custom plugin or build script that loaded @rspack/core through require blows up on the first run. The same problem applies to community plugins that have not been updated yet.
The fourth is forgetting the second minimizer. Passing only SwcJsMinimizerRspackPlugin in optimization.minimizer disables the defaults, including the CSS one, and stylesheets ship uncompressed.
The fifth is underestimating stale helper packages. The documentation names webpack-node-externals and node-polyfill-webpack-plugin as examples where a newer version is needed, because older ones lean on webpack internals. Before declaring a package incompatible, check whether bumping it is enough.
The sixth is removing the webpack dependency too early. As long as any loader, plugin, or script imports webpack or webpack/lib/*, the package has to stay as a compatibility dependency. Cleanup happens at the end, not at the start.
Drawbacks, risks, and when it is not worth it
The ecosystem is smaller than webpack's, and that is not a matter of a percentage in a table but of the long tail. Popular plugins are covered, but the more niche the need, the higher the chance you hit a package with no counterpart and end up writing your own. The eight entries marked incompatible in the official table are a set everyone should read before deciding, because a single item on that list can block a migration.
Dependence on one company is real. Rspack, Rsbuild, Rslib, Rspress, and Rsdoctor all come from the same team at ByteDance. The code is MIT so nobody can take it away, but the pace of development, the priorities, and support for new features depend on the decisions of one employer. That is a different risk profile from webpack, whose maintenance is distributed, and different from Vite with a foundation around the project.
Per-platform native binaries carry an operational cost. Fourteen optional packages mean more artifacts to mirror in a private registry, larger container image layers if you build carelessly, and a separate WebAssembly fallback path for platforms outside the list. The same trade-off applies to other native tools, for instance Biome or oxlint.
The pace of change has a price too. Under twenty months separated the 1.0 release in August 2024 from 2.0 in April 2026, and the breaking change list in version 2 is long. If you keep a dozen projects in a monorepo wired with Turborepo, expect the next major to force a similar round of fixes.
Rspack does not type check. Like webpack with swc-loader or esbuild-loader, it transforms TypeScript by stripping types, so tsc --noEmit stays in the pipeline or you add ts-checker-rspack-plugin, the replacement for fork-ts-checker-webpack-plugin. The same goes for linting, which has to run separately.
When is Rspack not worth reaching for? When the project has no webpack configuration to rescue, because then you pay for compatibility you never use. When you depend on one of the incompatible plugins and have no budget for a workaround. When builds already finish in a dozen seconds, because the saving will be unmeasurable against the risk. And when nobody on the team understands webpack configuration well enough to diagnose a behavioral difference, because error messages from the native layer are often less readable than those from plain JavaScript.
FAQ
Will an existing webpack configuration work unchanged?
Usually not entirely. Renaming the file to rspack.config.js and swapping built-in plugin imports covers most of it, but you separately need to rewrite cache, remove unsupported command line options, replace resolve.plugins with the resolve.tsConfig field, and check community plugins against the official compatibility table.
Do plugins written for webpack work in Rspack?
Some do, some need a counterpart. Of the 56 entries in the compatibility table, 28 work unchanged, 5 were absorbed into the core, 12 have a dedicated replacement, 3 work partially, and 8 do not work at all. Loaders are covered far better than plugins because their interface is simpler.
Rspack or Rsbuild?
Rsbuild for new projects, because it ships ready-made configuration for TypeScript, CSS, and a dev server. Bare Rspack when you are migrating an existing webpack configuration or need control over every part of the build. Rsbuild is built on Rspack, so this is not a choice between competitors.
What did version 2.0 break?
The most painful parts are the move to pure ESM and dropping Node 18. Beyond that: CSS enabled by default, incremental promoted to the top level and independent of cache, changed defaults for chunkLoadingGlobal, hotUpdateGlobal, resolve.roots, exportsPresence, and devtool, the removal of several experiments options, and the end of reading .swcrc.
Does Rspack type check TypeScript?
No. Types are stripped without verification, so type checking stays with tsc --noEmit or ts-checker-rspack-plugin. That is standard behavior for bundlers built on SWC and esbuild, not an Rspack quirk.
What happens if you install without optional dependencies?
You do not get a working bundler. The native core ships as 14 optional @rspack/binding-* packages, one per platform plus a WebAssembly variant. Installing while skipping optional dependencies leaves only the JavaScript layer, and in a locked-down registry those packages have to be mirrored separately.