31/01/2025 14 Minutes read

A Pragmatic Look at Rspack’s Features, Performance, and Benefits

Image from Unsplash

Introduction

What is this about?

The objective of this study is to assess the feasibility of adopting Rspack as a build system for some ekino-France React projects, evaluating its advantages, limitations, and suitability compared to Webpack, Vite, and SWC. The focus includes key performance metrics, migration complexity, compatibility with current workflows, and long-term maintainability.

This study is a continuation of Beyond Webpack: esbuild, Vite, Rollup, SWC, and Snowpack, where we examined modern Webpack alternatives for React projects. We previously selected Vite for CSR (Client Side Rendering) projects due to its speed and efficiency (esbuild + Rollup) and Next.js for SSR (Server Side Rendering) projects (SWC + Webpack), optimizing both CSR and SSR workflows.

However, legacy CSR projects using Webpack and non-ESM modules pose challenges where Vite may not be suitable. The study examines whether Rspack is a viable alternative, balancing performance enhancements with seamless migration while addressing Webpack’s complexity.

Key areas of evaluation include:

  • Performance Evaluation: Build speed, cold start times, incremental compilation, and HMR performance.
  • Compatibility Analysis: Integration with Webpack configurations, existing plugins, and loaders.
  • Migration Feasibility: Effort required to transition from Webpack, potential blockers, and configuration adjustments.
  • Ecosystem Maturity: Plugin and loader ecosystem, release stability, and adoption trends.
  • Use Case Alignment: Identifying project types at ekino (e.g., SPAs, SSR applications, micro-frontends) that would benefit the most.
  • Potential Risks: Limitations, missing features, or drawbacks affecting adoption.
  • Long-Term Viability: Rspack’s roadmap, maintainability, and industry alignment.

This study serves as a technical and pragmatic assessment to determine whether Rspack is a viable alternative for some of ekino’s legacy CSR projects.

POC Application: Benchmarking Webpack vs. Rspack

To compare Rspack fairly, we will use the same POC application tested with Webpack, esbuild, Rollup, and SWC. This ensures a consistent evaluation of performance, build speed, and developer experience.

While Webpack provides powerful features like declarative configuration, extensibility, HMR, code splitting, tree shaking, and module federation, its build performance and complex migration challenges remain key drawbacks.

Our goal is to assess whether Rspack can retain Webpack’s advantages while offering better efficiency. For reference, we will use the following Webpack benchmarks:

🔳 Bundle analyze:

// webpack (gzip)
All (170.73 KB)
webpack-frontend.js (141.08 KB)
webpack-frontend-389.chunk.js (15.88 KB)
webpack-frontend-595.chunk.js (7.15 KB)
webpack-frontend-930.chunk.js (4.52 KB)
webpack-frontend-615.chunk.js (1.46 KB)
webpack-frontend-553.chunk.js (646 B)

// webpack(brotli)
All (147.87 KB)
webpack-frontend.js (121.38 KB)
webpack-frontend-389.chunk.js (14.32 KB)
webpack-frontend-595.chunk.js (6.3 KB)
webpack-frontend-930.chunk.js (4.05 KB)
webpack-frontend-615.chunk.js (1.26 KB)
webpack-frontend-553.chunk.js (562 B)

// webpack + swc (gzip)
All (169.06 KB)
swc-frontend.js (140.07 KB)
swc-frontend-389.chunk.js (15.88 KB)
swc-frontend-431.chunk.js (7.16 KB)
swc-frontend-930.chunk.js (4.52 KB)
swc-frontend-671.chunk.js (832 B)
swc-frontend-887.chunk.js (632 B)

// webpack + swc (brotli)
All (146.38 KB)
swc-frontend.js (120.42 KB)
swc-frontend-389.chunk.js (14.32 KB)
swc-frontend-431.chunk.js (6.33 KB)
swc-frontend-930.chunk.js (4.06 KB)
swc-frontend-671.chunk.js (727 B)
swc-frontend-887.chunk.js (563 B)

🔳 The required time for build is:

