Rspack 2.0 Ships Pure ESM Core, 1.4-Second Cached Builds, and a Single Dependency
Rspack 2.0 was officially released on July 29. It's no longer just "ByteDance's webpack replacement."
The core changes in 2.0 can be condensed into three points:
- Doubled performance — Compared to 1.0, build speed is up to 100% faster. A 10,000-component project's cached build dropped from 5.6s to 1.4s.
- Dependency slimming —
@rspack/dev-serverdependencies were cut from 192 to 1, reducing install size by over 90%. - Full ESM conversion — Core packages are now pure ESM, supporting modern features like RSC and import defer.
In summary, it is no longer content with just being a "faster webpack" and has started to forge its own path.
1. What exactly did Rspack 2.0 update?
1.1 Performance Improvements
Let's look at the most intuitive benchmark data first (source: rspack-react-10k-benchmark, test environment: M3 Pro chip, 32GB RAM, NVMe SSD, Node.js 22.12):
| Version | Production Build (No Cache) | Production Build (Cached) | HMR |
|---|---|---|---|
| Rspack 1.0 | 5.6s | 5.6s | 128ms |
| Rspack 1.7 | 3.6s | 2.2s | 134ms |
| Rspack 2.0 | 3.1s | 1.4s | 118ms |
Simply put: With persistent caching enabled, a React project with 10,000 components takes only 1.4 seconds for a production build.
What information do these numbers convey? A native webpack 5 without deep performance optimization or persistent caching typically takes 30-60 seconds for a production build of the same scale. Rspack 2.0's first build without cache is about 10-20 times faster than webpack, and cached build scenarios are over 20 times faster than webpack's cached builds.
Besides build speed, 2.0 also made two optimizations for caching scenarios:
- SWC minifier results can be cached and reused, improving build performance by about 50% on cache hits.
- Memory usage dropped by over 20% when caching is enabled.
The same project's production build time under webpack 5 is about 35-45 seconds (first build) / 28-35 seconds (cache hit). Rspack 2.0's cached build of 1.4 seconds is roughly 20-25 times faster than webpack's cached build.
CI Environment Cache Pitfall: Persistent caching relies on package-lock.json and environment variables to generate cache keys. If the CI build environment is fresh every time and the node_modules/.cache directory is not persisted, the cache hit rate is almost zero, and it can even slow down the build due to disk I/O. It is recommended to persist the cache directory using CI caching capabilities, or fix the RSPACK_CONFIG_CACHE_KEY environment variable to improve the hit rate.
1.2 Major Dependency Slimming: From 192 to 1
This change generated more buzz on Hacker News than the performance data.
| Package | 1.x Dependencies | 2.0 Dependencies | Install Size |
|---|---|---|---|
@rspack/dev-server |
192 | 1 | 15MB → 1.4MB |
@rspack/core |
8 | 1 | — |
@rspack/cli |
— | 0 (zero dependencies) | — |
How was this achieved?
- Replaced Express with
connect-next— The dev server's middleware model only needs Connect, not the full Express framework. - Bundled dependencies into the release artifact — Non-core dependencies are bundled directly into the npm package, allowing Rspack to control versions uniformly and avoid supply chain risks. However, this is a double-edged sword: this 'vendorization' strategy means you cannot directly upgrade transitive dependency versions via
npm overridesto fix CVE vulnerabilities, requiring reliance on official releases;patch-packagecan work on the bundled artifact, but maintenance costs are extremely high and not recommended for enterprise projects. Large enterprises need to assess whether their internal security scanning strategies are compatible with this approach. - Used Node.js 20+ native APIs — For example, using the built-in
styleTextinstead ofpicocolors. - Made non-core dependencies optional — For instance,
@module-federation/runtime-toolsnow requires manual installation only when using the Module Federation plugin.
One commenter put it well: compared to the performance figures, the reduction in dependency count impressed me more—it represents a shift in philosophy, not just a benchmark.
1.3 Pure ESM Core
Rspack 2.0's core packages (@rspack/core, @rspack/cli, @rspack/dev-server, @rspack/plugin-react-refresh) are all published as pure ESM packages, removing CommonJS build artifacts.
So the question is, will my existing project, which still uses CommonJS, break?
The answer is, assuming the Node.js version meets the requirements and no ESM dependencies with top-level await are introduced, it probably won't. Node.js 22.12+ has stable support for loading ESM modules via require() in CommonJS (require(esm)), and 20.19+ has experimental support. Therefore, most projects using Rspack via the JavaScript API do not need to modify their code.
But one thing to note: if an ESM module internally uses top-level await, require() will directly throw an ERR_REQUIRE_ASYNC_MODULE error, which cannot be bypassed. Currently, Rspack 2.0 itself does not use top-level await, but if you introduce a third-party ESM dependency containing top-level await in your config file, the CJS project will break:
Example:
// ❌ Will error: CJS config file introduces an ESM dependency with top-level await
// rspack.config.js (CommonJS)
const someEsmPkg = require('some-esm-pkg-with-tla'); // Throws ERR_REQUIRE_ASYNC_MODULE
// ✅ Fix 1: Rename config file to rspack.config.mjs
import someEsmPkg from 'some-esm-pkg-with-tla';
// ✅ Fix 2: Set "type": "module" in package.json
Also, to clarify: this refers to Rspack's own packages becoming ESM, which does not affect your ability to build CommonJS output with Rspack. The build behavior and configuration methods remain the same as before.
1.4 Output Optimization: Comprehensive Tree Shaking Enhancements
Rspack 2.0 has significantly enhanced its static analysis capabilities:
1. CommonJS require destructuring can now be tree shaken
// ✅ Supports tree shaking: static destructuring
const { bar } = require('./foo');
// ❌ Does not support tree shaking: runtime property access
const foo = require('./foo');
const bar = foo.bar;
// ❌ Does not support tree shaking: conditional exports
module.exports = process.env.NODE_ENV === 'development' ? devApi : prodApi;
This optimization relies on the static analyzability of the exports object. If your CJS module has runtime conditional exports or dynamic property assignments (module.exports['prop' + 'Name']), Rspack will conservatively keep the entire module. It is recommended to explicitly declare sideEffects: false in such cases to assist the analysis.
2. Bundler Annotation #__NO_SIDE_EFFECTS__
/*#__NO_SIDE_EFFECTS__*/
export function join(a, b) {
return `${a}-${b}`;
}
import { join } from './utils';
join('btn', 'primary'); // Return value is unused → the entire call will be removed
This feature is currently experimental and needs to be enabled via experiments.pureFunctions; it will be enabled by default in future versions.
By the way, here's the difference between it and /*#__PURE__*/ — many might be more familiar with the latter. /*#__PURE__*/ acts on a function call (marking a specific call as side-effect-free), while #__NO_SIDE_EFFECTS__ acts on the function declaration itself (marking the entire function as side-effect-free). The latter is more powerful: even if referenced across modules and the return value is unused, Rspack can safely delete the entire function call. Previously, achieving this required third-party plugins like webpack-deep-scope-plugin; now it's natively supported.
3. Module Federation Tree Shaking
Previously, using Module Federation's shared declaration for dependencies would load the complete package at runtime. Now you can enable the treeShaking option to only bundle the exports actually used:
// Method 1: Runtime automatic inference (recommended, no need to manually declare used exports)
new rspack.container.ModuleFederationPlugin({
shared: {
'lodash-es': {
singleton: true,
treeShaking: { mode: 'runtime-infer' },
},
},
})
// Method 2: Manually specify used exports (higher determinism)
new rspack.container.ModuleFederationPlugin({
shared: {
'lodash-es': {
singleton: true,
treeShaking: {
mode: 'manual',
usedExports: ['debounce'], // Only bundle debounce
},
},
},
})
1.5 Experimental Support for React Server Components
Rspack 2.0 begins to provide low-level build support for RSC:
- Supports
"use client"directives and module/function-level"use server"directives. - Compile-time checks for API calls that violate RSC specifications.
- CSS collection and injection for server and client components.
- HMR support for both server and client components.
How to manually enable: You need to set experiments.rsc: true and configure resolve.conditionNames to include the 'react-server' condition export, ensuring the server build correctly resolves RSC-specific entry points. It is more recommended to use rsbuild-plugin-rsc for an out-of-the-box experience; the plugin automatically handles splitting client/server dual-end build configurations, avoiding the need to write two sets of configs manually.
Capability Boundaries: Currently, only the basic RSC build pipeline is supported; advanced features like Streaming SSR and Server Actions are not supported. It is only recommended for use with upper-level frameworks like Modern.js or TanStack Start, and manual configuration from scratch is not advised. Currently, Modern.js already provides full RSC support based on Rspack, and Rspack is also collaborating with the TanStack team to support TanStack Start later.
1.6 Other Notable New Features
| Feature | Description |
|---|---|
import.meta support |
Preserves unrecognized import.meta properties, no longer replacing them with undefined. |
import defer support |
Stage-3 proposal, defers module evaluation; TS 5.9 already supports it. |
modern-module library build |
Generates ESM output more suitable for library publishing, preserving directory structure. |
#/ subpath aliases |
Directly uses the imports field in package.json, no need for extra alias configuration. |
| Simplified target config | Top-level target is automatically inherited by loaders and minification plugins, no need for repeated declarations. |
| Simplified swc-loader config | detectSyntax: 'auto' automatically infers syntax, one rule covers all file types. |
| Hashed module IDs | optimization.moduleIds: 'hashed', shorter and more stable module IDs. |
| Code splitting improvements | Supports splitChunks.enforceSizeThreshold to force splitting of large chunks. |
2. Why: Why is Rspack 2.0 needed?
2.1 webpack's Pain Points, the Part Vite Can't Solve
The landscape of frontend build tools has become a "three-way standoff" in 2026:
Vite remains the king for small to medium-sized projects—zero-config startup, extremely fast HMR, rich plugin ecosystem. By 2026, Vite's production build has switched to Rolldown (Rust) under the hood, bringing a qualitative change to large project build performance. But Rspack still has irreplaceability in two scenarios: one is loading sub-applications in micro-frontend host apps (deep dependency on webpack's ModuleFederationPlugin ecosystem), and the other is enterprise-level projects requiring deep customization of webpack compiler hooks. Vite's production build follows the Rollup/Rolldown path, which is naturally incompatible with webpack's loader/plugin ecosystem—this is not a performance issue, but a path issue.
Rspack's core differentiation can be summed up in one sentence: enabling projects in the webpack ecosystem to gain Rust-level performance at a low cost.
This is not a small market. There are massive numbers of webpack projects globally, with their config files, custom loaders, and internal company plugins—these things cannot just be thrown away. Rspack has about 95% compatibility with webpack's common APIs and mainstream ecosystem, meaning most projects can run just by changing the package name.
2.2 Review of the 1.x Phase: Goals Achieved
Rspack 1.0 was released in August 2024, with the set goal of "achieving 10x performance improvement while maintaining webpack compatibility."
In the nearly two years from 1.0 to 2.0, what was achieved?
- Weekly downloads grew from 100,000 to 5 million—a 50x increase.
- Features like incremental builds, on-demand compilation, persistent caching, constant folding, and barrel file optimization were successively introduced.
- A complete toolchain, Rstack (Rsbuild, Rslib, Rstest, Rspress, Rsdoctor, Rslint), was built around Rspack.
- Community support: Frameworks like Angular, Nuxt, Next.js, Storybook, Docusaurus, and Modern.js have provided Rspack adapters.
2.3 Why launch 2.0?
The Rspack team stated it plainly:
"Rspack's goal is not just to become a 'faster webpack'. During the 1.x phase, we intentionally kept Rspack's API and defaults consistent with webpack 5 to help existing projects migrate at a low cost. But as JavaScript module specifications and the ecosystem evolve, some historical designs are no longer suitable as current defaults."
1.x was about 'looking like webpack' to attract migration; 2.0 starts to 'be itself'.
2.0's ESM conversion, RSC support, output optimization, and configuration simplification—these are not things webpack would do. While maintaining compatibility, Rspack is starting to introduce default behaviors more aligned with the 2026 JavaScript ecosystem.
3. How to adopt Rspack 2.0?
3.1 For new projects, use Rsbuild directly
If you are using Rspack for the first time, it is recommended to use Rsbuild (the out-of-the-box build tool driven by Rspack):
npm create rsbuild@latest
Rsbuild 2.0 has been released synchronously with Rspack 2.0, offering an out-of-the-box experience without fiddling with configurations.
3.2 Upgrading from Rspack 1.x
Step 1: Upgrade dependencies
{
"devDependencies": {
"@rspack/core": "^2.0.0",
"@rspack/cli": "^2.0.0",
"@rspack/dev-server": "^2.0.0",
"@rspack/plugin-react-refresh": "^2.0.0"
}
}
Step 2: Confirm Node.js version
Rspack 2.0 requires Node.js 20.19+ or 22.12+, and no longer supports Node.js 18.
node -v
# If lower than 20.19, upgrade Node.js first
Step 3: Handle Breaking Changes
Major breaking changes:
| Change | Impact | Solution |
|---|---|---|
Removed module.unsafeCache |
Cache configuration needs adjustment | Replace with cache.type: 'persistent' |
@rspack/cli no longer depends on @rspack/dev-server by default |
rspack serve command requires manual installation of dev-server |
npm add @rspack/dev-server -D |
resolve.exportsPresence default changed from warn to error |
Errors directly when exports are missing | ⚠️ The breaking change most likely to cause CI build failures! Many third-party libraries have non-standard exports fields that will be falsely flagged. If the build fails after upgrading, set resolve.exportsPresence to 'warn' to downgrade to a warning (does not interrupt the build). |
builtin:swc-loader no longer reads .swcrc |
SWC configuration needs migration to loader options | Move config to rspack.config.mjs |
output.chunkLoadingGlobal default value changed |
Chunk global variable name changed from webpackChunk to rspackChunk |
Can be explicitly configured if depended upon |
webpack-bundler-analyzer removed |
--analyze parameter no longer works |
Switch to Rsdoctor |
output.libraryTarget etc. config migration |
Library build configuration syntax changed | Migrate to output.library.type |
devServer.hot default value changed |
Hot Module Replacement no longer enabled by default | Must manually configure devServer.hot: true, otherwise HMR will fail after upgrade |
| Stats output configuration refactored | Some webpack-compatible stats fields removed | Custom log output needs adaptation to new format |
Example for downgrading resolve.exportsPresence:
export default defineConfig({
resolve: { exportsPresence: 'warn' },
});
Step 4 (Optional): Use an Agent to assist migration
If you are using a Coding Agent (like Cursor, Claude Code, etc.), you can install Rspack's official migration Skill:
npx skills add rstackjs/agent-skills --skill rspack-v2-upgrade
After installation, let the Agent help you complete the upgrade, which is usually more efficient than doing it manually.
3.3 Migrating from webpack
If you are still using webpack, Rspack's migration path is already very mature. Core steps:
# 1. Install Rsbuild (the simplest path)
npm create rsbuild@latest
# 2. Choose the "Convert from webpack" mode
# Rsbuild will automatically attempt to convert your webpack.config.js
Or migrate manually:
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import { defineConfig } from '@rspack/core';
// 2.0 recommends ESM config files; __dirname is no longer available by default and must be declared manually
const __dirname = path.dirname(fileURLToPath(import.meta.url));
export default defineConfig({
entry: './src/index.js',
output: {
path: path.resolve(__dirname, './dist'), // Must use an absolute path
},
module: {
rules: [
{
test: /\.(?:js|mjs|jsx|ts|tsx)$/,
use: {
loader: 'builtin:swc-loader',
options: {
// 2.0 new feature: auto-infer syntax, replacing manual syntax + jsx/tsx booleans
detectSyntax: 'auto',
jsc: {
transform: {
react: {
runtime: 'automatic',
},
},
},
},
},
},
],
},
});
The new detectSyntax: 'auto' in 2.0 greatly simplifies loader configuration for multiple file types.
Previously, you had to write multiple rules to handle .js, .jsx, .ts, .tsx separately, and manually specify options like jsc.parser.syntax, jsx, tsx. Now, one rule handles everything, automatically inferring which syntax parser to use based on the file extension.
Note:
output.pathmust be an absolute path; writing a relative path directly will cause an error under an ESM config file. Because 2.0 recommends ESM config files,__dirnameis no longer available by default and must be declared manually viafileURLToPath—this is a pitfall many users encounter when upgrading.
3.4 What to watch out for when migrating from webpack
It's important to note that the 95% compatibility mainly covers common APIs and the mainstream ecosystem; deeply customized projects may still encounter compatibility issues during migration. A few common issues:
1. Custom webpack plugins
If your project has custom-written webpack plugins, you need to check if they use any APIs not implemented by Rspack. Most plugins' tapable hooks are compatible, but some edge-case APIs might not be covered.
2. Specific loader compatibility
Most common loaders (babel-loader, css-loader, style-loader, postcss-loader) are compatible. But some niche loaders might have issues; check the Plugin Compatibility List.
3. Module Federation
Rspack supports Module Federation, but @module-federation/runtime-tools became an optional dependency in 2.0. If you use Module Federation, you need to install it manually:
npm add @module-federation/runtime-tools
4. Output verification and gradual rollout strategy
Risk control is the top concern for large project migrations. A phased approach is recommended:
- Switch development environment first — Switch to Rspack in the dev environment first, verifying basic functions like HMR, proxies, and static assets.
- Dual-build comparison for production — Run both webpack and Rspack production builds simultaneously, comparing output size, sourcemaps, and core functionality regression.
- Gradual full rollout — After confirming no differences, gradually switch the CI pipeline to Rspack.
4. Which one to choose?
| Dimension | Rspack 2.0 | Vite | Turbopack |
|---|---|---|---|
| Underlying Language | Rust | Rust (Rolldown for production build) | Rust |
| webpack Compatibility | ⭐⭐⭐⭐⭐ (Common APIs ~95%) | ❌ | ❌ |
| Dev Experience | ⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ (Next.js only) |
| Large Project Build | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐⭐ |
| Output Quality / Tree Shaking | ⭐⭐⭐⭐⭐ (Strong for both CJS + ESM) | ⭐⭐⭐⭐⭐ (Higher ceiling for pure ESM projects, weaker for CJS-compatible projects than Rspack) | ⭐⭐⭐⭐ |
| Ecosystem Maturity | ⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐ |
| RSC Support | ✅ (Experimental, basic pipeline) | ✅ (Via frameworks) | ✅ (Next.js) |
| Suitable Scenarios | webpack migration / Large projects | Small to medium SPAs | Next.js projects |
| Maintainer | ByteDance | VoidZero (Evan You) | Vercel |
This is not to say Rolldown cannot handle large projects; its underlying performance is already excellent. The difference lies in ecosystem compatibility: Vite's output build follows the Rollup/Rolldown pipeline and cannot directly reuse webpack's loaders and plugins. If your large project heavily depends on the webpack ecosystem (e.g., custom compiler hooks, specific webpack loaders), Rspack is the smoother choice.
A pragmatic selection principle:
- New project, small to medium SPA → Vite, best dev experience, Rolldown core is mature.
- Existing webpack project → Rspack, lowest migration cost, ~95% compatibility with common APIs.
- Next.js project → Turbopack, native integration, first-class citizen for Next.js.
- Micro-frontends / Deeply customized webpack hooks → Rspack, ModuleFederation and compiler hooks ecosystem are irreplaceable.
- Unsure / Want a fallback → Rspack, because its webpack compatibility gives you the biggest safety net.
5. Q&A
Q1: What is the core difference between Rspack and webpack?
The core difference is the underlying implementation language. webpack is written in JavaScript, limited by Node.js's single-threaded model and V8 engine GC pressure; Rspack's core is written in Rust, leveraging Rust's memory safety, zero-cost abstractions, and multi-threading capabilities. In terms of performance, the first build without cache is about 5-10 times faster than webpack, and persistent cache hit scenarios can be over 20 times faster. At the same time, Rspack has about 95% compatibility with webpack's common APIs and mainstream ecosystem, significantly reducing migration costs.
Q2: Rspack 2.0 has switched to pure ESM packages. What impact does this have on users?
For most projects, there is no impact. Node.js 22.12+ has stable support for loading ESM modules via
require()in CommonJS, and 20.19+ has experimental support. So even if your project is CommonJS, you can use Rspack 2.0 normally as long as the Node version requirement is met.⚠️ But there is one edge case: if an ESM module internally uses top-level await,
require()will directly throw anERR_REQUIRE_ASYNC_MODULEerror, which cannot be bypassed. Currently, Rspack 2.0's core packages themselves do not use top-level await, but if a third-party ESM dependency you introduce in your config file contains top-level await, the CJS project will break. Solution: renamerspack.config.jstorspack.config.mjs, or set"type": "module"inpackage.jsonto switch the project to ESM mode.Additionally, Rspack 2.0 no longer supports Node.js 18; the minimum requirement is 20.19+ or 22.12+.
Q3: What is the #__NO_SIDE_EFFECTS__ annotation? What problem does it solve?
This is an annotation defined in the Bundler Annotations specification, used to mark a function as side-effect-free. When the return value of a call to a marked function is unused, tree shaking can safely remove that call. This solves a long-standing problem: cross-module function calls, even if the return value is unused, could not be deleted by bundlers because the function body might have side effects (like modifying global variables, making requests). With this annotation, the bundler knows it can safely delete them.
Q4: Why is Rspack 2.0's dependency slimming important?
Two reasons. First, fewer dependencies mean faster installation and smaller disk usage—
@rspack/dev-serverdropping from 15MB to 1.4MB is a tangible improvement. Second, fewer dependencies mean lower supply chain risk. Incidents where a transitive dependency in the npm ecosystem is poisoned or accidentally broken are not uncommon. Rspack significantly reduces this risk by bundling dependencies into the release artifact (vendorization) and replacing third-party packages with Node.js native APIs. However, note that vendorization means you cannot directly upgrade transitive dependency versions vianpm overridesto fix CVE vulnerabilities, requiring reliance on official releases;patch-packagecan work on the bundled artifact, but maintenance costs are extremely high and not recommended for enterprise projects.
Q5: In what scenarios should I choose Rspack over Vite?
Two typical scenarios: 1) You have a large amount of existing webpack code—including custom loaders, plugins, config files, or a heavy reliance on webpack-specific capabilities (like Module Federation, specific compiler hooks). Migrating to Vite means rewriting the entire build pipeline, costing far more than switching to Rspack; 2) Micro-frontend host applications—needing to coordinate with multiple sub-applications built on webpack, where Rspack's Module Federation ecosystem compatibility is a hard requirement.
Conversely, if it's a small to medium SPA starting from scratch, without webpack historical baggage, Vite's zero-config dev experience remains the top choice.
References:
- Rspack 2.0 Release Announcement - Official Blog
- Rspack 2.0 Upgrade Guide
- InfoQ: RSPack 2.0 Performance Improvements, Leaner Dependencies, and ESM Core
- rspack-react-10k-benchmark
That's all for this section. If you found it valuable, feel free to follow for more great content. See you next time~