跪拜 Guibai
← Back to the summary

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

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:

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:

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

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

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:

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.