Skip to main content

Rspack Integration

Shakapacker supports Rspack as an alternative assets bundler to Webpack. Rspack is a fast Rust-based web bundler with webpack-compatible API that can significantly speed up your build times.

📖 For configuration options, see the Configuration Guide

Version Compatibility​

Shakapacker supports Rspack v2 (^2.0.0) going forward. Rspack v1 is no longer a supported target for new Shakapacker releases.

Fresh installs use the supported Rspack v2 ranges from lib/install/package.json.

Rspack v2 note: Rspack v2 ships as a pure ESM package and requires Node.js 20.19.0+.

Why Rspack v2?​

  • Persistent cache with proper invalidation — Rspack v2 promotes persistent caching (cache.type: 'filesystem') from experimental to stable, with portable cache support (cache.portable) and read-only cache for CI (cache.readonly). This means fast rebuilds that survive process restarts and are properly invalidated when dependencies change.
  • Incremental compilation (stable) — The incremental option moves from experiments to a top-level config, signaling it's production-ready. Incremental builds skip unchanged work in the dependency graph.
  • Better tree shaking — CJS require() destructuring and variable property access are now tree-shaken, and Module Federation shares can be tree-shaken.
  • Unified target configuration — A single target setting now propagates defaults to SWC and LightningCSS automatically, eliminating redundant per-loader configuration.
  • Stricter export validation — exportsPresence defaults to 'error', catching missing or misspelled exports at build time instead of silently producing broken bundles.
  • React Server Components — Built-in RSC support for frameworks.
  • Performance — Dozens of Rust-level optimizations across every beta release (hash caching, regex fast paths, reduced allocations, rayon parallelism).

See the Rspack v2 breaking changes discussion for full details.

Installation​

shakapacker-rspack ships shakapacker as a direct dependency and declares @rspack/core, @rspack/cli, @rspack/dev-server, and rspack-manifest-plugin as required peer dependencies. npm 7+ auto-installs those peers, so npm users can install the managed Rspack stack with one command:

npm install shakapacker-rspack -D

npm <7, Yarn Classic, pnpm, and Yarn PnP users should keep app-imported packages explicit in package.json. The default generated rspack config imports shakapacker/rspack, so list the supplemental package, shakapacker, and the required peers together:

npm install shakapacker-rspack shakapacker @rspack/core @rspack/cli @rspack/dev-server rspack-manifest-plugin -D
# or
yarn add shakapacker-rspack shakapacker @rspack/core @rspack/cli @rspack/dev-server rspack-manifest-plugin -D
# or
pnpm add shakapacker-rspack shakapacker @rspack/core @rspack/cli @rspack/dev-server rspack-manifest-plugin -D

See packages/shakapacker-rspack/README.md for the full install reference and the v10.1 supplemental packages migration guide for swapping an existing rspack install over to the supplemental package.

Manual install (self-managed versions)​

If you prefer to manage @rspack/core, @rspack/cli, @rspack/dev-server, and rspack-manifest-plugin versions yourself, install them directly:

npm install @rspack/core @rspack/cli @rspack/dev-server rspack-manifest-plugin -D
# or
yarn add @rspack/core @rspack/cli @rspack/dev-server rspack-manifest-plugin -D
# or
pnpm add @rspack/core @rspack/cli @rspack/dev-server rspack-manifest-plugin -D
# or
bun add @rspack/core @rspack/cli @rspack/dev-server rspack-manifest-plugin -D

Note: These packages are already listed as optional peer dependencies in Shakapacker, so you may see warnings if they're not installed.

Configuration​

To enable Rspack, update your config/shakapacker.yml:

default: &default # ... other config options
assets_bundler: "rspack" # Change from 'webpack' to 'rspack'

Configuration Files​

Rspack uses its own configuration directory to keep things organized. Create your Rspack configuration file at config/rspack/rspack.config.js:

const { generateRspackConfig } = require("shakapacker/rspack")

module.exports = generateRspackConfig()

Custom Configuration​

If you need to customize your Rspack configuration:

const { generateRspackConfig } = require("shakapacker/rspack")

const rspackConfig = generateRspackConfig({
plugins: [new SomeRspackCompatiblePlugin()],
resolve: {
extensions: [".ts", ".tsx", ".js", ".jsx"]
}
})

module.exports = rspackConfig

Migration from Webpack Config​

If you have an existing config/webpack/webpack.config.js, you can migrate it to config/rspack/rspack.config.js:

Old (webpack.config.js):

const { generateWebpackConfig } = require("shakapacker")
module.exports = generateWebpackConfig()

New (rspack.config.js):

const { generateRspackConfig } = require("shakapacker/rspack")
module.exports = generateRspackConfig()

Note: Shakapacker will show a deprecation warning if you use config/webpack/webpack.config.js with assets_bundler: 'rspack'. Please migrate to config/rspack/rspack.config.js.

