SWC 插件

SWC (Speedy Web Compiler) 是基于 Rust 语言编写的高性能 JavaScript 和 TypeScript 转译和压缩工具。在 Polyfill 和语法降级方面可以和 Babel 提供一致的能力,并且性能比 Babel 高出一个数量级。

Modern.js 提供了开箱即用的 SWC 插件,可以为你的 Web 应用提供语法降级、Polyfill 以及压缩,并且移植了一些额外常见的 Babel 插件。

适用场景

在使用 SWC 插件之前,请先了解一下 SWC 插件的适用场景和局限性,以明确你的项目是否需要使用 SWC 插件。

Rspack 场景

如果你的项目中已经使用了 Rspack 作为打包工具,那么你不需要接入 SWC 插件,因为 Rspack 默认会使用 SWC 进行转译和压缩,各个 SWC 编译能力可以开箱即用。

如果你使用 Rspack 时配置了当前的 SWC 插件,它将不会产生任何效果。

Babel 插件

如果你的项目需要注册一些自定义的 Babel 插件,由于 SWC 替代了 Babel 作为转译工具,因此使用 SWC 后,你将无法注册和使用 Babel 插件。

对于大部分常见的 Babel 插件,你可以在 SWC 中找到对应的替代品,比如:

如果你使用了 SWC 尚未支持的 Babel 插件能力,在切换到 SWC 编译后,将无法再使用它们。你可以到 swc-plugins 仓库下通过 issues 进行反馈,我们会评估是否需要内置支持。

产物体积

在使用 SWC 来代替 tersercssnano 进行代码压缩时,构建产物的体积可能会出现少量变化。在 JavaScript 代码压缩方面,SWC 的压缩率是优于 terser 的;在 CSS 代码压缩方面,SWC 的压缩率稍逊于 cssnano。

对于压缩工具之间的详细对比,可以参考 minification-benchmarks

快速开始

在 Modern.js 框架中使用

Modern.js 框架对 Builder 的 SWC 插件进行了封装,你可以通过以下方式来使用:

首先,你需要执行 pnpm run new 启用 SWC 编译:

? 请选择你想要的操作 启用可选功能
? 请选择功能名称 启用「SWC 编译」

执行完成后,你只需在 modern.config.ts 文件中注册 Modern.js 的 SWC 插件,即可启用 SWC 编译和压缩能力。

modern.config.ts
import { appTools, defineConfig } from '@modern-js/app-tools';
import { swcPlugin } from '@modern-js/plugin-swc';

export default defineConfig({
  plugins: [appTools(), swcPlugin()],
});

通过 Node API 使用

如果你直接使用了 Builder 的 Node API,那么需要手动安装和注册 Builder 的 SWC 插件。

安装插件

你可以通过如下的命令安装插件:

npm
yarn
pnpm
bun
npm add @modern-js/builder-plugin-swc -D

注册插件

通过 addPlugins 方法来注册 SWC 插件:

import { builderPluginSwc } from '@modern-js/builder-plugin-swc';

// 往 builder 实例上添加插件
builder.addPlugins([builderPluginSwc()]);

That's it! 现在你可以在项目中无缝使用 SWC 的转译和压缩能力了。

配置

  • 类型:
type PluginConfig =
  | ObjPluginConfig
  | ((
      config: ObjPluginConfig,
      utils: { mergeConfig: typeof lodash.merge; setConfig: typeof lodash.set },
    ) => ObjPluginConfig | void);

// SwcOptions 为 swc 配置项 https://swc.rs/docs/configuration/compilation
interface ObjPluginConfig extends SwcOptions {
  presetReact?: ReactConfig;
  presetEnv?: EnvConfig;
  jsMinify?: boolean | JsMinifyOptions;
  cssMinify?: boolean | CssMinifyOptions;
  extensions?: Extensions;
  overrides?: Overrides;
}

插件配置在 SWC 配置的基础上,为了简化部分深层配置和为提高开发体验,进行了部分拓展,例如当使用对象形式配置时,可以使用 presetReact 以及 presetEnv 快速配置 react 以及语法降级相关功能,另外不属于插件特有的配置也会直接透传给 swc。

当使用函数形式配置时,则会传入插件内部产出的默认配置,可以对其进行修改或返回新的配置。

presetReact

  • 类型: SWC 中的 react 配置。

对标 @babel/preset-react。传入的值会与默认配置进行合并。

插件默认会自动根据你的 react 版本确定 runtime 字段,如果 react 版本大于 17.0.0,会设置成 automatic,否则设置成 classic

presetEnv

对标 @babel/preset-env。传入的值会与默认配置进行合并。 默认配置为:

{
  targets: '', // 自动从项目中获取 browserslist
  mode: 'usage',
}

jsMinify

如果配置 false 将不会使用 SWC 的压缩能力,配置 true 会启用默认压缩配置,如果配置是对象,则会与默认配置进行合并。

cssMinify

  • 类型: boolean
  • 默认值: true

是否启用 SWC 对 CSS 文件进行压缩,若启用会使得 CSS 压缩性能提高,但压缩率会略微降低。

overrides

  • 类型:
interface Overrides extends SwcOptions {
  test: RegExp;
  include: RegExp[];
  exclude: RegExp[];
}
  • 默认值: undefined

