@remotion/bundler: Add experimental flag for Rspack (Fast Refresh not yet working)#6596
@remotion/bundler: Add experimental flag for Rspack (Fast Refresh not yet working)#6596JonnyBurger merged 17 commits intomainfrom
@remotion/bundler: Add experimental flag for Rspack (Fast Refresh not yet working)#6596Conversation
0c7b531 to
f2181b7
Compare
Add Rspack as an optional alternative bundler controlled by `--rspack` flag (e.g., `npx remotion studio --rspack`). Webpack remains the default. - Add `@rspack/core` and `@rspack/plugin-react-refresh` dependencies - Create `rspack-config.ts` paralleling webpack-config.ts with SWC loader - Define `--rspack` CLI option and thread it through studio/bundle paths - Branch compiler creation in start-server.ts and bundle.ts based on flag Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
f2181b7 to
506e5c0
Compare
…imentalRspackEnabled()` Mark the Rspack bundler flag as experimental, add a config setter, warn when enabled, add template validation test, and document the new option. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…eploySite` + docs Thread the `--experimental-rspack` CLI flag through the entire bundle/render pipeline instead of hardcoding `false`. Add `rspack?: boolean` to Lambda and CloudRun `deploySite()` options. Document the new option across all relevant CLI commands and Node.js APIs. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…-update files - Delete process-update.ts which had complex recursive HMR check/apply logic that interfered with rspack's native React Fast Refresh - Replace with minimal inline module.hot.check(true) in client.ts with hash comparison and idle status guards - Add disk fallback in dev middleware for .hot-update. files that rspack's native compiler may write to disk instead of memfs - Disable ReactRefreshPlugin overlay for rspack Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
600c05f to
bf9f8d3
Compare
@remotion/bundler: Add --experimental-rspack flag for optional Rspack bundling@remotion/bundler: Add experimental flag for Rspack (Fast Refresh not yet working)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
Low urgency. Well-structured experimental feature with proper gating. Found one bug in the option state management. The abstraction layer between webpack and rspack is clean, though users with custom webpackOverride functions may encounter issues since the same override is applied to both bundlers. The acknowledgment in the title about Fast Refresh not working sets appropriate expectations.
| if (commandLine[cliFlag] !== undefined) { | ||
| rspackEnabled = true; |
There was a problem hiding this comment.
Bug: When the CLI flag is explicitly set to false (e.g., --experimental-rspack=false), this still sets rspackEnabled = true in global state. Subsequent calls without the CLI flag will then incorrectly return true.
Should be:
rspackEnabled = commandLine[cliFlag] as boolean;| if (commandLine[cliFlag] !== undefined) { | |
| rspackEnabled = true; | |
| if (commandLine[cliFlag] !== undefined) { | |
| rspackEnabled = commandLine[cliFlag] as boolean; |
There was a problem hiding this comment.
Clean refactoring that extracts shared bundler config and adds an experimental rspack path. The approach is sound — shared config pieces in shared-bundler-config.ts, parallel rspack config, and consistent plumbing through CLI/API layers. A few items worth considering below, mostly around dependency strategy and a minor bug in the option definition.
| if (commandLine[cliFlag] !== undefined) { | ||
| rspackEnabled = true; |
There was a problem hiding this comment.
When the CLI flag is present but set to false (e.g. --no-experimental-rspack), this unconditionally sets the module-level rspackEnabled to true, clobbering the config value. The returned value is correct, but the side effect is wrong — if getValue is called again without a commandLine flag, it would return true instead of the intended false.
Should this be rspackEnabled = commandLine[cliFlag] as boolean instead?
| if (commandLine[cliFlag] !== undefined) { | |
| rspackEnabled = true; | |
| if (commandLine[cliFlag] !== undefined) { | |
| rspackEnabled = commandLine[cliFlag] as boolean; |
| "@rspack/core": "1.7.6", | ||
| "@rspack/plugin-react-refresh": "1.6.1", |
There was a problem hiding this comment.
Nit: @rspack/core and @rspack/plugin-react-refresh are added as hard dependencies, which means every user of @remotion/bundler will download them even if they never enable the experimental flag. These are not small packages. Would optionalDependencies (with a dynamic require() guarded by a try/catch) or lazy require() at the call site be more appropriate for an experimental feature? The dynamic require('@rspack/core') in bundle.ts already exists, but rspack-config.ts has top-level static imports from @rspack/core that would fail if it weren't installed.
| import type {Configuration} from '@rspack/core'; | ||
| import {DefinePlugin, ProgressPlugin, rspack} from '@rspack/core'; | ||
| import ReactRefreshPlugin from '@rspack/plugin-react-refresh'; |
There was a problem hiding this comment.
These are top-level static imports, so @rspack/core and @rspack/plugin-react-refresh will be loaded as soon as this module is imported — even when the user hasn't enabled rspack. Since rspack-config.ts is re-exported from index.ts via BundlerInternals, this means all users pay the import cost. If rspack is moved to optionalDependencies, these would need to become dynamic imports. Even as hard deps, lazy-loading here would avoid the startup cost for webpack users.
| if (actualArgs.rspack) { | ||
| const {rspack: rspackFn} = require('@rspack/core'); | ||
| const rspackCompiler = rspackFn(config); | ||
| const rspackOutput = await new Promise<{ | ||
| toJson: (opts: unknown) => { | ||
| errors?: Array<{message: string; details: string}>; | ||
| }; | ||
| }>((resolve, reject) => { | ||
| rspackCompiler.run( | ||
| ( | ||
| err: Error | null, | ||
| stats: { | ||
| toJson: (opts: unknown) => { | ||
| errors?: Array<{message: string; details: string}>; | ||
| }; | ||
| }, | ||
| ) => { | ||
| if (err) { | ||
| reject(err); | ||
| return; | ||
| } | ||
|
|
||
| rspackCompiler.close(() => { | ||
| resolve(stats); | ||
| }); | ||
| }, | ||
| ); | ||
| }); |
There was a problem hiding this comment.
The inline types for the rspack compiler/stats duplicate the shape already available from @rspack/core. Since @rspack/core is a hard dependency, you could do:
const {rspack: rspackFn} = require('@rspack/core') as typeof import('@rspack/core');This gives you full type safety from the rspack types without the manual toJson shape. Minor point for an experimental feature though.
| // Rspack config is structurally compatible with webpack config at runtime, | ||
| // but the TypeScript types differ. Cast through `any` for the override. |
There was a problem hiding this comment.
The comment is helpful. Worth noting that if a user's webpackOverride function inspects webpack-specific properties (e.g. checks config.module.rules for specific loader names), it could behave unexpectedly with an rspack config. Since this is experimental and behind a flag, probably fine for now — but the eventual docs for rspack usage should mention this.
| compiler = BundlerInternals.createRspackCompiler( | ||
| rspackConf, | ||
| ) as unknown as webpack.Compiler; |
There was a problem hiding this comment.
Double-casting through unknown to webpack.Compiler is understandable for an experimental feature, but fragile. The wdm and webpackHotMiddleware calls downstream depend on the compiler having the right shape. If rspack's compiler diverges from webpack's in subtle ways, this could produce confusing runtime errors. Consider adding a brief comment about why this cast is safe (rspack's compiler implements the same interface).
| environment === 'development' | ||
| ? [ | ||
| new ReactRefreshPlugin({overlay: false}), | ||
| new rspack.HotModuleReplacementPlugin(), |
There was a problem hiding this comment.
The PR title mentions "Fast Refresh not yet working." If HMR/React Refresh is known to be broken with this setup, should these plugins be omitted (or the inclusion gated with a TODO comment) to avoid confusing error output for users who try the experimental flag in development mode?
| experiments: { | ||
| lazyCompilation: isBun | ||
| ? false | ||
| : environment === 'production' | ||
| ? false | ||
| : { | ||
| entries: false, | ||
| }, | ||
| }, |
There was a problem hiding this comment.
Rspack supports experiments.lazyCompilation but the API shape may differ from webpack's (rspack docs show different options). Has this been verified to work with rspack, or does rspack silently ignore unknown experiment options?
There was a problem hiding this comment.
Pull request overview
This PR adds experimental Rspack support as an alternative bundler to Webpack across the Remotion ecosystem. Rspack is a high-performance JavaScript bundler that aims to be compatible with Webpack's API while offering faster build times. The implementation introduces a new --experimental-rspack CLI flag and setExperimentalRspackEnabled() config option, though Fast Refresh functionality is not yet working (as noted in the PR title).
Changes:
- Introduced Rspack configuration alongside Webpack with shared bundler logic extracted to common helper functions
- Added
rspackoption throughout CLI commands (studio, render, still, bundle, benchmark, compositions) and API methods (Lambda/Cloud Run deploy) - Created new files for Rspack-specific configuration while refactoring Webpack config to share common patterns
- Updated documentation across CLI and API docs to include the new experimental flag
- Added test to ensure templates don't use the experimental feature by default
Reviewed changes
Copilot reviewed 36 out of 37 changed files in this pull request and generated 7 comments.
Show a summary per file
| File | Description |
|---|---|
| packages/renderer/src/options/rspack.tsx | New option definition for experimental Rspack flag |
| packages/bundler/src/rspack-config.ts | Main Rspack bundler configuration implementation |
| packages/bundler/src/shared-bundler-config.ts | Extracted shared configuration logic between Webpack and Rspack |
| packages/bundler/src/define-plugin-definitions.ts | Shared plugin definitions for both bundlers |
| packages/bundler/src/webpack-config.ts | Refactored to use shared configuration helpers |
| packages/bundler/src/bundle.ts | Updated to support both Webpack and Rspack compilation |
| packages/bundler/src/index.ts | Exports Rspack-related functions for internal use |
| packages/bundler/package.json | Added @rspack/core and @rspack/plugin-react-refresh dependencies |
| packages/studio-server/src/start-studio.ts | Passes rspack flag to preview server |
| packages/studio-server/src/preview-server/start-server.ts | Conditionally creates Rspack or Webpack compiler |
| packages/studio-server/src/preview-server/dev-middleware/setup-hooks.ts | Updated to recognize Rspack compilation messages |
| packages/cli/src/*.ts | Added rspack option to all CLI commands (studio, render, still, bundle, benchmark, compositions) |
| packages/cli/src/render-queue/*.ts | Added rspack support to render queue processors |
| packages/cli/src/setup-cache.ts | Passes rspack option through bundle flow |
| packages/cli/src/render-flows/*.ts | Updated render flows to accept rspack parameter |
| packages/cli/src/config/index.ts | Added setExperimentalRspackEnabled() config method |
| packages/lambda/src/api/deploy-site.ts | Added rspack option to Lambda site deployment |
| packages/cloudrun/src/api/deploy-site.ts | Added rspack option to Cloud Run site deployment |
| packages/docs/docs/*.mdx | Documentation updates for CLI commands, config, and API methods |
| packages/it-tests/src/templates/validate-templates.test.ts | Test to ensure templates don't enable experimental Rspack by default |
| bun.lock | Lock file updates for new Rspack dependencies |
| export {WebpackConfiguration, WebpackOverrideFn} from './webpack-config'; | ||
| export {webpack}; |
There was a problem hiding this comment.
The RspackConfiguration type is not exported from the package, unlike WebpackConfiguration which is exported at line 31. This means users cannot properly type their Rspack-specific override functions or configurations.
Consider exporting RspackConfiguration alongside WebpackConfiguration to maintain consistency and provide proper type support for Rspack users.
| cache: options.enableCaching | ||
| ? { | ||
| type: 'filesystem', | ||
| name: getWebpackCacheName(options.environment, hash), |
There was a problem hiding this comment.
The cache naming strategy doesn't distinguish between Webpack and Rspack, which could lead to cache collision. When switching between Webpack and Rspack bundlers, they would try to reuse each other's caches, potentially causing issues since they generate different output formats.
Consider creating separate cache directories or prefixes for Webpack and Rspack. For example, you could modify getWebpackCacheName() to accept a bundler type parameter and include it in the cache name, or create a separate getRspackCacheName() function.
| name: getWebpackCacheName(options.environment, hash), | |
| name: `${process.env.REMOTION_BUNDLER ?? 'webpack'}-${getWebpackCacheName(options.environment, hash)}`, |
| if (commandLine[cliFlag] !== undefined) { | ||
| rspackEnabled = true; | ||
| return { | ||
| value: commandLine[cliFlag] as boolean, | ||
| source: 'cli', | ||
| }; |
There was a problem hiding this comment.
The getValue function doesn't use the actual value from the command line. When commandLine[cliFlag] is defined, it should return that value, not just set rspackEnabled to true unconditionally.
The current implementation would always enable Rspack when the flag is present, even if it's explicitly set to false (e.g., --experimental-rspack=false). This is inconsistent with other options like experimentalClientSideRenderingOption which correctly uses the command line value at line 23.
| experiments: { | ||
| lazyCompilation: isBun | ||
| ? false | ||
| : environment === 'production' | ||
| ? false | ||
| : { | ||
| entries: false, | ||
| }, | ||
| }, |
There was a problem hiding this comment.
The getBaseConfig() function includes a lazyCompilation configuration under the experiments key, which is a Webpack-specific feature that Rspack may not fully support. While Rspack's configuration structure is compatible with Webpack, this specific feature may not work as intended when using Rspack.
Consider documenting this limitation or conditionally excluding the lazyCompilation option when Rspack is being used. The PR title already mentions "Fast Refresh not yet working", and this could be related.
| experiments: { | |
| lazyCompilation: isBun | |
| ? false | |
| : environment === 'production' | |
| ? false | |
| : { | |
| entries: false, | |
| }, | |
| }, |
| description: () => ( | ||
| <>Uses Rspack instead of Webpack as the bundler for the Studio or bundle.</> | ||
| ), |
There was a problem hiding this comment.
The description doesn't mention that this is an experimental feature or that Fast Refresh is not yet working. While the CLI flag name includes "experimental", the description itself should explicitly mention the current limitations to set proper expectations.
Consider updating the description to something like: "Experimental feature: Uses Rspack instead of Webpack as the bundler for the Studio or bundle. Note: Fast Refresh is not yet functional with Rspack."
| if (useRspack) { | ||
| Log.warn( | ||
| {indent: false, logLevel}, | ||
| 'Enabling experimental Rspack bundler.', |
There was a problem hiding this comment.
The warning message doesn't mention the current limitations of the Rspack integration, specifically that Fast Refresh is not yet working (as noted in the PR title). Users should be informed about these limitations when they enable the experimental feature.
Consider expanding the warning to mention known limitations, similar to how the client-side rendering warning provides a documentation link at line 114.
| 'Enabling experimental Rspack bundler.', | |
| 'Enabling experimental Rspack bundler. Fast Refresh is currently not supported and other limitations may apply. See https://www.remotion.dev/docs/rspack for details.', |
| const keyboardShortcutsEnabled = keyboardShortcutsOption.getValue({ | ||
| commandLine: parsedCli, | ||
| }).value; | ||
| const rspack = rspackOption.getValue({commandLine: parsedCli}).value; |
There was a problem hiding this comment.
When the rspack flag is used with the bundle command, there's no warning message to inform users that they're using an experimental feature with known limitations (Fast Refresh not working). This is inconsistent with the studio command which does provide a warning at line 143-146 in studio.ts.
Consider adding a similar warning message for the bundle command to maintain consistency and inform users about the experimental nature and limitations of Rspack integration.

See #6608 for progress regarding Rspack