⚡ Done in 289ms // ESBuild
created dist in 8.2s // Rollup
webpack 5.84.1 compiled with 2 warnings in 3365 ms (3,3365 s) // webpack
webpack 5.84.1 compiled with 2 warnings in 3368 ms // webpack + SWC

🔳 Lighthouse results for production mode:

// webpack
Performance: 95, Best Practices: 100

// ESBuild
Performance: 96, Best Practices: 100

// Rollup
Performance: 100, Best Practices: 100

// ESBuild
Performance: 99, Best Practices: 100

By maintaining the same test conditions, we can fairly compare Rspack against Webpack and determine if it can offer tangible improvements. Let’s see if Rspack can do better! 🚀

Understanding Rspack’s Internal Architecture

Tracing Rspack’s Execution from index.js to bundle.js

We will trace how Rspack compiles an index.js file into bundle.js, integrating the links to key files involved in the process.

🔵 Step 1: CLI Entry Point rspack.js

const { RspackCLI } = require("../dist/index");

async function runCLI() {
const cli = new RspackCLI();
await cli.run(process.argv);
}

runCLI();

🔸 What Happens Here?

  • User runs the Rspack CLI (rspack build or rspack serve).
  • The entry script (rspack.js) is executed.
  • It imports RspackCLI, the command-line interface module.
  • It runs runCLI(), which processes command-line arguments.

👉 Key Takeaway: rspack.js is the entry point of Rspack’s CLI, initializing and running commands.

🔵 Step 2: Parsing Commands (build, serve, etc.) cli.ts

export class RspackCLI {
async run(argv: string[]) {
this.program.strictCommands(true).strict(true);
this.registerCommands();
await this.program.parseAsync(hideBin(argv));
}

async registerCommands() {
const builtinCommands = [new BuildCommand(), new ServeCommand()];
for (const command of builtinCommands) {
command.apply(this);
}
}
}

🔸 What Happens Here?

  • Command Parsing: rspack-cli registers available commands (build, serve, etc.).
  • Command Dispatching: Calls the appropriate command handler (build.ts for rspack build).
  • Middleware Processing: Environment variables are normalized.

👉 Key Takeaway: The CLI reads user input (rspack build), validates it, and executes the corresponding command.

🔵 Step 3: Handling ( rspack build )build.ts

const compiler = await cli.createCompiler(
rspackOptions,
"build",
errorHandler
);

compiler.run((error, stats) => {
compiler.close(closeErr => {
if (closeErr) logger.error(closeErr);
errorHandler(error, stats);
});
});

🔸 What Happens Here?

  • Reads user’s configuration (rspack.config.js).
  • Creates a compiler instance using createCompiler().
  • Runs the compiler, triggering the bundling process.

👉 Key Takeaway: This initiates the compilation process by creating and running the compiler.

🔵 Step 4: Creating the Compiler rspack.ts

function createCompiler(userOptions: RspackOptions): Compiler {
const options = getNormalizedRspackOptions(userOptions);
applyRspackOptionsBaseDefaults(options);
assert(!isNil(options.context));
const compiler = new Compiler(options.context, options);

new NodeEnvironmentPlugin({
infrastructureLogging: options.infrastructureLogging
}).apply(compiler);

if (Array.isArray(options.plugins)) {
for (const plugin of options.plugins) {
if (typeof plugin === "function") {
plugin.call(compiler, compiler);
} else if (plugin) {
plugin.apply(compiler);
}
}
}
applyRspackOptionsDefaults(compiler.options);
new RspackOptionsApply().process(compiler.options, compiler);
return compiler;
}

🔸 What Happens Here?

  • Parses & normalizes user options (e.g., entry, output, loaders).
  • Initializes a Compiler instance.
  • Applies plugins like RspackOptionsApply for transformations.
  • Triggers compilation hooks (environment, afterEnvironment, initialize).
if (Array.isArray(options.plugins)) {
for (const plugin of options.plugins) {
if (typeof plugin === "function") {
plugin.call(compiler, compiler);
} else if (plugin) {
plugin.apply(compiler);
}
}
}

👉 Key Takeaway: Plugins register their modifications here, affecting the compilation process.

🔵 Step 5: Running Plugins Before Compilation Compiler.ts

