Rspack 2.3 lands SWC Next parser, new cache, and leaner bundles
Rspack 2.3 is now released! This update integrates the SWC Next parser, improving build speed, reducing memory usage and output size. It also brings an experimental new cache, supports per-loader caching of transform results, and includes built-in CSS improvements.
Notable changes are as follows:
- SWC Next integration
- Performance
- Performance optimization
- SplitChunks optimization
- New cache implementation
- New features
- Filter loaders by code content
- Built-in CSS
- Other improvements
- Ecosystem
- Rsbuild
- Rslib
- Rsdoctor
- Rstest
- Rspress
SWC Next Integration
SWC Next is a next-generation JavaScript compiler developed jointly by the SWC team and the Rstack team, and will become the next major version of SWC. It adopts a new parser architecture inspired by Yuku, with a flat ESTree AST at its core, balancing execution speed, memory efficiency, and JavaScript extensibility.
In benchmarks, SWC Next's native parsing speed is up to 3x that of SWC, and up to 10x when parsed via the JavaScript API:
Currently, we are gradually migrating SWC modules to this architecture and replacing the original implementation in Rspack. Rspack 2.3 has migrated the JavaScript parser to SWC Next, and with its more compact AST and memory layout, optimized its parsing and traversal.
The following is a comparison of parsing time and instruction count before and after integrating SWC Next into Rspack:
| Project | Parsing time | Instruction count |
|---|---|---|
| three.js | 23 ms → 20 ms (−14%) | 205 M → 155 M (−24%) |
| React 10k | 207 ms → 193 ms (−6%) | 1597 M → 1421 M (−11%) |
The next goal of SWC Next is to allow developers to write high-performance transform plugins in JavaScript. Currently, custom code transforms typically rely on Babel plugins or SWC Wasm plugins. We want to let developers enjoy Rust performance while keeping the flexibility of writing plugins in JavaScript.
SWC Next's flat AST supports efficient data exchange between Rust and JavaScript, making this possible. We expect to introduce SWC Next-based loaders and support JavaScript transform plugins in Rspack 2.4 or 2.5.
Performance
Performance optimization
In multi-agent parallel development scenarios, multiple build processes and dev servers may run simultaneously, and memory usage becomes a significant bottleneck. So we optimized memory in Rspack 2.3.0, reducing memory usage during build and development. Here is an RSS comparison from a real project:
| Scenario | Metric | 2.2.0 | 2.3.0 | Reduction |
|---|---|---|---|---|
| Build | Peak RSS | 28.3 GiB | 20.5 GiB | 27.4% |
| Build | Build end RSS | 25.3 GiB | 16.1 GiB | 36.5% |
| Dev | Startup peak RSS | 28.6 GiB | 19.0 GiB | 33.8% |
| Dev | Steady-state RSS | 28.9 GiB | 18.8 GiB | 34.8% |
Additionally, Rspack 2.3 adds memory snapshot capability based on allocative, helping developers locate memory hotspots during builds. If your project has high memory usage, follow the memory diagnostic guide to collect and submit snapshots, and we will help analyze and optimize.
Thanks to SWC Next, memory optimizations, and other micro-optimizations, overall build performance in Rspack 2.3.0 also improved. In benchmarks, build performance is about 5% faster than 2.2.0; in some large projects, we observed up to 20% improvement.
SplitChunks optimization
Large projects often contain multiple pages or entries that may share many modules. If these modules are repeatedly bundled into different output files, output size increases and users may re-download the same code.
To address this, Rspack 2.3 introduces several optimizations to reduce duplicate modules and generate leaner output:
- New
splitChunks.dedupDepthidentifies more extractable shared module combinations across pages and entries, reducing duplicate bundling. This optimization is enabled by default in production mode (dedupDepth: 1). - Support for
splitChunks.minRemainingSize, which avoids leaving too-small output files when splitting code, reducing unnecessary file requests and aligning output behavior with webpack. - Optimized module deduplication during code splitting, reusing existing modules more fully and reducing duplicate code in output files.
Here is a Brotli-compressed size comparison of two large real projects with dedupDepth: 1 enabled:
| Project | Before | After | Savings | Reduction |
|---|---|---|---|---|
| Project A (client JS/CSS) | 23.2 MB | 22.7 MB | 550 KB | 2.4% |
| Project B (client JS/CSS) | 40.0 MB | 36.0 MB | 4.0 MB | 9.9% |
New cache implementation
Rspack 2.3 introduces an experimental new cache, focused on reducing disk usage and write overhead of cache files, and improving reliability of cache recovery. This improvement targets three problems with the old cache:
- Large cache files and complex recovery: The old cache saved the intermediate state of the entire build, including dependency relationships between modules. These data were large and error-prone when restoring complex state. The new cache instead saves the results of individual build tasks like modules, reducing the amount of data to store and the risk of recovery errors.
- Cache writes blocking builds: The old cache needed to convert data to a disk-writable format during compilation, adding wait time. The new cache arranges these tasks during idle time or at the end of the build, reducing blocking of the compilation process.
- Hard to reuse between dev and production: The old cache was tightly coupled with incremental builds in dev mode, and some mechanisms were hard to use for production builds. The new cache separates the two and unifies management of in-memory and disk caches, making it easier to reuse results across build scenarios and clean up unneeded caches.
In benchmarks, the new cache reduces disk usage by about 79%, peak memory during cold builds by about 16%, but hot build time increases slightly:
The new cache is disabled by default and can be enabled via experiments.newCache:
// rspack.config.mjs
export default {
cache: {
type: 'persistent',
},
experiments: {
newCache: true,
},
};
We will continue optimizing performance in subsequent versions and plan to enable the new cache by default in a future minor release.
loader cache
The new cache supports enabling caching for specific loaders, reusing their transform results.
Compared to the overall persistent cache, loader cache is finer-grained and allows each loader to have its own invalidation conditions. So even if changing Rspack config invalidates the overall cache, as long as a loader's input, config, and related dependencies haven't changed, its cache can still be reused. When switching between dev and build modes, these caches can also be reused.
This is especially helpful for loaders like babel-loader and less-loader that have high overhead, avoiding re-transform of unchanged code and reducing build time.
The following config enables the new persistent cache and turns on caching for babel-loader:
// rspack.config.mjs
export default {
cache: {
type: 'persistent',
},
experiments: {
newCache: true,
},
module: {
rules: [
{
test: /\.[cm]?[jt]sx?$/,
use: [
{
loader: 'babel-loader',
cache: true,
},
],
},
],
},
};
In a test with approximately 10,000 mock modules processed by babel-loader, enabling loader cache reduces build time to about 11–12 seconds:
| Parallel loaders | Cache off | Cache on | Speedup |
|---|---|---|---|
| Off | 63.8 s | 11.2 s | 5.7x |
| On | 28.5 s | 11.9 s | 2.4x |
New features
Filter loaders by code content
New module.rules[].use.codeFilter can skip unnecessary loader transforms based on code content. For example, only transform files containing customMacro() calls:
// rspack.config.mjs
export default {
module: {
rules: [
{
test: /\.[cm]?[jt]sx?$/,
use: [
{
loader: './custom-macro-loader.js',
codeFilter: /\bcustomMacro\s*\(/,
},
],
},
],
},
};
Rspack's core is written in Rust, and calling a JavaScript loader incurs cross-language communication overhead, even if the code doesn't need transformation. codeFilter checks the code on the Rust side ahead of time and only calls the loader when transformation is needed, reducing cross-language communication.
In a test with 10,000 modules where only 10% needed transformation, codeFilter avoided 90% of unnecessary loader calls, reducing full build time from 622 ms to 481 ms, about 23% faster.
Built-in CSS
This update improves support for more CSS scenarios, including CSS Modules scoping, composition declarations, value references, and nested selectors.
Standalone style entry
When publishing themes or style libraries independently, you usually only need to generate CSS files. Rspack now supports bundling a pure CSS entry into a standalone CSS file without generating extra JavaScript files. For example:
// rspack.config.mjs
export default {
entry: {
styles: './src/styles.css',
},
module: {
rules: [{ test: /\.css$/i, type: 'css/auto' }],
},
output: {
cssFilename: '[name].css',
},
};
This config outputs only one file: styles.css.
External resources
When CSS or images are served by external services like CDNs, you often want the browser to load them directly instead of bundling them into the output.
Rspack adds two external types, css-import and asset-url, to keep specified references in CSS as external resources. For example, configuring stylesheets and images as externals:
// rspack.config.mjs
export default {
externals: {
'normalize.css': 'css-import normalize.css',
'https://example.com/a.css': 'css-import https://example.com/a.css',
'https://example.com/a.png': 'asset-url https://example.com/a.png',
},
};
Reference these external resources in CSS:
/* styles.css */
@import 'normalize.css';
@import url('https://example.com/a.css');
.logo {
background-image: url('https://example.com/a.png');
}
The @import and url() above are output as-is and not bundled.
@import, url(), and image-set() in CSS also support rspackIgnore comments, letting you skip resource bundling on demand:
/* styles.css */
@import /* rspackIgnore: true */ url('./runtime-theme.css');
Other improvements
- Lazy compilation: Reduces duplicate builds triggered by saving code when lazy compilation is enabled, shortening wait time for updates.
- Native watcher: Provides more complete file time information, improving compatibility with libraries like
ts-loaderandvue-loader. - Native watcher: Supports functions for
watchOptions.ignored, and improves detection of new directories, file deletions, and recreations. - Plugin compatibility and types: Improves compatibility of plugin hooks with webpack, and completes TypeScript types for loaders, externals, and stats.
- WebAssembly: New
output.wasmStreamingFallbacksupports disabling non-streaming loading fallback.
Ecosystem
With the Rspack 2.3 release, Rsbuild, Rslib, Rsdoctor, Rstest, and Rspress in the Rstack ecosystem also bring recent updates.
Rsbuild
TanStack CLI supports Rsbuild
TanStack CLI now officially supports creating TanStack Start projects with Rsbuild. When creating a project, add --build-tool rsbuild to select Rsbuild. For example, create a React project:
npx @tanstack/cli@latest create my-app --framework React --build-tool rsbuild
create-rsbuild also adds a TanStack Start option, which automatically calls TanStack CLI to create the project. You can directly select that template with:
npx -y create-rsbuild@latest my-app -t tanstack-start-react
Lynx Rsbuild plugin
Lynx introduces @lynx-js/rsbuild-plugin, integrating Lynx-specific configuration, defaults, and build adaptations previously wrapped in Rspeedy into the pluginLynx plugin. Now Lynx applications can be built directly with Rsbuild and this plugin, without depending on Rspeedy's wrapper.
You can use the familiar rsbuild.config.ts and Rsbuild CLI, configure the project according to Rsbuild docs, and continue using existing Rsbuild and Lynx build plugins.
create-rsbuild also adds a Lynx option, which automatically calls @lynx-js/create-lynx to create a project. For example, create a ReactLynx project using TypeScript:
npx -y create-rsbuild@latest my-app -t lynx-ts
File size report improvements
Rsbuild's file size report adds Brotli compressed size statistics and supports configuring gzip and Brotli compression levels, making it easier to estimate transfer size according to the deployment environment's compression settings. To include Brotli compressed size, add:
// rsbuild.config.ts
export default {
performance: {
printFileSize: {
compressed: { type: 'brotli' },
},
},
};
Other improvements
- compact-hashed ID by default: Production builds now use
compact-hashedIDs by default, reducing output size with shorter and more stable module and chunk IDs. - Dev experience: When developing apps that include both client and server, frontend code changes can take effect in the browser faster.
- Public asset directory: Supports configuring
server.publicDirper environment, specifying public asset directories for different environments. - Web Worker: When
output.targetis'web-worker', you can useoutput.module: trueto generate ES module format output. - Host validation:
server.allowedHostsrestricts hostnames used to access the dev server and preview server. - Preview server: New
onClosePreviewServerhook lets plugins clean up when the preview server closes. - Plugin transform:
api.transformsupportscodeFilterto skip unnecessary transforms based on code content.
Rslib
Rslib 1.0 officially released
Rslib 1.0 is now officially released. See the Rslib 1.0 release blog for details.
Inline Wasm modules
Rslib now supports inlining Wasm binaries into JavaScript output via ?inline, eliminating the need to distribute and load separate .wasm files:
import { add } from './add.wasm?inline';
console.log(add(1, 2));
For more usage, see the Wasm guide.
TypeScript 7.1 support
Rslib 1.0.4 supports using TypeScript 7.1 Beta to generate type declarations and supports extending type generation with its new Content Mappers.
To use, install the corresponding mapper and register the file extensions it handles in tsconfig.json. For example, assuming an example-css-modules-mapper (sample package name) that handles CSS Modules is installed:
// tsconfig.json
{
"contentMappers": [
{
"package": "example-css-modules-mapper",
"extensions": [".module.css"]
}
]
}
Rslib automatically loads this mapper during type generation, generating type declarations from CSS Modules class names.
You can install TypeScript 7.1 Beta to try it:
pnpm add [email protected] -D
Rsdoctor
Rsdoctor 2.0 is officially released! The new version recommends using the Agent CLI for AI-assisted analysis, which can directly read build data and output structured results. It also enhances Rspack analysis, supports multi-Compiler reports, reduces installation size by 56%, analysis time by 57%, and dev-mode HMR time by about 47%.
Rstest
Browser mode API enhancements
Browser mode recently adds keyboard interaction and React hydration APIs, making it easier to test these behaviors in a real browser:
locator.pressSequentially()types text character by character and fires keyboard events for each character, suitable for testing auto-complete input fields and other controls that respond to individual key presses.@rstest/browser-react'srender()addshydrateandcreateRootOptionsoptions. You can hydrate existing server-rendered content in a specified container and pass React root options to test hydration behavior.
await page.getByLabel('Search').pressSequentially('rspack', { delay: 50 });
await expect.element(page.getByRole('listbox')).toBeVisible();
Screenshot testing
Rstest 0.12.4 adds visual regression testing in browser mode with the Playwright provider. Through toMatchScreenshot(), you can compare screenshots of elements or pages against saved baselines, catching visual changes that DOM assertions alone might miss.
// button.test.ts
import { page } from '@rstest/browser';
import { expect, test } from '@rstest/core';
test('button appearance', async () => {
document.body.innerHTML = '<button>Submit</button>';
await expect(
page.getByRole('button', { name: 'Submit' }),
).toMatchScreenshot();
await expect(page).toMatchScreenshot('full-page', { fullPage: true });
});
On comparison failure, Rstest saves the actual screenshot and a diff image for debugging. You can mask dynamic content, configure pixel difference tolerance, and update baselines with --update.
Rspress
Rspress 2.1.0 supports WebMCP by default, with built-in tools for site information queries, documentation search, and page navigation, letting agents in the browser interact directly with documentation sites.
To try it, run the following in a WebMCP-supported browser:
const tools = await document.modelContext.getTools();
const tool = tools.find(({ name }) => name === 'rspress_get_current_page');
const page = JSON.parse(await document.modelContext.executeTool(tool, {}));
console.log(page.markdown);
You can also register custom tools via the useWebMcp Hook to extend an agent's interaction with the site.
For more usage, see the WebMCP documentation.