> For AI agents: the complete documentation index is available at /zh/llms.txt, the full documentation bundle is available at /zh/llms-full.txt.

# Tree shaking

Rspack 支持 [tree shaking](https://developer.mozilla.org/zh-CN/docs/Glossary/Tree_shaking) 功能，这是一个在 JavaScript 生态中广泛使用的术语，主要用于去除未被访问的代码，俗称“死代码”。当一个模块的某些导出未被使用且不存在副作用时，这部分代码就可以被安全地删除，以减小最终产物的体积。

## 什么是 tree shaking

你可以将应用程序想象成一棵树。绿色表示实际用到的源码和库，是树上活的树叶。灰色表示未引用代码，是秋天树上枯萎的树叶。为了除去死去的树叶，你必须摇动（shake）这棵树，使它们落下。

需要注意的是，Rspack 本身不直接删除死代码，而是标记未使用的导出为潜在的"死代码"。这些代码能被后续的压缩工具识别并处理。因此，如果 [压缩功能](/zh/config/optimization.md#optimizationminimize) 被关闭，你将不会看到代码有任何实际的删除效果。

:::tip 什么是死代码
[死代码（dead code）](https://en.wikipedia.org/wiki/Dead_code) 是指程序中一段已经不会被执行的代码，通常是因为重构、优化或者逻辑错误导致的。这些代码可能是之前版本的遗留物，或者某些条件下永远不会被执行的代码。
:::

## 前置条件

为了有效利用 tree shaking 功能，你需要：

- 将 Rspack 的 [mode](/zh/config/mode.md) 设置为 `production` 以启用相关优化。
  - 在执行生产环境构建时，`mode` 默认为 `production`。
- 使用 ES modules 语法（即 `import` 和 `export`）。
  - 在使用 SWC 或 Babel 等编译器时，确保它们没有将 ES modules 语法转换为 CommonJS。
  - 例如在 [@babel/preset-env](https://babeljs.io/docs/en/babel-preset-env) 中，需要将 `modules` 选项设置为 `false`。

## 配置项

当 [mode](/zh/config/mode.md) 被设置为 `production` 时，Rspack 会默认启用一系列与 tree shaking 相关的优化措施，包括：

- [usedExports](/zh/config/optimization.md#optimizationusedexports) 检查模块导出是否被使用到，没被使用到的导出可以被移除。
- [sideEffects](/zh/config/optimization.md#optimizationsideeffects) 检查模块是否包含副作用，如果不包含副作用可以被重导出优化。
- [providedExports](/zh/config/optimization.md#optimizationprovidedexports) 分析模块的所有导出和重导出来源。
- [innerGraph](/zh/config/optimization.md#optimizationinnergraph) 追踪变量的传递，更加准确地判断导出是否真的被使用到。

下面有一些例子来说明各个配置项的作用，为了增强文档的可读性，我们会使用伪代码来展示代码的删除效果。

让我们通过一个例子来更好地理解这一机制，假设 `src/main.js` 是项目的入口文件：

```js title='src/main.js'
import { foo } from './util.js';

console.log(foo);
// `bar` is not used
```

```js title='src/util.js'
export const foo = 1;
export const bar = 2;
```

在上述示例中，`util.js` 中的 `bar` 没有被使用。在 `production` 模式下，Rspack 会默认启用 [usedExports](/zh/config/optimization.md#optimizationusedexports) 优化，这能检测出哪些导出实际被使用了。未使用的导出如 `bar`，将被安全删除。最终的产物将类似：

```js title='dist/main.js'
const foo = 1;

console.log(foo);
```

## 副作用分析

在 `production` 模式中，Rspack 也会默认分析模块是否含有副作用。如果一个模块的所有导出都未被使用且没有副作用，那么整个模块都可以被删除。我们对上面的例子做一些修改：

```diff title='src/main.js'
import { foo } from './util.js';

- console.log(foo);
// `bar` is not used
```

此时 `util.js` 文件中的导出都没有被使用，且可以分析出该文件没有副作用，所以 `util.js` 可以被整个删除。

你也可以通过 `package.json` 或 `module.rules` 来手动标记模块是否包含副作用，需要开启配置 [optimization.sideEffects](/zh/config/optimization.md#optimizationsideeffects)。

在 `package.json` 中，可以用 `true` 或 `false` 标记当前 package 下全部 module 是否包含副作用。

```json title="package.json"
{
  "name": "package",
  "version": "1.0.0",
  "sideEffects": false
}
```

上面 `package.json` 表示该 package 下的所有 module 都没有副作用。

也可以用写 glob 或字符串匹配部分 module，没有被匹配到的 module 会直接视为没有副作用，因此如果手动标记 side effects，一定要确保没有标记到的 module 都没有副作用。

```json title="package.json"
{
  "name": "package",
  "version": "1.0.0",
  "sideEffects": ["./src/main.js", "*.css"]
}
```

上述写法表示除了 `./src/main.js` 和所有的 `.css` 文件外，其他的 module 都没有副作用。

## 重导出分析

重导出在开发中非常常见，但如果一个模块中包含重导出过多，这个模块会引入太多的其他模块，但一般我们只需要其中的一小部分，Rspack 能够优化这种场景，确保引用方可以直接访问到实际的导出模块。看以下包含重导出的例子：

```js title='src/main.js'
import { value } from './re-exports.js';
console.log(value);
```

```js title='src/re-exports.js'
export * from './value.js';
export * from './other.js'; // this can be removed if `other.js` does not have any side effects
```

```js title='src/value.js'
export const value = 42;
export const foo = 42; // not used
```

Rspack 默认开启 [providedExports](/zh/config/optimization.md#optimizationprovidedexports)，这可以分析出重导出模块中的所有导出以及他们各自的来源。

如果 `src/re-exports.js` 不包含副作用，Rspack 可以将 `src/main.js` 中对 `src/re-exports.js` 的引入，直接转换成对 `src/value.js` 的引入，效果类似于：

```diff title='src/main.js'
- import { value } from './re-exports.js';
+ import { value } from './value.js';
console.log(value);
```

这样做的好处是可以直接忽略掉整个 `src/re-exports.js` 模块。

由于能够分析出 `src/re-exports.js` 所有的重导出，可以知道 `src/value.js` 中的 `foo` 并未使用到，最终产物中 `foo` 会被删掉。

## 变量传递

有时候，即使导出被访问到，它们也可能并不会被实际使用到。例如：

```js title='src/main.js'
import { foo } from './value.js';

function log() {
  console.log(foo);
} // `log` is not used

const bar = foo; // `foo` is not used
```

在上例中，即使 `log` 函数和 `bar` 变量依赖了 `foo`，由于他们自身未被使用到，`foo` 仍然可以被视为死代码并删除。

在开启 [innerGraph](/zh/config/optimization.md#optimizationinnergraph) 优化后（在 `production` 模式下默认开启），针对跨模块的复杂情形，Rspack 依然能够追踪变量的使用情况，从而实现精确的代码优化。

```js title='src/main.js'
import { value } from './bar.js';
console.log(value);
```

```js title='src/bar.js'
import { foo } from './foo.js';
const bar = foo;
export const value = bar;
```

```js title='src/foo.js'
export const foo = 42;
```

在这种情况下，由于 `value` 最终被使用，它所依赖的 `foo` 也得以保留。

## Pure 注解

通过 [`/*#__PURE__*/`](https://github.com/javascript-compiler-hints/compiler-notations-spec/blob/main/pure-notation-spec.md) 注解可以告诉 Rspack 某个函数调用无副作用。它可以被放到函数调用之前，用来标记此函数调用是无副作用的。

如果一个没被使用的变量定义的初始值被认为是无副作用的，它会被标记为死代码，不会被执行且会被压缩工具清除掉。

```js
/*#__PURE__*/ double(55);
```

:::tip

- 传入函数的参数无法被这个注释所标记，需要单独对每一个参数进行标记。
- 当 [optimization.innerGraph](/zh/config/optimization.md#optimizationinnergraph) 被设置成 `true` 时这个行为将被启用。

:::

## NO\_SIDE\_EFFECTS 注解

`/*#__NO_SIDE_EFFECTS__*/` 是一项遵循社区[规范](https://github.com/javascript-compiler-hints/compiler-notations-spec/blob/main/no-side-effects-notation-spec.md) 的特性。与 `/*#__PURE__*/` 标记在调用方不同，`/*#__NO_SIDE_EFFECTS__*/` 用于标记**函数的定义**，声明该函数在被调用时不会产生副作用。
相比 `/*#__PURE__*/`，它的优势在于只需在定义处标记一次，所有调用都会受益。

该功能目前处于实验阶段，由 `experiments.pureFunctions` 控制；该选项在生产模式下默认开启。它还依赖 [optimization.sideEffects](/zh/config/optimization.md#optimizationsideeffects) 为 `true`（sideEffects 在生产模式下默认值为 `true`）：

```js title="rspack.config.mjs"
export default {
  experiments: {
    pureFunctions: true,
  },
};
```

你可以将该注解放在函数声明、函数表达式、箭头函数或导出语句之前，更多请参考社区[规范](https://github.com/javascript-compiler-hints/compiler-notations-spec/blob/main/no-side-effects-notation-spec.md)：

```js
/*#__NO_SIDE_EFFECTS__*/
function foo() {
  console.log('foo');
}

/*#__NO_SIDE_EFFECTS__*/
const bar = () => {
  console.log('bar');
};

/*#__NO_SIDE_EFFECTS__*/
export default function baz() {}

/*#__NO_SIDE_EFFECTS__*/
export function baz() {}
```

如果一个函数被标记为无副作用且其返回值未被使用，Rspack 将移除该调用。

## pureFunctions

`pureFunctions` 允许你在命中的模块中，手动将顶层标识符标记为“调用无副作用”，供基于 pure function 的 tree shaking 使用。配置的名字必须能解析到该模块中的顶层绑定 —— 即顶层的 function/class/变量声明、`import` 引入的标识符，或它们的 export 别名。这既适用于无法修改源码的第三方库（例如 `node_modules` 中的包），也适用于在消费侧直接断言某个 import 调用是 pure 的场景。

配置方式请参考 [`experiments.pureFunctions`](/zh/config/experiments.md#experimentspurefunctions) 和 [`module.parser.javascript.pureFunctions`](/zh/config/module-parser.md#javascriptpurefunctions)。

:::warning
该选项目前是实验能力，且只有在启用 `experiments.pureFunctions` 时才会生效；该实验选项在生产模式下默认开启。
:::

:::note
对于配置在源模块上的默认导出，请使用 `default` 作为名字。
:::

对于可以修改源码的业务代码，优先使用 `/*#__NO_SIDE_EFFECTS__*/` 注解。

### 使用方式

该选项配置在 `module.rules` 的 `parser` 中：

```js title="rspack.config.mjs"
export default {
  experiments: {
    pureFunctions: true,
  },
  module: {
    rules: [
      // 将第三方库中的导出标记为 pure
      {
        test: /node_modules\/some-library\/index.js/,
        parser: {
          pureFunctions: ['isString', 'default'],
        },
      },
      // 也可以在消费侧标记 import
      {
        test: /src\/styles\.js$/,
        parser: {
          pureFunctions: ['cva'],
        },
      },
    ],
  },
};
```

Rspack 会校验 `pureFunctions` 中配置的每个名字是否能在命中模块中解析到顶层绑定；如果找不到，就会发出警告。


本页改编自 [webpack 文档](https://webpack.docschina.org/guides/tree-shaking/)，遵循 [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/)，且已作修改。