class Compiler {
compile(callback) {
this.hooks.beforeCompile.callAsync({}, err => {
if (err) return callback(err);
this.hooks.compile.call({});
this.hooks.afterCompile.call({});
callback();
});
}
}

🔸 What Happens Here? Hooks like beforeCompile, compile, and afterCompile allow plugins to modify the AST (Abstract Syntax Tree).

👉 Key Takeaway: Plugins can optimize, transform, or modify the compilation output.

🔵 Step 6: Running Loaders (index.js -> Transformed Code) loader-runner/index.ts

async function runLoaders(compiler, context) {
const loaderContext = { loaders: context.loaderItems.map(LoaderObject.__from_binding) };
while (loaderContext.loaderIndex < loaderContext.loaders.length) {
const currentLoader = loaderContext.loaders[loaderContext.loaderIndex];
await loadLoaderAsync(currentLoader);
const fn = currentLoader.normal;
[context.content] = await runSyncOrAsync(fn, loaderContext, [context.content]);
}
return context;
}

🔸 What Happens Here?

  • Runs each loader in sequence (e.g., Babel for JS, PostCSS for CSS).
  • Transforms the source code.
  • Passes processed content to the bundler.

👉 Key Takeaway: Loaders transform files before bundling (e.g., TypeScript → JavaScript).

🔵 Step 7: Running Built-in Plugins builtin-plugin/index.ts

🔸 Key Built-in Plugins:

  1. EntryPlugin → Handles entry points.
  2. DefinePlugin → Replaces global constants.
  3. SplitChunksPlugin → Optimizes chunk loading.
  4. CssModulesPlugin → Handles CSS Modules.
  5. JsLoaderRspackPlugin → Runs JavaScript transformations.
class EntryPlugin {
apply(compiler) {
compiler.hooks.entryOption.tap("EntryPlugin", (context, entry) => {
// Modify entry points dynamically
return true;
});
}
}

👉 Key Takeaway: Plugins inject features into the compilation pipeline.

🔵 Step 7: Chunking & Bundling (JavascriptModulesPlugin.ts) JavascriptModulesPlugin.ts

class JavascriptModulesPlugin {
apply(compiler) {
compiler.hooks.compilation.tap("JavascriptModulesPlugin", compilation => {
compilation.hooks.renderChunk.tap("JavascriptModulesPlugin", chunk => {
return `(()=>{ ${chunk.modules.map(m => m.code).join("n")} })()`;
});
});
}
}

🔸 What Happens Here?

  • Rspack chunks files into separate modules.
  • Applies tree shaking & optimizations.
  • Converts modules into a single JavaScript bundle.

👉 Key Takeaway: Optimizations like tree shaking are done here.

🔵 Step 8: Emitting bundle.js (Compiler.ts) Compiler.ts

class Compiler {
emitAssets(callback) {
this.hooks.emit.callAsync(this.assets, err => {
if (err) return callback(err);
this.outputFileSystem.writeFile(
this.outputPath,
this.assets["bundle.js"].source(),
callback
);
});
}
}

🔸 What happens here?

  • Plugins optimize the output.
  • The final bundle is written to disk.

👉 Key Takeaway: The final bundle.js is saved and ready to execute.

🔵 In summary, Rspack’s build pipeline consists of:

  • CLI Execution (rspack.js) → Initializes CLI.
  • Command Handling (cli.ts) → Dispatches rspack build.
  • Compiler Creation (rspack.ts) → Initializes plugins.
  • Compilation Lifecycle (Compiler.ts) → Runs compilation hooks.
  • Loaders (loader-runner/index.ts) → Transforms source files.
  • Chunking & Optimization (JavascriptModulesPlugin.ts) → Bundles modules.
  • Emission (Compiler.ts) → Writes final bundle.js.

Each step integrates Rust-based performance optimizations while maintaining Webpack compatibility.

Core Components of Rspack

Rspack is a high-performance Rust-based bundler designed to be compatible with Webpack while improving build speed, memory efficiency, and parallel execution.

Rspack architecture diagram

Below, we outline its key components and their roles in the bundling process.