Key Differences from Webpack​

Built-in Loaders​

Rspack has built-in loaders that are faster than their webpack counterparts:

  • JavaScript/TypeScript: Uses builtin:swc-loader instead of babel-loader
  • CSS Extraction: Uses rspack.CssExtractRspackPlugin instead of mini-css-extract-plugin
  • Asset Handling: Uses built-in asset modules instead of file-loader/url-loader

Customizing SWC on Rspack​

config/swc.config.js is not read on the Rspack path. Shakapacker's built-in Rspack rule (package/rules/rspack.ts) hard-codes its builtin:swc-loader options inline. Options you put in config/swc.config.js are picked up on webpack only — on Rspack they are silently ignored, with no error and no warning.

Passing module.rules into generateRspackConfig() does not replace the built-in rule. generateRspackConfig() merges your extra config with plain webpack-merge, which concatenates arrays. Your SWC rule lands alongside Shakapacker's, so both run and the built-in one still applies its own options.

To actually override the SWC options, wrap the output of generateRspackConfig() in mergeWithRules (re-exported from shakapacker/rspack) so your entry is matched into the existing rule instead of appended after it. Shakapacker's Rspack rule set has two SWC rules — one for .js/.jsx/.mjs and one for .ts/.tsx — so you need an entry per test regexp, or you only cover one of them:

// config/rspack/rspack.config.js
const { generateRspackConfig, mergeWithRules } = require("shakapacker/rspack")

const swcOverride = {
use: [
{
loader: "builtin:swc-loader",
options: {
jsc: { experimental: { plugins: [["your-plugin-package", {}]] } }
}
}
]
}

module.exports = mergeWithRules({
module: {
rules: { test: "match", use: { loader: "match", options: "merge" } }
}
})(generateRspackConfig(), {
module: {
rules: [
{ test: /\.(js|jsx|mjs)$/, ...swcOverride },
{ test: /\.(ts|tsx)$/, ...swcOverride }
]
}
})

merge vs replace for the options strategy​

The options strategy in the mergeWithRules spec decides whether Shakapacker's base SWC options survive, so pick it deliberately:

  • options: "merge" (used above) deep-merges your options onto Shakapacker's. The built-in jsc.parser (syntax: "ecmascript" / syntax: "typescript", jsx/tsx) and jsc.transform.react.runtime: "automatic" are preserved, and your keys are layered on top. This is what you want for adding a plugin or tweaking one option.
  • options: "replace" swaps the options object out wholesale. Shakapacker's jsc.parser and jsc.transform are gone, not overridden — with the example above, Rspack would be left with no parser syntax and no automatic JSX runtime. Only use replace when you are deliberately supplying a complete SWC options object yourself.

Keep both test: "match" and loader: "match" in the spec, for different reasons. loader: "match" is what merges your entry into the existing rule instead of appending a new one. test: "match" is what pins each entry to the rule it belongs to: matching on use.loader alone leaves the rule count unchanged but applies your override to every rule that has a use chain, appending builtin:swc-loader onto the CSS and Sass chains too. And without mergeWithRules at all, the js rule is duplicated rather than updated.

Wasm SWC plugins​

The common reason to reach for this is jsc.experimental.plugins, used to load a Wasm SWC plugin such as @swc/plugin-styled-components. A Wasm plugin placed only in config/swc.config.js never loads on Rspack, which is what the recipe above fixes. Wasm plugin builds must also match the swc_core version your bundler embeds — Rspack 2.2 changed it, so a plugin that worked on 2.1.x can start failing after the upgrade. See Wasm plugin compatibility with Rspack for the version-matching details and the error message to look for.

Plugin Compatibility​

Most webpack plugins work with Rspack, but some have Rspack-specific alternatives:

Webpack PluginRspack AlternativeStatus
mini-css-extract-pluginrspack.CssExtractRspackPluginBuilt-in
copy-webpack-pluginrspack.CopyRspackPluginBuilt-in
terser-webpack-pluginrspack.SwcJsMinimizerRspackPluginBuilt-in

Minification​

Rspack uses SWC for minification by default, which is significantly faster than Terser:

optimization: {
minimize: true,
minimizer: [
new rspack.SwcJsMinimizerRspackPlugin(),
new rspack.LightningCssMinimizerRspackPlugin()
]
}

Shakapacker's generated Rspack config preserves the shared optimization defaults from the base config, including optimization.splitChunks.chunks = "all" and optimization.runtimeChunk = "single". In production it also preserves compression plugins and Rspack's SWC/Lightning CSS minimizers.

Module IDs and chunk IDs​

Shakapacker does not set optimization.moduleIds or optimization.chunkIds itself (package/optimization/rspack.ts only configures minimize/minimizer), so Rspack's own defaults apply (moduleIds is 'deterministic' in production).

