为 Remotion 切换传统 Babel 转译:@remotion/babel-loader 与 replaceLoadersWithBabel 全解析
Remotion 默认使用 esbuild-loader(Webpack)或 SWC(Rspack)进行转译以获得更快的构建速度,但在遇到某些依赖 Babel 插件体系的场景时,需要通过兼容包 @remotion/babel-loader 提供的 replaceLoadersWithBabel() 将打包器的 JavaScript/TypeScript loader 替换回 Babel。读完本文,你将掌握该包的安装方式、在 remotion.config.ts 与 Node.js bundle() API 两种场景下的完整配置写法,并能从源码层面理解 loader 替换的实现细节、默认转译目标的差异,以及仓库中现有的集成测试如何验证替换生效。
一、@remotion/babel-loader 是什么
@remotion/babel-loader 是 Remotion 仓库中的一个独立子包(见 packages/babel-loader/package.json),其官方描述就是 "Babel loader for Remotion"。它的作用单一而明确:向 Remotion 的打包流程注入 webpack 风格的 babel-loader 规则,替换掉默认的 esbuild/SWC 转译。
从包定义可以看到几个关键事实:
- 包版本随 Remotion 主版本发布,当前仓库中为
4.0.520(见 packages/babel-loader/package.json); - 它以
@remotion/bundler为 peerDependency,导出函数replaceLoadersWithBabel()接收的类型正是@remotion/bundler中的BundlerConfiguration; - 包自身直接声明了 Babel 全家桶依赖:
@babel/core@7.29.6、@babel/preset-env@7.23.2、@babel/preset-react@7.14.5、@babel/preset-typescript@7.23.2、babel-loader@8.2.2、react-refresh@0.18.0与webpack@5.105.0(见 packages/babel-loader/package.json)。
官方文档对它的定位是"兼容性包"(compatibility package),并明确建议:"一般情况下不应需要这样做,我们鼓励你报告默认转译器的问题"(见 packages/docs/docs/legacy-babel-loader.mdx)。换句话说,这是一条为特殊场景保留的退路,而非推荐的常规配置。
二、安装:--save-exact 与版本对齐
README(packages/babel-loader/README.md)给出的安装命令是:
npm install @remotion/babel-loader --save-exact
这里有两个要点:
- 必须用精确版本(
--save-exact)。README 强调:安装任何remotion/@remotion/*包时,所有相关包版本必须对齐到同一版本,需要去掉版本号前的^字符。这是因为 Remotion 各包(bundler、renderer、cli 等)之间通过内部协议协作,版本不一致会直接导致运行时报错。 - 官方文档在示例中另外要求用户自行安装 Babel 侧依赖,以便兼容用户自己项目里的 Babel 生态:
# npm
npm i babel-loader @babel/preset-env @babel/preset-react
# pnpm
pnpm i babel-loader @babel/preset-env @babel/preset-react
# yarn
yarn add babel-loader @babel/preset-env @babel/preset-react
三种包管理器的命令等价(见 packages/docs/docs/legacy-babel-loader.mdx)。
三、场景一:在 remotion.config.ts 中替换 loader
Remotion 的打包器覆盖采用 reducer 风格:你收到默认配置对象,返回修改后的配置对象。官方文档给出的最小可用示例(packages/docs/docs/legacy-babel-loader.mdx):
import { Config } from "@remotion/cli/config";
import { replaceLoadersWithBabel } from "@remotion/babel-loader";
Config.overrideBundlerConfig((currentConfiguration) => {
return replaceLoadersWithBabel(currentConfiguration);
});
这段配置放在 remotion.config.ts 中即可对 Studio、渲染等所有走配置文件的路径生效。
replaceLoadersWithBabel 到底做了什么
阅读源码 packages/babel-loader/src/index.ts 可以确认其完整行为:
import type {BundlerConfiguration} from '@remotion/bundler';
const envPreset = [
require.resolve('@babel/preset-env'),
{
targets: {
chrome: '85',
},
},
] as const;
export const replaceLoadersWithBabel = <
Configuration extends BundlerConfiguration,
>(
conf: Configuration,
): Configuration => {
return {
...conf,
module: {
...conf.module,
rules: (conf.module?.rules ?? []).map((rule) => {
// ... 仅重写匹配 .tsx / .jsx 的规则
}),
},
};
};
逐条拆解它的实现逻辑:
- 只动脚本规则,其余规则原样保留。函数遍历
conf.module.rules,逐条检查rule.test?.toString():包含.tsx的规则被替换为 TypeScript/TSX 规则,包含.jsx的规则被替换为 JavaScript/JSX 规则,其余规则(如 CSS、字体、媒体文件规则以及 Rspack 的'...'占位符)一律不动(见 packages/babel-loader/src/index.ts)。 - TS/TSX 规则(
.tsx?) 使用babel-loader,注入的 presets/plugins 为:@babel/preset-env,targets固定为chrome: '85'——这与 Remotion 渲染所依赖的 Chromium 环境对齐;@babel/preset-react,runtime: 'automatic',即不依赖手动import React的自动 JSX 运行时;@babel/preset-typescript,isTSX: true, allExtensions: true,让.ts与.tsx都按 TSX 语义解析;- plugins:
@babel/plugin-proposal-class-properties,并且在conf.mode === 'development'时额外注入react-refresh/babel,为 Studio 的快速刷新提供 Babel 侧支持(见 packages/babel-loader/src/index.ts)。
- JS/JSX 规则(
.jsx?) 使用同一套babel-loader与preset-env+preset-react(automatic),plugins 仅保留 class properties,不包含 TypeScript preset(见 packages/babel-loader/src/index.ts)。 - 源码中有一条注释值得注意:"All modules that use require.resolve need to be added to cli/src/load-config -> external array"(packages/babel-loader/src/index.ts),即 loader 内部用
require.resolve锁定的每个模块,CLI 在加载配置文件时都必须将其标记为 external,避免被提前打包。从源码结构看,这是该包能在remotion.config.ts这种"被 CLI 自身打包加载"的场景中运行的前提。
与默认配置的对比
要理解替换的差异,可以参考默认配置。在 packages/bundler/src/webpack-config.ts 中,Webpack 路径默认使用 esbuild-loader,目标同样是 Chrome 85:
const esbuildLoaderOptions: LoaderOptions = {
target: 'chrome85',
loader: 'tsx',
implementation: esbuild,
remotionRoot,
};
其中 .tsx? 规则在 development 模式下还会追加 fast-refresh/loader.js(见 packages/bundler/src/webpack-config.ts)。replaceLoadersWithBabel 的 TSX 规则刻意将 react-refresh/babel 放在 use 数组中保持与 fast-refresh loader 相同的相对顺序(源码注释 "Keep the order to match babel-loader",见 packages/bundler/src/webpack-config.ts),保证替换后热刷新行为一致。此外,bundlerOverride 是在基础配置构造完成、webpackOverride 之前统一应用的(见 packages/bundler/src/webpack-config.ts),这解释了为什么 reducer 风格"收到默认配置、返回修改后配置"是官方推荐写法。
值得注意的是,Webpack 与 Rspack 两条路径共用同一个 bundlerOverride:Rspack 侧默认使用内置的 builtin:swc-loader,replaceLoadersWithBabel 通过相同的规则字符串匹配逻辑对其生效,因此该包对两种 bundler 都是"可移植"(portable)的——这也是集成测试用 "portable overrides" 命名的原因。
四、场景二:通过 Node.js API bundle() 传覆盖函数
Node.js API 不读取 remotion.config.ts,因此覆盖函数必须直接传入。官方文档示例(packages/docs/docs/legacy-babel-loader.mdx):
import { bundle } from "@remotion/bundler";
import { replaceLoadersWithBabel } from "@remotion/babel-loader";
await bundle({
entryPoint: require.resolve("./src/index.ts"),
bundlerOverride: (config) => replaceLoadersWithBabel(config),
});
文档同时指出:若要把 bundle() 生成的目录部署到 Lambda,应将其传给 @remotion/lambda 的 deploySiteFromBundle()(该函数实现在 packages/lambda/src/api/deploy-site-from-bundle.ts)。
五、真实用法参考:组合其他 override
Remotion 的示例工程展示了一个更完整的组合写法。在 packages/example/src/webpack-override.mjs 中,replaceLoadersWithBabel 与 SCSS、Skia、Tailwind 的 enable 函数以及自定义 MDX loader 规则嵌套组合:
/** @type {import('@remotion/bundler').BundlerOverrideFn} */
export const bundlerOverride = (currentConfiguration) => {
const replaced = (() => {
if (WEBPACK_OR_ESBUILD === 'webpack') {
const {replaceLoadersWithBabel} = require(/* @remotion/babel-loader */);
return replaceLoadersWithBabel(currentConfiguration);
}
return currentConfiguration;
})();
return enableScss(
enableSkia(
enableTailwind({
...replaced,
module: {
...replaced.module,
rules: [
...(replaced.module?.rules ?? []),
{test: /\.mdx?$/, use: [{loader: '@mdx-js/loader', options: {}}]},
],
},
resolve: {
...replaced.resolve,
alias: {
...replaced.resolve.alias,
lib: path.join(process.cwd(), 'src', 'lib'),
},
},
}),
),
);
};
这个例子说明了 override 组合的两条惯例:
- 每个
enable*函数和replaceLoadersWithBabel都遵循"展开原配置 → 局部修改 → 返回"的 reducer 模式,可以任意嵌套; - 追加自定义规则时保留原有
rules数组(...(replaced.module?.rules ?? [])),避免覆盖掉 Babel 规则或 CSS 规则。
六、集成测试:如何验证替换真正生效
仓库中的集成测试 packages/it-tests/src/bundle/rspack-portable-overrides.test.ts 专门验证了 Babel 替换与 SCSS、Tailwind v3 的组合(测试名为 "SCSS, Tailwind v3, and Babel helpers work through a shared Rspack override"):
const bundlerOverride: BundlerOverrideFn = (configuration) => {
const withHelpers = replaceLoadersWithBabel(
enableScss(
enableTailwind(configuration, {
configLocation: path.join(fixtureDirectory, 'tailwind.config.cjs'),
}),
),
);
// ...收集 tsx 规则中实际生效的 loader 列表
return withHelpers;
};
断言部分(packages/it-tests/src/bundle/rspack-portable-overrides.test.ts):
expect(scriptLoaders[0]).toContain('babel-loader');
expect(scriptLoaders).not.toContain('builtin:swc-loader');
expect(result).toContain('BABEL_LOADER_SENTINEL');
即:.tsx 规则的第一个 loader 必须是 babel-loader、不能残留 Rspack 的 builtin:swc-loader,且打包产物包含 fixture 中定义的哨兵字符串。这正是判断"替换是否成功"的可复现验证方式——检查最终 rule 的 use 链与产物内容。
七、适用前提与限制小结
- 适用前提:使用 Remotion 4.x(本仓库版本为
4.0.520),且所有remotion与@remotion/*包版本严格对齐; - 生效范围:
Config.overrideBundlerConfig作用于走配置文件的所有路径;Node.js API 必须显式传bundlerOverride; - 对 Rspack 同样有效:
replaceLoadersWithBabel匹配的是规则字符串而非 Webpack 专属 loader,测试证明其在 Rspack(SWC)下也能把脚本规则替换为 Babel; - 开发模式差异:仅
mode === 'development'时注入react-refresh/babel,production 构建不会包含该插件; - 官方立场:该包是兼容性退路,遇到默认 esbuild/SWC 转译问题时,官方建议优先提 issue 反馈而非切换到 Babel。
八、关键文件索引
| 内容 | 路径 |
|---|---|
| 包 README(安装说明) | packages/babel-loader/README.md |
核心实现 replaceLoadersWithBabel() |
packages/babel-loader/src/index.ts |
| 包依赖与版本 | packages/babel-loader/package.json |
| 官方文档(legacy-babel) | packages/docs/docs/legacy-babel-loader.mdx |
| 默认 Webpack 配置与 esbuild-loader | packages/bundler/src/webpack-config.ts |
| 组合 override 示例 | packages/example/src/webpack-override.mjs |
| Babel 替换集成测试 | packages/it-tests/src/bundle/rspack-portable-overrides.test.ts |
| Lambda 部署入口 | packages/lambda/src/api/deploy-site-from-bundle.ts |
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0624
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00