Tree Shaking

目录

  1. What Does Tree-Shaking Actually Mean?

  2. ES Modules Vs. CommonJS

  3. Scope And Side Effects

  4. Optimizing Webpack

  5. Webpack Version 3 And Below

  6. Avoid Premature Transpiling

  7. Tree-Shaking Checklist

Tree Shaking 是什么

Tree Shaking 通常用于描述移除 JavaScript 上下文中的未引用代码 (dead-code)。它依赖于 ES2015 模块系统中的静态结构特性,例如 import 和 export

这个术语和概念兴起于 ES2015 模块打包工具 rollup

Tree Shaking 是什么 - 模块化

模块化这个话题在 ES6 之前是不存在的,因此这也被诟病为早期 JavaScript 开发全局污染和依赖管理混乱问题的源头。

常见的模块化方案包含这几种:

  1. CommonJS

  2. AMD

  3. CMD

  4. UMD

  5. ES Modules

Tree Shaking 是什么 - CommonJS vs ES Modules

CommonJS 比 ES Modules 规范早了几年。 它旨在解决 JavaScript 生态系统中缺乏对可重用模块的支持。 CommonJS 有一个 require() 函数,它根据提供的路径获取外部模块,并在运行时将其添加到作用域中。

 

运行时执行的特点

  1. 无法在编译阶段确定产物
  2. 你可以在代码中随意使用 require,比如全局、函数、if/else 条件语句中等等

 

从 CommonJS 规范中吸取教训,ES Modules 标准采用 import/export 关键字对模块进行处理,且不依赖运行时执行结果

Tree Shaking 是什么 - CommonJS vs ES Modules

执行 require 后内存产物

{
  id: '...',
  exports: { ... },
  loaded: true,
  ...
}

CommonJS 的引用方式多种多样

const foo = require('foo');

function demo() {
	const foo = require('foo');
}

if (statement) {
	const foo = require('foo');
}

switch (key) {
    case value:
        const foo = require('foo');
        break;
    default:
        break;
}

// ...

Tree Shaking 是什么 - CommonJS vs ES Modules

ES Modules 标准的特点

  • 只能作为模块顶层的语句出现
  • import 的模块名只能是字符串常量
  • import binding 是 immutable的

Scope And Side Effect

一个函数是不是“纯”的?

const pure = 
	(a:number, b:number) => a + b
const impure = 
	(c:number) => window.foo.number + c

Scope And Side Effect

在 package.json 中标明

{
    "name": "my-package",
    "sideEffects": false
}

单独表明

var Button$1 = 
	/*#__PURE__*/ withAppProvider()(Button);
{
  "name": "your-project",
  "sideEffects": [
    "./src/some-side-effectful-file.js",
    "*.css"
  ]
}

Optimizing Webpack

在 webpack v4+ 开始

{
    "mode": 
    	process.env.NODE_ENV === 'production' ? 
        	'production' : 'development'

}

Optimizing Webpack

在 webpack v3 及之前

// transform.js
import * as mylib from 'mylib';

export const someVar = mylib.transform({
  // ...
});

export const someOtherVar = mylib.transform({
  // ...
});

// -------------------------------------------

// index.js

import { someVar } from './transforms.js';

// Use `someVar`...

Optimizing Webpack

// before transformation
import { Row, Grid as MyGrid } from 'react-bootstrap';
import { merge } from 'lodash';

// after transformation
import Row from 'react-bootstrap/lib/Row';
import MyGrid from 'react-bootstrap/lib/Grid';
import merge from 'lodash/merge';

Avoid Premature Transpiling

  1. Webpack loader 纷繁复杂,是先 Babel 还是先 TypeScript?
  2. target: node 时代码会被优先编译为 CommonJS 代码

Tree-Shaking Checklist

  1. 定义 type
  2. 指明各类入口
{
    // ...
    "type": "module",
    "main": "./index-cjs.js",
    "module": "./index-esm.js",
    "exports": {
        "require": "./index-cjs.js",
        "import": "./index-esm.js"
    }
    // ...
}

Tree-Shaking Checklist

  1. 在编程时使用 ES Modules 规范,同时优先考虑那些支持 ES Modules 的开源库。
  2. 确保你确切的知道哪些依赖项没有声明 sideEffects 或手动设置他们。
  3. 在使用具有 sideEffects 的包时,使用内联注释的方式来声明纯函数调用。
  4. 如果你在导出 CommonJS 模块,请确保在转换 import 和 output 语句之前优化好你的代码,即避免错误的编译过程。

Thanks