摇树优化

Rspack 支持摇树优化,这个术语在 JavaScript 生态系统中被广泛使用,指的是删除未使用的代码,通常被称为“死代码”。当模块中的某些导出没有被使用,并且它们没有副作用时,就会出现死代码,这种情况下可以安全地删除这些代码,以减少最终的输出大小。

在将 mode 设置为 production 后,Rspack 默认情况下会启用一系列与摇树优化相关的优化,包括

  • usedExports: 检查模块导出是否被使用,允许删除未使用的导出。
  • sideEffects: 评估模块是否有副作用。没有副作用的模块可以通过重新导出进行进一步优化。
  • providedExports: 分析所有导出及其重新导出来源。
  • innerGraph: 追踪变量的传递,提高确定导出是否实际使用的准确性。

以下是一些示例,说明这些配置选项如何工作。

信息

请注意,Rspack 不会直接删除死代码,而是将未使用的导出标记为潜在的“死代码”。这些标签可以被后续的压缩工具识别和处理。因此,如果关闭了压缩功能,则不会观察到实际的代码删除。为了提高可读性,可能会使用伪代码来演示代码删除的效果。

让我们通过一个示例来更好地理解这种机制,假设 src/main.js 是项目的入口点

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

console.log(foo);
// `bar` is not used
src/util.js
export const foo = 1;
export const bar = 2;

在这个示例中,util.js 中的 bar 未使用。在 production 模式下,Rspack 默认情况下会启用 usedExports 优化,检测哪些导出被积极使用。未使用的导出,比如 bar,会被安全地删除。最终的输出将类似于

dist/main.js
const foo = 1;

console.log(foo);

副作用分析

production 模式下,Rspack 通常还会分析模块是否有副作用。如果模块中的所有导出都没有被使用,并且模块没有副作用,那么整个模块就可以被删除。让我们稍微修改一下之前的示例

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

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

在这种情况下,util.js 中的导出都没有被使用,并且它被分析为没有副作用,允许删除整个 util.js

您可以通过 package.jsonmodule.rules 手动指定模块是否保留副作用。有关如何操作的信息,请参阅 sideEffects

重新导出分析

重新导出在开发中很常见。但是,一个模块可能会引入许多其他模块,而实际上可能只需要其中一部分。Rspack 通过确保引用方可以直接访问实际导出的模块来优化这种情况。考虑以下涉及重新导出的示例

src/main.js
import { value } from './re-exports.js';
console.log(value);
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
src/value.js
export const value = 42;
export const foo = 42; // not used

Rspack 默认情况下会启用 providedExports,它可以分析重新导出模块的所有导出并识别其各自的来源。

如果 src/re-exports.js 没有副作用,Rspack 可以将 src/main.js 中的导入从 src/re-exports.js 直接转换为从 src/value.js 的导入,实际上

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 没有被使用,并且将在最终输出中被删除。

变量传递

在某些情况下,即使访问了导出,它们也可能没有实际使用。例如

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 优化(在 production 模式下默认启用)后,对于复杂的跨模块情况,Rspack 保持了追踪变量使用情况的能力,从而实现了精确的代码优化。

src/main.js
import { value } from './bar.js';
console.log(value);
src/bar.js
import { foo } from './foo.js';
const bar = foo;
export const value = bar;
src/foo.js
export const foo = 42;

在这种情况下,由于 value 最终被使用,它依赖的 foo 被保留。