🔵 Plugins :

🔵 Loaders :

  • Loaders process and transform non-JavaScript assets before they enter the bundling pipeline.
  • Loaders in Rspack operate in two modes: the pitching phase, which can alter the execution order or skip loaders entirely, and the normal phase, where the actual transformation of module content occurs.
  • Supports TypeScript, CSS, SCSS, images, and more, similar to Webpack but with better parallel execution.
  • Loaders execute in parallel via Rust’s multi-threaded architecture, reducing bottlenecks.
  • Supports both CommonJS & ESM natively, unlike Rollup, which prioritizes ESM.

🔵 Rust-Based Compilation Core:

One of Rspack’s biggest advantages is its Rust-powered architecture, making it significantly faster and more memory-efficient than Webpack.

  • Multi-threading: Unlike Webpack’s single-threaded model, Rspack leverages multi-core CPUs for parallel processing.
  • Lightning-fast parsing & transpilation: Uses SWC instead of Babel, making JavaScript/TypeScript ~20x faster to compile.
  • Reduced memory overhead: Rust eliminates the need for garbage collection (GC), leading to better stability and lower memory usage.

🔵 Default Optimization:

Rspack provides built-in optimizations to enhance performance and reduce bundle size:

  • Tree Shaking: Efficiently removes dead code, minimizing output.
  • Code Splitting: Supports SplitChunksPlugin and entry-based chunking, enabling optimized lazy loading.
  • Prefetching & Preloading: Uses inline directives similar to Webpack for smarter resource loading.
  • Minification: Leverages SwcJsMinimizerRspackPlugin for JavaScript and LightningCssMinimizerRspackPlugin for CSS, ensuring optimized production builds.

These optimizations improve performance, reduce load times, and keep bundles lightweight by default.

🔵 Hot Module Replacement (HMR) :

  • Just like Webpack, Rspack enables live updates to JavaScript & CSS without requiring a full page reload.
  • Thanks to Rust’s parallel execution, HMR is faster and more efficient, making real-time development smoother.

🔵 Incremental Compilation in Rspack :

Rspack optimizes Hot Module Replacement (HMR) with affected-based incremental compilation, reducing update complexity from O(project) to O(change) by re-executing only affected tasks.

  • Pass-Based Bundler with Early Cutoff: Unlike Webpack, Rspack introduces a Rebuilder to track dependencies and skip redundant processing.
  • Minimizing Redundant Work: It detects changes at different stages and updates only impacted tasks.
  • Inspired by Self-Adjusting Computation: Ensures efficient propagation of updates through the dependency graph.

This results in faster rebuilds and highly efficient HMR, focusing only on modified parts of the project.

Building types
Bundlers comparaison

🔵 Cache Optimization:

Rspack speeds up builds by caching snapshots and intermediate products, reusing them in subsequent builds to reduce redundant work and improve performance.

  • Persistent Cache (Experimental): Introduced in Rspack v1.2, this feature stores and reuses compilation artifacts to accelerate hot startup times.
  • Performance Gains: Can improve build speed by up to 250%, especially in large projects.

⚠️ Note: Persistent cache is still experimental and currently applies only to the make stage of the build process.

Persistent cache performance gains

🔵 Module Federation : Module Federation enables JavaScript applications to share code dynamically, supporting micro-frontends and independent team workflows. Rspack provides native support with performance improvements over webpack.

🔸 Progressive Migration: Rspack’s federation runtime is compatible with webpack, allowing both to share dependencies at runtime. This enables a smooth transition without a full rebuild.

🔸 Rspack Module Federation Versions:

  • v2.0 (Advanced) — Type hints, Chrome DevTools, preloading, and runtime plugins.
  • v1.5 (Built-in) — Adds runtime plugin support while maintaining Webpack compatibility.
  • v1.0 (Legacy) — Webpack-compatible but deprecated.

Rspack’s Module Federation delivers faster, scalable micro-frontends while staying webpack-compatible.

Now that we’ve explored Rspack’s architecture and core features, it’s time to put theory into practice — first, we’ll create a new Rspack application from scratch, and then we’ll dive into migrating an existing webpack project to Rspack.

