Tree-shaking
当前端项目到达一定的规模后,我们一般会采用按模块方式组织代码,这样可以方便代码的组织及维护。但会存在一个问题,比如我们有一个utils工具类,在另一个模块中导入它。这会在打包的时候将utils中不必要的代码也打包,从而使得打包体积变大,这时候就需要用到Tree shaking技术了
tree-shaking 是一种通过清除多余代码方式来优化项目打包体积的技术
原理
- 利用ES6模块的特点
- 只能作为模块顶层的语句出现
- import的模块名只能是字符串常量,不能动态引入模块
- import 引入的模块不能再进行修改的 虽然tree-shaking的概念在1990年就提出来了,但是直到ES6的ES6-style模块出现后才真正被利用起来。这是因为tree-shaking只能在静态模块下工作。ES6模块加载是静态的,因此在ES6种使用tree-shaking是非常容易地。而且,tree-shaking不仅支持import/export级别,而且也支持声明级别
在ES6以前,我们可以使用CommonJS引入模块:require(),这种引入是动态地,也意味着我们可以基于条件来导入需要的代码:
let mainModule;//动态导入if(condition){mainModule=require('dog')}else{mainModule=require('cat')}
CommonJS的动态特性意味着tree-shaking不适用。因为它是不可能确定哪些模块实际运行之前是需要的或者是不需要的。在ES6中,进入了完全静态的导入语法:import。
//不可行if(condition){mainModule=require('dog')}else{mainModule=require('cat')}
只能通过导入所有的包后再进行条件获取
import dog from 'dog';import cat from 'cat';if(condition){//dog.xxx}else{//cat.xxx}
ES6的import语法可以使用tree-shaking,因为可以在代码不运行的情况下就能分析出不需要的代码。
如何使用?
从webpack2开始支持实现了tree-shaking特性,webpack2正式版本内置支持ES6的模块(也叫harmony模块)和未引用模块检测能力。webpack4正式版本扩展了这个检测能力,通过package.json的sideEffects属性作为标记,向complier提供提示,表明项目中哪些文件是ES6模块,由此可以安全地删除文件中未使用地部分 如果使用的是webpack4,只需要将mode设置为production,就可以开启tree-shaking
entry:'./src/index.js',mode:'production',output:{path:path.resolve(__dirname,'dist'),filename:'bundle.js'},
如果使用webpack2,可能你会发现tree-shaking不起作用。因为babel会将代码编译成CommonJS模块,而tree-shaking不支持CommonJS,所以需要配置不转义
options:{presets:[['es2015',{modules:false}]]}
关于副作用
副作用是指那些当import的时候会执行一些动作,但是不一定会有任何export。比如ployfill,ployfills不对外暴露方法给主程序使用
tree-shaking不能自动识别哪些代码属于副作用,因此手动指定这些代码显得非常重要,如果不指定可能会出现一些意想不到的问题
在webpack中,是通过package.json的sideEffects属性来实现的
"name":"tree-shaking","sideEffects":false
如果所有的代码都不包含副作用,我们就可以简单地将该属性标记为false来告知webpack,它可以安全地删除未用到的export导出。
如果你的代码确实有一些副作用,那么可以改为提供一个数组:
"name":"tree-shaking","sideEffects":["./src/public/polyfill.js"]
总结
- tree-shaking不会支持动态导入(如CommonJS的require()语法),只纯静态的导入(ES6的import/export)
- webpack中可以在项目package.json文件中,添加一个”sideEffects”属性,手动指定副作用的脚本
