首页
/ 为 Remotion 切换传统 Babel 转译:@remotion/babel-loader 与 replaceLoadersWithBabel 全解析

为 Remotion 切换传统 Babel 转译:@remotion/babel-loader 与 replaceLoadersWithBabel 全解析

2026-09-06 20:55:04作者:齐添朝

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.2babel-loader@8.2.2react-refresh@0.18.0webpack@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

这里有两个要点:

  1. 必须用精确版本(--save-exact。README 强调:安装任何 remotion / @remotion/* 包时,所有相关包版本必须对齐到同一版本,需要去掉版本号前的 ^ 字符。这是因为 Remotion 各包(bundler、renderer、cli 等)之间通过内部协议协作,版本不一致会直接导致运行时报错。
  2. 官方文档在示例中另外要求用户自行安装 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-envtargets 固定为 chrome: '85'——这与 Remotion 渲染所依赖的 Chromium 环境对齐;
    • @babel/preset-reactruntime: 'automatic',即不依赖手动 import React 的自动 JSX 运行时;
    • @babel/preset-typescriptisTSX: 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-loaderpreset-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-loaderreplaceLoadersWithBabel 通过相同的规则字符串匹配逻辑对其生效,因此该包对两种 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/lambdadeploySiteFromBundle()(该函数实现在 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 组合的两条惯例:

  1. 每个 enable* 函数和 replaceLoadersWithBabel 都遵循"展开原配置 → 局部修改 → 返回"的 reducer 模式,可以任意嵌套;
  2. 追加自定义规则时保留原有 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
登录后查看全文
热门项目推荐
相关项目推荐