最近朋友面试问了关于tree shaking
相关问题,之前对tree shaking
不是很了解,只知道是减少打包体积用的。趁此机会了解了一下它的原委。
当前端项目到达一定的规模后,我们一般会采用按模块方式组织代码,这样可以方便代码的组织及维护。但会存在一个问题,比如我们有一个utils
工具类,在另一个模块中导入它。这会在打包的时候将utils
中不必要的代码也打包,从而使得打包体积变大,这时候就需要用到Tree shaking
技术了。
Tree shaking
是一种通过清除多余代码方式来优化项目打包体积的技术,专业术语叫Dead code elimination
首先,新建一个简单的webpack项目,项目结构如下(推荐使用vscode编辑器):
主要文件如下:
- package.json
1 | { |
- webpack.config.js
1 | const path = require('path'); |
可在项目根目录运行打包命令
1 | npm run build |
接下来,创建utils.js
文件:
1 | export function add(a, b) { |
index.js
文件中导入utils.js
的add
方法并调用:
1 | import { add } from './utils'; |
运行npm run build
后查看dist/bundle.js
文件,可以发现utils.js
中所有的代码都打包了,并没有像我们预期的那样只打包add()
函数。
当启用tree shaking
后,多余的代码就不会打入最终的文件。
tree shaking 如何工作的呢?
虽然tree shaking
的概念在1990就提出了,但知道ES6的ES6-style
模块出现后才真正被利用起来。这是因为tree shaking
只能在静态modules
下工作。ECMAScript 6
模块加载是静态的,因此整个依赖树可以被静态地推导出解析语法树。所以在ES6
中使用tree shaking
是非常容易的。而且,tree shaking
不仅支持import/export
级别,而且也支持statement(声明)
级别。
在ES6
以前,我们可以使用CommonJS
引入模块:require()
,这种引入是动态的,也意味着我们可以基于条件来导入需要的代码:
1 | let dynamicModule; |
CommonJS
的动态特性模块意味着tree shaking
不适用。因为它是不可能确定哪些模块实际运行之前是需要的或者是不需要的。在ES6
中,进入了完全静态的导入语法:import。这也意味着下面的导入是不可行的:
1 | // 不可行,ES6 的import是完全静态的 |
我们只能通过导入所有的包后再进行条件获取。如下:
1 | import foo from "foo"; |
ES6
的import
语法完美可以使用tree shaking
,因为可以在代码不运行的情况下就能分析出不需要的代码。
如何使用Tree shaking
从webpack 2
开始支持实现了Tree shaking
特性,webpack 2
正式版本内置支持ES2015
模块(也叫做harmony
模块)和未引用模块检测能力。新的webpack 4
正式版本,扩展了这个检测能力,通过package.json
的 sideEffects
属性作为标记,向compiler
提供提示,表明项目中的哪些文件是 “pure(纯的 ES2015 模块)”,由此可以安全地删除文件中未使用的部分。
本项目中使用的是webpack4
,只需要将mode
设置为production
即可开启tree shaking
1 | entry: './src/index.js', |
如果是使用webpack2
,可能你会发现tree shaking
不起作用。因为babel
会将代码编译成CommonJs
模块,而tree shaking
不支持CommonJs
。所以需要配置不转义:
1 | options: { presets: [ [ 'es2015', { modules: false } ] ] } |
参考tree-shaking-es6-modules-in-webpack-2
关于side effects(副作用)
side effects
是指那些当import
的时候会执行一些动作,但是不一定会有任何export
。比如ployfill
,ployfills
不对外暴露方法给主程序使用。
tree shaking
不能自动的识别哪些代码属于side effects
,因此手动指定这些代码显得非常重要,如果不指定可能会出现一些意想不到的问题。
在webapck中
,是通过package.json
的sideEffects
属性来实现的。
1 | { |
如果所有代码都不包含副作用,我们就可以简单地将该属性标记为false
,来告知 webpack
,它可以安全地删除未用到的export
导出。
如果你的代码确实有一些副作用,那么可以改为提供一个数组:
1 | { |
总结
tree shaking
不支持动态导入(如CommonJS的require()语法),只支持纯静态的导入(ES6的import/export)webpack
中可以在项目package.json
文件中,添加一个 “sideEffects” 属性,手动指定由副作用的脚本
tree shaking
其实很好理解:一颗树,用力摇一摇,枯萎的叶子会掉落下来。剩下的叶子都是存活的
参考: