首先,在此声明一下,这篇文章之前写的时候也是有点模模糊糊,应该有很多错的地方(后半部分),或者逻辑比较乱的地方,各位小伙伴如果读这篇文章的话,感觉不清晰的地方就不要去深究了,以免浪费时间,同时把你们带偏,之后会抽空把文章纠正更新一下,在此对那些可能被我带偏的伙伴说声抱歉,同时也希望看我文章的哥们、小姐姐们能够指出我的一些问题,或者提供一些补充和一些小demo。
foreword(前言)
这里主要声明一下,对于下面出现的treeShaking失效是由于babel转码的原因,目前最新的webpack版本4.41.2是没问题的,而下面提到的这部分是由于本篇文章前一个版本在test时使用的版本为4.1.x,这或许是当时这个webpack版本中的一个bug,目前应该已经修复,所以这部分大家可以忽略。(大家可以主要从demo2中开始浏览。)
什么是treeShaking
tree shaking
tree shaking 是一个术语,通常用于描述移除 JavaScript 上下文中的未引用代码(dead-code)。它依赖于 ES2015 模块系统中的静态结构特性,例如 import 和 export。这个术语和概念实际上是兴起于 ES2015 模块打包工具 rollup。
新的 webpack 4 正式版本,扩展了这个检测能力,通过 package.json 的 “sideEffects” 属性作为标记,向 compiler 提供提示,表明项目中的哪些文件是 “pure(纯的 ES2015 模块)”,由此可以安全地删除文件中未使用的部分。
准备工作
-
目录:
–src
webpack.config.js -
webpack配置:
'use strict'
const path = require('path')
module.exports = {
mode: 'production',
entry: './src/index.js',
output: {
path: path.join(__dirname, 'dist'),
filename: 'bundle.js'
},
module: {
rules: [
{
test: /\.js$/,
use: [
'babel-loader'
]
}
]
}
}
- 依赖:
- webpack
- webpack-cli
- babel-loader(还需要配备其他依赖包,可以根据官网的命令下载,官网地址:https://www.npmjs.com/package/babel-loader)
webpack下如何配置treeShaking
首先先看一个小案例demo1,添加一个通用模块:
src/math.js:
export function square(x) {
console.log('square');
return x * x;
}
export function cube(x) {
console.log('cube');
return x * x * x;
}
接着,更新入口脚本,并使用其中一个方法:
src/index.js:
...
import { cube } from './math.js';
console.log(cube(2));
...
现在我们运行我们的npm脚本npm run build,并检查我们输出的bundle,如图所示:
注意,我们没有导入squre,但是它仍然被包含在bundle中,这个是为什么呢,为什么还会保留?小伙伴们可能会说“这还用问吗,现在根本就没有进行任何配置,自然不会帮我们进行优化”,真的是这样吗,我们继续看。
现在,我们在package.json文件中添加一个配置"sideEffects": false (这个配置的意思是将入口文件涉及到的文件都标识为不含副作用,即标示代码为pure——纯的代码,以此来告知webpack,它可以安全地删除未用到的export导出,副作用的理解可以参考这篇文章:http://www.fly63.com/article/detial/1176)
然后输入命令npm run build进行打包操作,结果如下:
我擦,根本没有上面卵用,和文档上是一样的啊。莫急莫急,我们接着来。
前方高能,胆小勿入(以下黑色粗体字为本人自我臆想,切莫相信,切莫相信)
同样的,没有进行treeShaking,没有引用的函数还是包含在bundle.js文件中,按道理,如果它真的会优化,当我们设置"sideEffects": false,它便会进行优化打包,实际上它没有,那么真的没有吗?
其实上面的所有情况(包括不设置sideEffects配置的情况)webpack都会帮我们进行优化,操作treeShaking,至于为什么,这里要提一下:
还记得官方怎么说的吗?
tree shaking 是一个术语,通常用于描述移除 JavaScript 上下文中的未引用代码(dead-code)。它依赖于
ES2015 模块系统中的静态结构特性,例如 import 和 export。这个术语和概念实际上是兴起于 ES2015 模块打包工具
rollup。
Tree shaking必须要依赖于es6的import和export,针对export引用的文件以及真正使用到的部分进行优化处理,而我们在这之前进行了babel转码的操作,对于转码后的es5语法我们就不会使用到tree shaking。
是的,上面的纯属本人扯淡,其实事实并非如此,其实在加了sideEffects时,webpack确实已经将我们的代码进行tree shaking,我们继续来玩玩吧:
我们在index.js入口文件中添加这样几句,如:
function test() {
return 1234;
}
test();
咱打下包吧,然后搜索一下1234看看,结果:
看到没找不到,说明webpack已经启动了tree shaking并检测出上述代码是dead code(无用代码),故而将上述代码删除。
那。。。,为哈子我们import时的代码中square函数仍然保留呢?这个嘛。。。这个确实跟es6语法有关:因为我们之前不是用了babel吗,babel把es6转化为es5的过程中,会使得转化之后的代码产生副作用,故而上述math.js中的square会存在其中。
所以呢,为了删除上面没有用到的square函数,我们需要去除掉babel的作用效果,即在webpack.config.js配置文件中注释掉babel的配置,如下:
接着我们重新打包文件:
看到没有,现在只剩下cube这个函数。(注:这次打包基于sideEffects设为false)
————————————————————————————————————
注:以上由于webpack测试版本原因会有babel的问题,目前最新版本(笔者测试版本为4.41.2)应该是没有问题的。
接下来,我们再看看几个小例子:
demo2: (sideEffects配置不设)
代码同demo1
然后,npm run build进行打包:
同样的square并没有被引入,所以我们可以想到,webpack默认是开启了treeShaking的。那么,默认开启的treeShaking是等同于怎样的配置呢?看下一个例子:
demo3:(sideEffects: “./src/*.*”,即表示项目所有脚本为具有副作用)
代码同demo1
在打包之前,先将上次的打包结果复制一份,然后再将代码打包,打包后的代码和前面备份的demo2的bundle进行比较,发现两份代码是一样的,也就是说,默认情况下开启的treeShaking和sideEffects标识所有脚本为具有副作用是等价的。
OK,接下来我们再看几个例子。
上面的demo1、demo2、demo3,都是将cube引入,然后执行,如果不执行cube呢,来试试看。
demo4:(sideEffects: false)
================================
math.js:
export function square(x) {
console.log('square');
return x * x;
}
export function cube(x) {
console.log('cube');
return x * x * x;
}
================================
index.js:
...
import { cube } from './math.js';
...
================================
打包后的结果如下:
demo5:(sideEffects配置项不设,代码同上)
代码同demo4
打包后结果和demo4一样。
这时,你可能非常怀疑,设不设sideEeffects不都是一样的吗?
非也,我们再来看一些例子:
demo6: (sideEffects: false)
================================
menu.js:
function Menu () {
console.log('menu');
}
Menu.prototype.show = function () {
console.log('menu prototype');
}
Array.prototype.unique = function () {
// 将 array 中的重复元素去除
console.log('unique');
}
export default Menu;
================================
index.js:
...
import menu from './menu.js'
...
================================
打包后,搜索“menu”和“unique”均为“找不到”。
demo7:(sideEffects不设)
代码同demo6
打包后,搜索“menu”、“menu prototype”、“unique”均能搜到。
rules(treeShaking解析规则)
基于上面的demo,我们可以得出以下几项规则:
- treeShaking一定会消除的代码:
- 声明的变量未调用,则会被删除;
- 调用某个函数时,函数仅仅声明了一些变量,并返回这个变量,或者返回的就是一个常量,这时仅仅调用函数,最终代码是会被消除的(除非对函数返回的值进行操作),另外“作为值赋值给一个变量,但这个变量无后续操作”也是会被消除的,还有一种情况就是函数内仅仅声明变量并调用,这样代码也是会被消除的;
- treeShaking一定不会消除的代码:(需要满足下面所有条件)
- 声明变量并使用它(计算、打印等);
- 对于函数,函数内需要有操作,比如:“let a = 1; a = a * a;”,或者“console.log(xxx)”,然后对函数进行调用;或者函数仅返回一个值,没有操作,这时要求函数返回的值必须被操作,比如作为另一个函数的参数输入,“作为值赋值给一个变量,但这个变量无后续操作”除外;
- 标识为副作用时才会保留:
- 函数符合上述1中的描述,并对函数原型进行了自定义设置,代码将在标识副作用时全部保留;
- 对诸如Array、Function、Object的prototype注入自定义的变量或函数时,这部分代码将会被保留;
summary(总结)
- webpack4.x默认对treeShaking进行了支持,默认情况下标识项目目录下所有文件存在副作用;
- treeShaking对代码的消除和保存满足上面的三点条件(个人测试,可能还有其他的条件,对这方面有了解的还望指出);
- treeShaking标识某些代码具有副作用即“告诉程序针对符合某些条件的代码块进行保留”;
thinking(思考)
由于这是周刊文,所以首先我们来回顾一下上一篇周文中笔者给出的思考:
- 第一个问题,debugger for chrome有什么优势呢:笔者个人认为这个插件产生的原因是chrome devtool在调试代码时的便捷性不够,比如我们要来回切换IDE和Browser,或者当我们通过浏览器下断点时,我们从souces里找文件笔者个人是觉得恶心的。debugger for chrome的出现使得代码调试更专注于代码,我们能够直接停留在断点处,然后停在问题出错处,然后直接修改代码。
- 第二个问题,对于个人编写的模块,常用debbger下断点,能使用debugger for chrome,最好使用它,样式问题则在浏览器中编写好、调整好后,复制粘贴到代码中保存,而如果是在别人代码中修改,比如修改css样式,如果不知道某些动态样式是在哪里进行设置的,可以设置dom断点等。另外,活用chrome devtool中的application和perfomance。
本期思考:
- 如何将treeShaking运用到实际项目中呢?(俺也不知道啊,有大大可以指点指点吗(^_^)。。。不对,按自己要把它研究出来,哼。。。)
- 如何根据项目类型、体量等各种因素标识项目某些代码文件存在副作用?
欢迎大家阅读我关于webpack4.x的其他博文:
https://blog.csdn.net/yaodebian/category_9282168.html
last(最后)
非常感谢您能阅读完这篇文章,您的阅读是我不断前进的动力。对于上面所述,有什么新的观点或发现有什么错误,希望您能指出。
最后,附上个人常逛的社交平台:
知乎:https://www.zhihu.com/people/bi-an-yao-91/activities
csdn:https://blog.csdn.net/YaoDeBiAn
github: https://github.com/yaodebian个人目前能力有限,并没有自主构建一个社区的能力,如有任何问题或想法与我沟通,请通过上述某个平台联系我,谢谢!!!