what
首先,我们来看一张图。(很形象,借用别处)

如图所示,我们的js文件就相当于这棵树,tree-shaking就相当对摇动这个操作,目的是让枯黄的叶子和坏掉的苹果掉下来。说到这里,tree-shaking的原理就是,通过摇动我们的js文件,剔除掉DCE(Dead Code Elimination)。是一种优化性能的方式。
why
具体说,每一个webpack项目中,都有一个入口,相当于树的主干,依赖了很多模块,相当于树枝。在我们的实际使用的过程中,我们其实只是使用了模块里的某个方法,我们需要把不使用的树枝给摇落下来,也就是过滤掉这些无用的代码。起到性能优化的目的。

用到这一方法的工具有rollUp,webpack,cc。该文章只介绍webpack中tree-shaking的使用。
原理
tree-shaking的本质是消除无用代码。无用代码也就是所谓的dead code elimination。
function test(){
const a = 1;
return a+1;
// dce 不会被执行的代码
const b = 2;
return b;
}
test();
function test(){
const a = 1;
if(false){
....
}
const b = 2;
return b;
}
test();
什么是Dead Code Elimination?
- 不会被执行的代码。
- 代码执行的结果不会被用到。
- 代码只写不读。
传统的编译型语言中,都是将DCE从AST(抽象语法树)中删除掉。那JavaScript是怎么做到呢?
JavaScript怎么剔除DCE的呢?
webapck的DCE是uglify做的,你可能会问,那tree-shaking 是做什么呢?tree-shaking只是帮助DCE。打个比方,就好像我们要从一堆水果里面,拿掉苹果。首先我们得把苹果选出来。tree-shaking就是一个选苹果的过程。但是拿掉苹果是选出来之后的操作。
实践
目录结构:

// ./src/index.js
import printMe from "./print.js"; // unused
import { cube } from './math.js';
import menu from './menu.js'; // unused
import './styles.css';
// if(process.env.NODE_ENV !== 'production'){
// console.log('development mode!')
// }
function component(){
var element = document.createElement('pre');
element.innerHTML = [
'hello webpack',
'5 cubed is equal to ' + cube(5)
].join('\n\n')
return element;
}
document.body.appendChild(component());
mode='development'
webpack的mode='development'会将 DefinePlugin 中 process.env.NODE_ENV 的值设置为 development。启用 NamedChunksPlugin 和 NamedModulesPlugin。

打包结果:


mode='production'
mode='production'会将 process.env.NODE_ENV 的值设为 production。启用 FlagDependencyUsagePlugin, FlagIncludedChunksPlugin, ModuleConcatenationPlugin, NoEmitOnErrorsPlugin, OccurrenceOrderPlugin, SideEffectsFlagPlugin 和 UglifyJsPlugin.
打包结果:


使用注意事项
先思考一个问题,为什么tree-shaking是最近几年流行起来了?而前端模块化概念已经有很多年历史了,其实tree-shaking的消除原理是依赖于ES6的模块特性。
require可以吗?
// import _ from "lodash";
import printMe from "./print.js";
import { cube } from './math.js';
// import menu from './menu.js';
const menu = require('./menu.js');
import './styles.css';
.....
production模式下,require的也被打进去了。所以require这种动态引入的是不行的。require需要js执行的时候才知道。import是属于静态引入。

借用官网注意事项:
- 使用ES2015模块语法(即import和export)。
- 在项目 package.json 文件中,添加一个 "sideEffects" 属性。(这个是告诉webpack可以过滤掉无用的代码,也可以根据条件设置对应的文件)
- 确保没有 compiler 将 ES2015 模块语法转换为 CommonJS 模块(这也是流行的 Babel preset 中 @babel/preset-env 的默认行为 )
- 通过将 mode 选项设置为 production,启用 minification(代码压缩) 和 tree shaking。