Opt-in, requires Rspack >= 2.2.0: Rspack 2.2 adds a 'compat-hashed' value for both optimization.moduleIds and optimization.chunkIds, using the shortest available prefix of a stable hash. Rspack's own benchmark — measured with both options set to 'compat-hashed' together — reports modest real-world savings versus 'deterministic' (~0.33% smaller minified output, ~0.87% smaller minified+gzip output). Setting only one of the two won't reproduce those numbers.

This is not a Shakapacker default, and we don't plan to make it one:

  • It requires Rspack >= 2.2.0, while Shakapacker's peer range still admits 2.0.x and 2.1.x.
  • Switching either option changes every module's or chunk's id, which invalidates all of your long-term-cached chunk hashes exactly once, on the deploy where you switch. That's a deploy-time tradeoff you should opt into deliberately, not one Shakapacker should make for you.
  • compat-hashed module ids (not chunk ids, which stay lowercase) are case-sensitive and may contain uppercase letters, per Rspack's own docs. If any output filename uses [id] as the only distinguishing placeholder for an asset or WebAssembly module filename, that's unsafe on case-insensitive filesystems (macOS and Windows, by default) — prefer [contenthash], or include it alongside [id].

If you still want to opt in:

// config/rspack/rspack.config.js
const { generateRspackConfig } = require("shakapacker/rspack")

module.exports = generateRspackConfig({
optimization: {
moduleIds: "compat-hashed",
chunkIds: "compat-hashed"
}
})

Browserslist baseline targets (optional)​

Requires Rspack >= 2.1.9 (Shakapacker's peer range, ^2.0.0, also admits 2.0.x and 2.1.0–2.1.8, where this isn't available). From 2.1.9 on, Rspack supports targeting a Baseline browser set via target, e.g. target: 'browserslist:baseline widely available', or a date-pinned variant like target: 'browserslist:baseline widely available on 2025-05-01'. This is an opt-in override in your own Rspack config — Shakapacker doesn't set target for either bundler, and the browserslist key the installer writes to package.json is unaffected either way.

Limitations​

  • CoffeeScript: Not supported with Rspack
  • Some Webpack Plugins: May not be compatible; check Rspack documentation

Commands​

All existing Shakapacker commands work the same way and automatically use Rspack when configured:

# Build (automatically uses rspack when assets_bundler: 'rspack')
./bin/shakapacker

# Development server (automatically uses rspack when assets_bundler: 'rspack')
./bin/shakapacker-dev-server

# Watch mode
./bin/shakapacker --watch

The same dev server configuration in shakapacker.yml applies to both webpack and rspack.

Lazy Compilation​

Rspack v2 uses top-level lazyCompilation; do not rely on experiments.lazyCompilation. For Rails split dev-server topology, Shakapacker sets top-level lazyCompilation: false in the generated Rspack development config when the dev server is running. This avoids Rspack CLI dev-server auto-lazy behavior sending dynamic imports through lazy trigger URLs that Rails does not serve.

If your app has a custom, safe lazy-compilation setup, configure the top-level field explicitly in config/rspack/rspack.config.js:

const { generateRspackConfig } = require("shakapacker/rspack")

module.exports = generateRspackConfig({
lazyCompilation: {
imports: false,
entries: true
}
})

Performance Benefits​

Rspack typically provides:

  • Substantially faster cold builds — Rspack's own benchmark reports roughly 8x faster production builds on a 5,000-component React app (rspack.rs, benchmark sources)
  • Substantially faster development startup — roughly 10–15x in the same benchmark
  • Substantially faster HMR — roughly 17x in the same benchmark
  • Lower memory usage in most reported cases

Actual gains depend on project size, configuration, source maps, cache state, and hardware. See Transpiler Performance Guide for measurement guidance.

Migration Checklist​

  1. Install Rspack dependencies:

    npm install @rspack/core @rspack/cli @rspack/dev-server rspack-manifest-plugin -D
  2. Update configuration:

    # config/shakapacker.yml
    default: &default
    assets_bundler: "rspack"
  3. Create Rspack config:

    // config/rspack/rspack.config.js
    const { generateRspackConfig } = require("shakapacker/rspack")
    module.exports = generateRspackConfig()
  4. Remove CoffeeScript files (if any) - not supported by Rspack

  5. Test your application - same commands work automatically

  6. Compare the generated configs - use bin/diff-bundler-config when you are converting custom webpack configuration or need to prove the rspack config kept the same entrypoints, loaders, output paths, and plugin intent.

Troubleshooting​

Configuration Issues​

If you encounter configuration issues:

  1. Check that all plugins are Rspack-compatible
  2. Verify custom loaders work with Rspack
  3. Review the Rspack migration guide

Performance Issues​

If builds are unexpectedly slow:

  1. Ensure you're using built-in Rspack loaders
  2. Check for webpack-specific plugins that should be replaced
  3. Review your asset optimization settings

Further Reading​