re-exports 带来的性能问题与 Tree shaking 的局限性

date
Dec 4, 2020
slug
re-exports-and-tree-shaking
status
Published
tags
ES Module
Tree Shaking
summary
type
Post
  1. 它会使code-splitting变得更加困难
    1. import { Button } from './components'
      上述代码不仅仅引入了 Button,还引入了整个 components。
  1. 每次浏览器下载 bunle 时,它都必须执行它-及其所有模块。

在工具库中,一种常见的模式是在一个文件中导出来自其他文件的内容,如下图所示:
notion image
当我们使用如下代码从该文件导入组件时:
import { Button } from './components';
导入的并不仅仅是 Button 这一个组件,而是 components/index.js 所导入的所有组件。
这种行为会对同时对打包性能和页面加载性能带来影响。这与模块副作用有关。

副作用

副作用在此处可以理解为:一个函数可能会对函数外部变量产生影响的行为。例如:
let c = 1;
const A = () => `A = ${c++}`;
A 即是一个含有副作用的函数,因为它会改变外部变量 c。
面对有副作用的代码模块,Webpack 是无法直接删除未使用的代码的,因为这可能会带来问题。举一个例子:
// index.js 入口文件
(async () => {
  await import('./chunk1.js');
  await import('./chunk2.js');
}());

// chunk1.js
import { A } from "module";
console.log(A());

// chunk2.js
import { B } from "module";
console.log(B());

// module.js
let c = 1;
export const A = () => `A = ${c++}`;
export const B = () => `B = ${c++}`;
我们期望打包完后的代码执行结果是 A=1 B=2
module.js 是一个含有副作用的模块,但如果 Webpack 仍然对两个 chunk 文件做 Tree shaking 处理的话,处理完之后 module 在两个 chunk 中的代码如下所示:
// module.js in chunk1
let c = 1;
export const A = () => `A = ${c++}`;

// module.js in chunk1
let c = 1;
export const B = () => `B = ${c++}`;
代码的执行结果变成了A=1 B=1,这与我们的代码预期不符。

Tree shaking 的局限性

Webpack 的 Tree shaking 功能负责移除 JavaScript 上下文中的未引用代码,但是它检测未使用部分代码时需要判断哪些模块是无副作用(side effects)的,若含副作用的模块,则不会执行 Tree Shaking。

Webpack 默认所有非 ES Modules 的代码都具有副作用

为了安全起见 Webpack 默认所有非 ES modules 的代码模块都具有副作用,即 Tree-shaking 对非 ES Modules 不起效。

(Webpack4)re-exports 在多个 chunk 场景下无法做到 Tree-shaking

在上述 ES Module re-exports 的场景中,Webpack Tree shaking 不能完美执行,即它没法在多个 chunk 之间做到 Tree-shaking,见:

解决方案

In a 100% ESM module world, identifying side effects is straightforward. However, we aren't there just yet, so in the mean time it's necessary to provide hints to webpack's compiler on the "pureness" of your code.The way this is accomplished is the "sideEffects" package.json property.
对于工具、组件库类型的 npm 包,我们可以通过 package.json"sideEffects" 属性向 Webpack 表明库中的哪些文件是无副作用的,由此可以安全地删除文件中未使用的部分,或者可以直接设置值为false,表示整个库都不含副作用。re-exports 带来的性能问题就此解决。

参考资料
  1. https://github.com/webpack/webpack/issues/7782
  1. https://twitter.com/iamakulov/status/1331551383804403713
  1. https://webpack.js.org/guides/tree-shaking/

© Sytone 2021