Experimenting with Rspack

New application with Rspack: development mode

Rspack offers a Webpack-compatible configuration while significantly improving build performance. If you’re familiar with Webpack, transitioning to Rspack will feel intuitive.

To generate a new Rspack project with the necessary files (rspack.config.mjs, index.html, etc.), run:

pnpm create rspack@latest

Like Webpack, Rspack uses an entry field to define the main file where the bundling process begins:

entry: {
main: './src/index.js',
},

Rspack provides the HtmlRspackPlugin (similar to Webpack’s HtmlWebpackPlugin) to generate an HTML file and inject bundled assets:

plugins: [
new rspack.HtmlRspackPlugin({
template: './index.html',
}),
isDev ? new RefreshPlugin() : null,
].filter(Boolean),

Rspack natively supports SWC as the default loader (builtin:swc-loader), removing the need for Babel in most cases.

For React 18: We only need SWC, with no Babel plugin.

For React 19: Since React 19 requires the React Compiler, we must use Babel alongside SWC:

      
...
{
test: /.jsx$/,
use: [
{
loader: 'builtin:swc-loader',
options: {
// SWC options for JSX
},
},
{ loader: 'babel-loader' },
],
},
],
...

babel.config.js file:

const ReactCompilerConfig = {
/* ... */
};

module.exports = function () {
return {
plugins: [
['babel-plugin-react-compiler', ReactCompilerConfig], // must run first!
'@babel/plugin-syntax-jsx',
],
};
};

💡 While relying on Babel for React 19 may not be ideal, it remains necessary due to the compiler being exposed via a Babel plugin.

Since we’re using React Router, we need to enable history fallback to support client-side routing (rspack.config.mjs ):

devServer: {
historyApiFallback: true,
},

To run and build the project, add the following scripts to package.json:

"start": "cross-env NODE_ENV=development rspack serve",
"build": "cross-env NODE_ENV=production rspack build",

Below are the results of the local launch:

<i> [webpack-dev-server] Project is running at:
<i> [webpack-dev-server] Loopback: http://localhost:9001/
<i> [webpack-dev-server] On Your Network (IPv4): http://10.180.152.212:9001/
<i> [webpack-dev-server] On Your Network (IPv6): http://[fe80::1]:9001/
<i> [webpack-dev-server] Content not from webpack is served from '/Users/hela.ben-khalfallah/Desktop/github_workspace/frontend-beyond-webpack/rspack-app/public' directory
<i> [webpack-dev-server] 404s will fallback to '/index.html'
● ━━━━━━━━━━━━━━━━━━━━━━━━━ (100%) emitting after emit
Rspack compiled successfully in 258 ms
Rspack lighthouse analysis — local (Image by the author)

You can find here the full Rspack application.

Now, let’s move on to Production Mode! 🌟

New application with Rspack: Production mode

Rspack’s CLI provides built-in bundle analysis via the –analyze flag, leveraging webpack-bundle-analyzer:

"analyze": "cross-env NODE_ENV=production rspack build --analyze",

By default, Rspack compresses output using Gzip:

Bundle analyze with Gzip compression — rspack (Image by the author)

Rspack does not have a dedicated Brotli plugin, but we can reuse Webpack’s approach. Since it maintains Webpack compatibility, Brotli compression can be added using compression-webpack-plugin:

import zlib from 'zlib';
import CompressionPlugin from 'compression-webpack-plugin';
import { BundleAnalyzerPlugin } from 'webpack-bundle-analyzer-brotli';

...