对指定文件运用另外的配置。例如需要对 foo.ts 的语法降级成 ie 11,则可以如下配置:

{
  test: /foo.ts/,
  env: { targets: 'ie 11' }
}

该配置会与默认配置进行合并,并且不会影响到其他文件。

extensions

  • 类型: Object

extensions 包含了从 Babel 移植过来的一些插件能力。

extensions.reactUtils

  • 类型:
type ReactUtilsOptions = {
  autoImportReact?: boolean;
  removeEffect?: boolean;
  removePropTypes?: {
    mode?: 'remove' | 'unwrap' | 'unsafe-wrap';
    removeImport?: boolean;
    ignoreFilenames?: string[];
    additionalLibraries?: string[];
    classNameMatchers?: string[];
  };
};

一些用于 React 的工具,包括以下配置项:

reactUtils.autoImportReact

  • 类型: boolean

自动引入 React, import React from 'react',用于 jsx 转换使用 React.createElement

reactUtils.removeEffect

  • 类型: boolean

移除 useEffect 调用。

reactUtils.removePropTypes

  • 类型:
type RemovePropTypesOptions = {
  mode?: 'remove' | 'unwrap' | 'unsafe-wrap';
  removeImport?: boolean;
  ignoreFilenames?: string[];
  additionalLibraries?: string[];
  classNameMatchers?: string[];
};

移除 React 组件在运行时的类型判断。移植自 @babel/plugin-react-transform-remove-prop-types

相应配置和 @babel/plugin-react-transform-remove-prop-types 插件保持一致。

extensions.lodash

  • 类型:
type LodashOptions = {
  cwd?: string;
  ids?: string[];
};
  • 默认值:
const defaultOptions = {
  cwd: process.cwd(),
  ids: ['lodash', 'lodash-es'],
};

移植自 babel-plugin-lodash,用于自动将 Lodash 的引用转换为按需引入,从而减少打包后的 Lodash 代码大小。

// Input
import { get, throttle } from 'lodash';

// Output
import get from 'lodash/get';
import throttle from 'lodash/throttle';

extensions.styledComponents

  • 类型:
boolean | {
  displayName?: boolean; // 默认开发模式开启, 生产模式关闭,
  ssr?: boolean; // 默认开启
  fileName?: boolean; // 默认开启
  topLevelImportPaths?: string[]; // 默认空
  meaninglessFileNames?: string[]; // 默认为 ["index"].
  cssProp?: boolean; // 默认开启
  namespace?: string; // 默认空
};

由 Next.js 团队移植自 babel-plugin-styled-components

extensions.emotion

  • 类型:
boolean | {
  sourceMap?: boolean, // 默认开启
  autoLabel?: 'never' | 'dev-only' | 'always', // 默认 'dev-only'
  // 默认 '[local]'.
  // 允许的值为: `[local]` `[filename]` 以及 `[dirname]`
  // 只有当 autoLabel 设置成 'dev-only' 或者 'always' 才会生效.
  // 该配置允许你定义结果标签的格式,该格式的组成是字符串以及可以由方括号包裹字符串作为变量
  // 例如对于 "my-classname--[local]",其中的 [local] 会被替换成相应的变量
  labelFormat?: string,
  // 默认值 undefined.
  // 该配置允许让编译器知道哪一个引入需要进行转换,所以如果你重导出了 emotion
  // 的导出,你仍然可以使用该插件进行转换
  importMap?: {
    [packageName: string]: {
      [exportName: string]: {
        canonicalImport?: [string, string],
        styledBaseImport?: [string, string],
      }
    }
  },
},

由 Next.js 团队移植自 @emotion/babel-plugin

extensions.pluginImport

TIP

Builder 提供了 source.transformImport 配置项,因此你不需要手动配置 extensions.pluginImport

移植自 babel-plugin-import,配置选项保持一致。

一些配置可以传入函数,例如 customNamecustomStyleName 等,这些 JavaScript 函数会由 Rust 通过 Node-API 调用,这种调用会造成一些性能劣化。

简单的函数逻辑其实可以通过模版语言来代替,因此customNamecustomStyleName 等这些配置除了可以传入函数,也可以传入字符串作为模版来代替函数,提高性能。

我们以下面代码为例说明:

import { MyButton as Btn } from 'foo';

添加以下配置:

PluginSWC({
  extensions: {
    pluginImport: [
      {
        libraryName: 'foo',
        customName: 'foo/es/{{ member }}',
      },
    ],
  },
});

其中的 {{ member }} 会被替换为相应的引入成员,转换后:

import Btn from 'foo/es/MyButton';

可以看出配置 customName: "foo/es/{{ member }}" 的效果等同于配置 customName: (member) => `foo/es/${member}` ,但是不会有 Node-API 的调用开销。

这里使用到的模版是 handlebars,模版配置中还内置了一些有用的辅助工具,还是以上面的导入语句为例,配置成:

PluginSWC({
  extensions: {
    pluginImport: [
      {
        libraryName: 'foo',
        customName: 'foo/es/{{ kebabCase member }}',
      },
    ],
  },
});

会转换成下面的结果:

import Btn from 'foo/es/my-button';

除了 kebabCase 以外还有 camelCasesnakeCaseupperCaselowerCase 可以使用。

限制

不支持 @babel/plugin-transform-runtime 以及其他自定义的 Babel 插件。