# SWC 插件

import SWC from '@zh/shared/swc.md';

<SWC />

## 适用场景

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

### Rspack 场景

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

### Babel 插件

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

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

- `@babel/preset-env`: 使用 [presetEnv](#presetenv) 代替。
- `@babel/preset-react`: 使用 [presetReact](#presetreact) 代替。
- `babel-plugin-import`：使用 [source.transformImport](/api/config-source.html#sourcetransformimport) 代替。
- `babel-plugin-lodash`：使用 [extensions.lodash](#extensionslodash) 代替。
- `@emotion/babel-plugin`：使用 [extensions.emotion](#extensionsemotion) 代替。
- `babel-plugin-styled-components`：使用 [extensions.styledComponent](#extensionsstyledcomponent) 代替。
- `@babel/plugin-react-transform-remove-prop-types`: 使用 [reactUtils.removePropTypes](#extensionsreactutils) 代替。

如果你使用了 SWC 尚未支持的 Babel 插件能力，在切换到 SWC 编译后，将无法再使用它们。你可以到 [swc-plugins](https://github.com/web-infra-dev/swc-plugins) 仓库下通过 issues 进行反馈，我们会评估是否需要内置支持。

### 产物体积

在使用 SWC 来代替 [terser](https://github.com/terser/terser) 和 [cssnano](https://github.com/cssnano/cssnano) 进行代码压缩时，构建产物的体积可能会出现少量变化。在 JavaScript 代码压缩方面，SWC 的压缩率是优于 terser 的；在 CSS 代码压缩方面，SWC 的压缩率稍逊于 cssnano。

对于压缩工具之间的详细对比，可以参考 [minification-benchmarks](https://github.com/privatenumber/minification-benchmarks)。

## 快速开始

### 安装

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

```bash
# npm
npm install @modern-js/builder-plugin-swc -D

# yarn
yarn add @modern-js/builder-plugin-swc -D

# pnpm
pnpm add @modern-js/builder-plugin-swc -D
```

### 注册插件

在 Modern.js 等上层框架中，你可以通过 `builderPlugins` 配置项来注册 SWC 插件：

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

export default {
  builderPlugins: [builderPluginSwc()],
};
```

当你直接调用 Builder 的 Node API 时，可以通过 `addPlugins` 方法来注册 SWC 插件：

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

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

That's it !

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

## 配置

- **类型：**

```ts
type PluginConfig = {
  presetReact?: ReactConfig;
  presetEnv?: EnvConfig;
  jsMinify?: boolean | JsMinifyOptions;
  cssMinify?: boolean | CssMinifyOptions;
  extensions?: Extensions;
};
```

### presetReact

- **类型：** SWC 中的 [presetReact](https://swc.rs/docs/configuration/compilation#jsctransformreact)。

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

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

### presetEnv

- **类型：** SWC 中的 [presetEnv](https://swc.rs/docs/configuration/supported-browsers#options)。

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

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

### jsMinify

- **类型：** `boolean` 或者 [terser 中的 compress 配置](https://terser.org/docs/api-reference.html#compress-options)。
- **默认值：** `{ compress: {}, mangle: true }`。

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

### cssMinify

- **类型：** `boolean`
- **默认值：** `true`

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

### extensions

- **类型：** `Object`

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

#### extensions.reactUtils

- **类型：**

```ts
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**

- **类型：**

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

移除 `React` 组件在运行时的类型判断。移植自 [@babel/plugin-react-transform-remove-prop-types](https://github.com/oliviertassinari/babel-plugin-transform-react-remove-prop-types)。

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

#### extensions.lodash

- **类型：**

```ts
type LodashOptions = {
  cwd?: string;
  ids?: string[];
};
```

- **默认值：**

```ts
const defaultOptions = {
  cwd: process.cwd(),
  ids: ['lodash', 'lodash-es'],
};
```

移植自 [babel-plugin-lodash](https://github.com/lodash/babel-plugin-lodash)，用于自动将 Lodash 的引用转换为按需引入，从而减少打包后的 Lodash 代码大小。

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

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

#### extensions.styledComponent

- **类型：**

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

由 Next.js 团队移植自 [babel-plugin-styled-components](https://github.com/styled-components/babel-plugin-styled-components)。

#### extensions.emotion

- **类型：**

```ts
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](https://www.npmjs.com/package/@emotion/babel-plugin)。

#### extensions.pluginImport

:::tip
Builder 提供了 [source.transformImport](/api/config-source.html#sourcetransformimport) 配置项，因此你不需要手动配置 `extensions.pluginImport`。
:::

移植自 [babel-plugin-import](https://github.com/umijs/babel-plugin-import)，配置选项保持一致。

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

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

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

```ts
import { MyButton as Btn } from 'foo';
```

添加以下配置：

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

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

```ts
import Btn from 'foo/es/MyButton';
```

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

这里使用到的模版是 [handlebars](https://handlebarsjs.com)，模版配置中还内置了一些有用的辅助工具，还是以上面的导入语句为例，配置成：

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

会转换成下面的结果:

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

除了 `kebabCase` 以外还有 `camelCase`，`snakeCase`，`upperCase`，`lowerCase` 可以使用。

## 限制

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