plugins: [
...
!isDev &&
new CompressionPlugin({
filename: '[path][base].br',
algorithm: 'brotliCompress',
test: /.(js|css|html|svg)$/,
compressionOptions: {
params: {
[zlib.constants.BROTLI_PARAM_QUALITY]: 11,
},
},
threshold: 10240,
minRatio: 0.8,
}),
!isDev &&
new BundleAnalyzerPlugin({
analyzerPort: 9003,
}),
...

This perfectly works and the result is as follow:

Bundle analyze with Brotli compression — rspack (Image by the author)
Bundle analyze with Gzip compression — rspack (Image by the author)

Comparing the output size to full Webpack application:

// webpack (gzip)
All (170.73 KB)
webpack-frontend.js (141.08 KB)
webpack-frontend-389.chunk.js (15.88 KB)
webpack-frontend-595.chunk.js (7.15 KB)
webpack-frontend-930.chunk.js (4.52 KB)
webpack-frontend-615.chunk.js (1.46 KB)
webpack-frontend-553.chunk.js (646 B)

// webpack(brotli)
All (147.87 KB)
webpack-frontend.js (121.38 KB)
webpack-frontend-389.chunk.js (14.32 KB)
webpack-frontend-595.chunk.js (6.3 KB)
webpack-frontend-930.chunk.js (4.05 KB)
webpack-frontend-615.chunk.js (1.26 KB)
webpack-frontend-553.chunk.js (562 B)


// rspack (gzip)
All (169.39 KB)
main.js (140.35 KB)
410.js (15.72 KB)
62.js (6.77 KB)
764.js (4.5 KB)
207.js (868 B)
100.js (643 B)
220.js (596 B)

// rspack(brotli)
All (147.11 KB)
main.js (121.16 KB)
410.js (14.16 KB)
62.js (5.95 KB)
764.js (4.04 KB)
207.js (759 B)
100.js (566 B)
220.js (513 B)

The size difference is minimal, but Rspack achieves significantly faster build times:

⚡ Done in 289ms // ESBuild
created dist in 8.2s // Rollup
webpack 5.84.1 compiled with 2 warnings in 3365 ms (3,3365 s) // webpack
webpack 5.84.1 compiled with 2 warnings in 3368 ms // webpack + SWC
Rspack compiled with 2 warnings in 1.02 s // Rspack

Rspack’s Rust-based architecture significantly reduces build times while maintaining bundle size optimizations.

To further analyze the bundle and build performance, we can use Rsdoctor:

pnpm add @rsdoctor/rspack-plugin -D

Then, add the plugin to rspack.config.mjs:

import { RsdoctorRspackPlugin } from '@rsdoctor/rspack-plugin';

...

plugins: [
...

// Only register the plugin when RSDOCTOR is true, as the plugin will increase the build time.
process.env.RSDOCTOR &&
new RsdoctorRspackPlugin({
// plugin options
}),
...
].filter(Boolean),

...

Run:

"scripts": {
...
"doctor": "RSDOCTOR=true pnpm run build"
...

This generates a detailed diagnostic report including project configuration, compilation stats, and performance insights:

Rsdoctor report — Home Page (Image by the author)
Rsdoctor report — Bundle Details Page (Image by the author)

Now, let’s attempt to deploy this application in Netlify and see the results.


<!doctype html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<title>Rspack + React</title>
<script defer src="main.js"></script>
</head>
<body>
<div id="rspack-frontend-root"></div>
</body>
</html>
Lighthouse audit — rspack (Image by the author)

The Lighthouse audit confirms that Rspack delivers performance on par with Webpack.

You can find here the full Rspack application.

Migrating legacy application from Webpack to Rspack

You can find the source code of the existing Webpack application before migration to Rspack.

Now, let’s go step by step through the migration process.

1️⃣ Remove Webpack Configuration: Since Rspack maintains compatibility with Webpack, we start by removing the Webpack configuration folder and dependencies we no longer need:

  • Remove Babel Dependencies (for React 18).
  • Uninstall Webpack & Loaders (keeping only Brotli-related Webpack plugins).
Webpack configuration folder (Image by the author)
Dependencies cleaning (Image by the author)

2️⃣ Update Project Configuration: Next, we copy the Rspack configuration into the legacy project, ensuring compatibility while updating any required paths.

3️⃣ Update Build Scripts: Replace Webpack-related scripts with their Rspack equivalents:

🚀 Before (webpack scripts)

"start": "webpack serve -c webpack/webpack.dev.config.js --progress --color",
"build": "webpack -c webpack/webpack.prod.config.js --progress --color",
"analyze": "webpack -c webpack/webpack.debug-prod.config.js --progress --color",

🛠️ After (rspack scripts)

"start": "cross-env NODE_ENV=development rspack serve -c ./rspack/rspack.config.dev.mjs",
"build": "cross-env NODE_ENV=production rspack build -c ./rspack/rspack.config.prod.mjs",
"analyze": "BUNDLEANALYZE=true cross-env NODE_ENV=production rspack build -c ./rspack/rspack.config.prod.debug.mjs --analyze",
"doctor": "RSDOCTOR=true rspack build -c ./rspack/rspack.config.prod.debug.mjs",

4️⃣ Install Required Dependencies: To complete the migration, install Rspack dependencies:

  "devDependencies": {
...
"@rsdoctor/rspack-plugin": "^0.4.13",
"@rspack/cli": "=1.2.2",
"@rspack/core": "=1.2.2",
"@rspack/plugin-react-refresh": "1.0.0",
"cross-env": "=7.0.3"
...

5️⃣ Validate Migration Success: Finally, we test all scripts to ensure the migrated application functions correctly:

✔️ Start: OK (No regression or exceptions)
✔️ Build: OK (Brotli & Gzip correctly generated)
✔️ Analyze: OK (Bundle size remains unchanged)
✔️ Doctor: OK (No issues detected)

The migration process was seamless! With minimal changes, Rspack delivered faster build times while maintaining full compatibility. 🚀

Review and summary

Based on our initial evaluation objectives, here’s how Rspack performed:

1️⃣ Performance Evaluation:

  • Build speed: Rspack consistently outperforms Webpack (~3x faster in most cases).
  • Cold start & incremental builds: Faster than Webpack, but persistent caching is still evolving.
  • HMR performance: Affected-based updates make HMR more efficient.

2️⃣ Compatibility Analysis:

  • Supports most Webpack configurations with minor path and script updates.
  • Works with existing plugins/loaders, except for some needing Webpack-specific APIs.
  • Compression: Gzip is built-in; Brotli requires compression-webpack-plugin.

3️⃣ Migration Feasibility:

  • Minimal effort required: Removing Webpack dependencies and adjusting config paths is enough.
  • No major regressions: Bundle output remains comparable to Webpack.
  • Tooling adjustments: Some diagnostic tools (e.g., @rsdoctor/rspack-plugin) help maintain parity.

Having validated Rspack’s core performance, compatibility, and migration process, the next phase will focus on ecosystem maturity (plugins, loaders, debugging tools), potential risks (missing features, debugging challenges), and use case suitability for different project types.

Evaluating Rspack for ekino Projects

Advantages of Adopting Rspack

  • Significant Build Speed Improvements — Rspack outperforms Webpack in both cold starts and incremental builds, reducing developer wait times.
  • Rust-Based Performance Optimizations — Efficient memory management and multi-threading result in lower resource usage.
  • Seamless Webpack Compatibility — Existing Webpack projects can migrate progressively without full rewrites.
  • Built-in Optimization Features — Tree shaking, code splitting, and caching improve runtime performance.
  • Module Federation Support — Enables scalable micro-frontends and interoperability with Webpack-based applications.
  • First-Class CSS Support — Rspack provides built-in CSS handling with native CSS support, CssExtractRspackPlugin for optimized extraction, and compatibility with PostCSS, Sass, Less, and Tailwind CSS, ensuring a seamless styling experience.

Potential Limitations and Risks

  • Ecosystem Maturity — While Rspack offers strong Webpack compatibility, some Webpack plugins and loaders may not yet be fully supported, requiring additional validation. Many widely used plugins such as html-webpack-plugin, mini-css-extract-plugin, and copy-webpack-plugin have direct replacements (html-rspack-tags-plugin, CssExtractRspackPlugin, and CopyRspackPlugin, respectively). Additionally, certain Webpack plugins are only partially compatible (e.g., image-minimizer-webpack-plugin, webpack-assets-manifest), while others remain incompatible (e.g., @cypress/webpack-preprocessor, circular-dependency-plugin, pnp-webpack-plugin).
  • Community Adoption and Support — Compared to Webpack, Rspack has a smaller community and fewer third-party integrations, which could impact troubleshooting and long-term support.
  • SSR Support is Not Fully Mature — While Rspack integrates with the Modern.js framework and covers many SSR capabilities, it does not yet provide full parity with Webpack’s SSR features. Additional testing is needed to assess its suitability for complex SSR applications.
  • Integration with Third-Party Tools — Certain tools that integrate well with Webpack, such as Storybook, may not yet be fully compatible with Rspack, potentially requiring workarounds or custom configurations.
  • React 19 Compiler Requires Custom Configuration — The React Compiler, introduced in React 19, currently only supports Babel compilation, which can slow down build performance. To use it with Rspack, developers must manually configure babel-loader alongside Rspack’s builtin:swc-loader, increasing complexity. This additional setup makes Rspack integration more involved compared to Webpack, especially for projects that require optimal build performance.

Thanks to the close collaboration and parallel iteration of the Modern.js framework and Rspack, Modern.js Rspack mode has covered 85% of the framework’s capabilities, supporting SSR, BFF, micro front-end scenarios, and aligning with TypeScript type checking, code compatibility detection and other features. — https://rspack.dev/blog/announcing-0-2#modernjs-framework

Use Case Analysis for ekino

Rspack effectively addresses two key Client-Side Rendering (CSR) scenarios at ekino:

1️⃣ Migrating Legacy Webpack Projects (Including Module Federation)

  • Ideal for projects reliant on CommonJS and not ESM-compatible.
  • Provides a seamless migration path when Vite or Next.js are not viable.
  • Maintains Webpack compatibility while significantly improving build performance.
  • Supports Module Federation, allowing progressive migration between Webpack and Rspack without full rewrites.

2️⃣ New Micro-Frontend Projects with Module Federation (CSR Only)

  • First-class support for Module Federation, making it ideal for scalable micro-frontends.
  • Enables runtime dependency sharing between Webpack and Rspack applications.
  • Offers faster builds and lower overhead compared to Webpack’s federation.

🚩 Limitations: SSR support is not fully mature.

💡 Recommendation:

  • For legacy Webpack projects, Rspack is the best migration path, especially when Module Federation is used.
  • For new CSR-based micro-frontends, Rspack provides robust Module Federation support and superior performance over Webpack.

✳️ Based on our evaluations, we categorize build tools according to project type and compatibility needs:

✔️ Vite for CSR (ESM-Compatible Projects) — Best for new ESM-based projects with modern frameworks.

✔️ Rspack for Webpack Migration (Non-ESM Projects) — Ideal for legacy Webpack projects needing a seamless transition, especially with Module Federation.

✔️ Next.js for SSR/SSG/ISR — The go-to framework for server-side rendering, as Rspack’s SSR support is still evolving.

This structured approach ensures optimal performance, maintainability, and scalability based on project requirements.

Conclusion

Rspack presents a compelling alternative to Webpack, delivering faster builds, better resource efficiency, and seamless compatibility.

Our evaluation confirms that Rspack excels in two key areas:

  • Migrating legacy Webpack projects without breaking changes, particularly for applications that are not ESM-compatible.
  • Micro-frontend architectures with Module Federation, where Rspack’s optimizations offer significant performance gains over Webpack.

However, Rspack’s ecosystem is still evolving, and full SSR support remains a work in progress, making it less suited for SSR-heavy applications at this stage.

Our proposal is to follow a structured approach that is tailored to the project requirements:

  • Vite — Best for modern ESM-based CSR applications.
  • Rspack — Ideal for migrating legacy Webpack projects and micro-frontends with Module Federation.
  • Next.js — The preferred choice for SSR/SSG/ISR applications.

By aligning the right tool with each use case, we optimize build performance, maintainability, and developer experience across projects at ekino.

📌 For more insights, check out our previous study:
👉 Beyond Webpack: esbuild, Vite, Rollup, SWC, and Snowpack

Until our paths meet again — happy reading and coding! ♥️

Want to Connect? 
You can find me at ekino: https://github.com/helabenkhalfallah


Rspack: An Engineer’s Approach to Build System Innovation was originally published in ekino-france on Medium, where people are continuing the conversation by highlighting and responding to this story.