tree-shaking

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Tree Shaking

Tree Shaking

It can happen that we add code to our bundle that isn't used anywhere in our application. This piece of dead code can be eliminated in order to reduce the size of the bundle, and prevent unnecessarily loading more data! The process of eliminating dead code before adding it to our bundle, is called tree-shaking.
我们可能会向包中添加一些在应用中从未使用过的代码。这些死代码可以被移除,从而减小包体积,避免不必要的数据加载!在将代码加入包之前移除死代码的过程被称为Tree Shaking。

When to Use

使用场景

  • Use this when your bundle includes unused code from imported modules
  • This is helpful for keeping JavaScript bundles lean and improving load performance
  • 当你的包中包含来自导入模块的未使用代码时使用此方法
  • 这有助于保持JavaScript包精简,提升加载性能

Instructions

操作指南

  • Use ES2015
    import
    /
    export
    syntax — only ES modules can be tree-shaken
  • Use named imports instead of importing entire modules to enable effective tree-shaking
  • Mark packages as side-effect-free in
    package.json
    when appropriate
  • Be aware that modules with side effects cannot be safely tree-shaken
  • 使用ES2015的
    import
    /
    export
    语法——只有ES模块支持Tree Shaking
  • 使用命名导入而非导入整个模块,以实现有效的Tree Shaking
  • 适当时在
    package.json
    中将标记包为无副作用
  • 注意:带有副作用的模块无法安全地进行Tree Shaking

Details

详细说明

Concepts

核心概念

Tree shaking is aimed at removing code that will never be used from a final JavaScript bundle. When done right, it can reduce the size of your JavaScript bundles and lower download, parse and (in some cases) execution time. For most modern JavaScript apps that use a module bundler (like webpack or Rollup), your bundler is what you would expect to automatically remove dead code.
Consider your application and its dependencies as an abstract syntax tree (we want to "shake" the syntax tree to optimize it). Each node in the tree is a dependency that gives your app functionality. In tree shaking, input files are treated as a graph. Each node in the graph is a top level statement which is called a "part" in the code. Tree shaking is a graph traversal which starts from the entry point and marks any traversed paths for inclusion.
Every component can declare symbols, reference symbols, and rely on other files. Even the "parts" are marked as having side effects or not. For example, the statement
let firstName = 'Jane'
has no side effects because the statement can be removed without any observed difference if nothing needs firstName. But the statement
let firstName = getName()
has side effects, because the call to
getName()
can not be removed without changing the meaning of the code, even if nothing needs firstName.
Tree Shaking旨在从最终的JavaScript包中移除永远不会被使用的代码。操作得当的话,它可以减小JavaScript包的体积,缩短下载、解析(在某些情况下)以及执行时间。对于大多数使用模块打包工具(如webpack或Rollup)的现代JavaScript应用,打包工具会自动移除死代码。
可以将应用及其依赖视为一棵抽象语法树(我们需要“摇晃”这棵语法树来进行优化)。树中的每个节点都是为应用提供功能的依赖项。在Tree Shaking中,输入文件被视为一个图。图中的每个节点都是一个顶级语句,在代码中被称为一个“部分”。Tree Shaking是一种从入口点开始的图遍历过程,会标记所有遍历到的路径以纳入最终包中。
每个组件都可以声明符号、引用符号,并依赖其他文件。甚至这些“部分”也会被标记为是否有副作用。例如,语句
let firstName = 'Jane'
没有副作用,因为如果没有任何地方需要firstName,移除该语句不会产生任何可见差异。但语句
let firstName = getName()
有副作用,因为即使没有地方需要firstName,移除对
getName()
的调用也会改变代码的含义。

Imports

导入方式

Only modules defined with the ES2015 module syntax (
import
and
export
) can be tree-shaken. The way you import modules specifies whether the module can be tree-shaken or not.
Tree shaking starts by visiting all parts of the entry point file with side effects, and proceeds to traverse the edges of the graph until new sections are reached. Once the traversal is completed, the JavaScript bundle includes only the parts that were reached during the traversal. The other pieces are left out.
Let's say we define the following
utilities.js
file:
js
export function read(props) {
  return props.book
}

export function nap(props) {
  return props.winks
}
Then we have the following index.js file:
js
import { read } from 'utilities';

eventHandler = (e) => {
  read({ book: e.target.value })
}
In this example,
nap()
isn't important and therefore won't be included in the bundle.
只有使用ES2015模块语法(
import
export
)定义的模块才能进行Tree Shaking。导入模块的方式决定了该模块是否可以被Tree Shaking处理。
Tree Shaking从入口文件中所有带有副作用的部分开始遍历,然后沿着图的边继续遍历,直到到达新的部分。遍历完成后,JavaScript包只会包含遍历过程中到达的部分,其他部分则会被排除在外。
假设我们定义了如下的
utilities.js
文件:
js
export function read(props) {
  return props.book
}

export function nap(props) {
  return props.winks
}
然后我们有如下的index.js文件:
js
import { read } from 'utilities';

eventHandler = (e) => {
  read({ book: e.target.value })
}
在这个例子中,
nap()
并未被使用,因此不会被包含在最终包中。

Side Effects

副作用

When we're importing an ES6 module, this module gets executed instantly. It could happen that although we're not referencing the module's exports anywhere in our code, the module itself affects the global scope while it's being executed (polyfills or global stylesheets, for example). This is called a side effect. Although we're not referencing the exports of the module itself, if the module has exported values to begin with, the module cannot be tree-shaken due to the special behavior when it's being imported!
The Webpack documentation gives a clear explanation on tree-shaking and how to avoid breaking it.
当我们导入一个ES6模块时,该模块会立即执行。可能会出现这种情况:尽管我们的代码中从未引用该模块的导出内容,但模块本身在执行时会影响全局作用域(例如polyfills或全局样式表)。这被称为副作用。即使我们没有引用模块的导出内容,如果该模块本身有导出值,由于导入时的特殊行为,该模块也无法被Tree Shaking处理!
Webpack文档对Tree Shaking以及如何避免破坏它有清晰的解释

Source

来源