内容简介:在上周末广州举办的feday中,webpack的核心开发者Sean在介绍webpack插件系统原理时,隆重介绍了一个中国学生于Google夏令营,在导师Tobias带领下写的一个webpack插件,tree-shaking 作为 rollup 的一个杀手级特性,能够利用ES6的静态引入规范,减少包的体积,避免不必要的代码引入,webpack2也很快引入了这个特性,但是目前,webpack只能做比较简单的解决方案,比如:这个例子中,webpack会寻找引入变量的引用,当发现没有对isNumber的引用时,就
在上周末广州举办的feday中,webpack的核心开发者Sean在介绍webpack插件系统原理时,隆重介绍了一个中国学生于Google夏令营,在导师Tobias带领下写的一个webpack插件, webpack-deep-scope-analysis-plugin ,这个插件能够大大提高webpack tree-shaking的效率。
tree-shaking目前的缺陷
tree-shaking 作为 rollup 的一个杀手级特性,能够利用ES6的静态引入规范,减少包的体积,避免不必要的代码引入,webpack2也很快引入了这个特性,但是目前,webpack只能做比较简单的解决方案,比如:
这个例子中,webpack会寻找引入变量的引用,当发现没有对isNumber的引用时,就会去除isNumber的代码。这其实不太实用,毕竟在现在的vscode中,没有引用的变量在ide中都会灰显提示,一般不会犯这种import某个模块却不用的错误了。
如果是接下来这种引入方式呢,我写了一个demo如下
这个例子非常简单,如果用图来表示是这样
在index.js中引入了func.js中的func2,并没有引入func1,但是func1引入了lodash。webpack检查的时候发现func.js中的确用到了lodash,所以不会把lodash去掉。实际上,我们根本没用到它。
webpack-deep-scope-analysis-plugin就可以解决这种判断。
插件效果
引入前
引入后
85.8kb -> 不到1kb
当然,我这里是标题党了,因为这里直接把一个lodash库给去掉了,所以变化才这么惊人。但是即使在实际项目中,我们也能轻易用一个插件减少大量的不必要的引入。
原理
那么这个插件是怎么去解决这个问题的呢?这里根据原作者在Medium上写的文章,简单介绍一下他的做法。
webpack的原理,其实就是遍历所有的模块,把它们打包成一个文件,在这个过程中,它就知道哪些export的模块有被使用到。那我们同样也可以遍历所有的scope(作用域),简化没有用到的scope,最后只留下我们需要的。
上图中,func5层层引用fun4 fun3 fun2 fun1,最后解析出来其实只使用了deepEqual模块。
什么是scope呢,其实scope在各个语言中都有存在,在Wikipedia中是作为计算机术语,有更详细的解释,我觉得可以翻译为作用域或者上下文,在ECMAScript中,有以下明确的定义:
// module scope start // Block { // <- scope start } // <- scope end // Class class Foo { // <- scope start } // <- scope end // If else if (true) { // <- scope start } /* <- scope end */ else { // <- scope start } // <- scope end // For for (;;) { // <- scope start } // <- scope end // Catch try { } catch (e) { // <- scope start } // <- scope end // Function function() { // <- scope start } // <- scope end // Scope switch() { // <- scope start } // <- scope end // module scope end 复制代码
在ES6中,module是一种根作用域,只有function和class才能作为子作用域被导出,所以我们解析的时候,不会把所有的scope都作为节点算进去。
我们提到的这个webpack插件,正是内置了这样一个scope分析器,它能够从入口文件中分析出scope的引用关系,最后排除掉所有没有用到的模块。
当然,这个插件也并不是自己做了所有的事情,它也是依赖于了前人的工作。 escope 是一个分析ES中scope的工具,插件作者将它改成了ts版本集成到了插件中,并且利用了webpack暴露的接口,可以解析出来的模块的AST树,基于这个AST就可以交给escope分析出scope的引用关系。
一些边际用例
凡事不能完美,这个插件也有一些情况会导致判断失误
情况一:重复赋值变量
比较典型的是以下这个例子:
import { isNull } from 'lodash-es'; var fun = 1; fun = function scope(...args) { return isNull(...args); } export { fun } 复制代码
这个例子中fun变量一开始被赋值为数字,然后被赋值成一个函数,但是scope分析器会直接跳过这个变量,不把它当作一个单独的scope。
情况二:纯函数
// copy from rambda/es/allPass.js import _curry1 from './internal/_curry1'; import curryN from './curryN'; import max from './max'; import pluck from './pluck'; var allPass = /*#__PURE__*/_curry1(function allPass(preds) { return curryN(reduce(max, 0, pluck('length', preds)), function () { var idx = 0; var len = preds.length; while (idx < len) { if (!preds[idx].apply(this, arguments)) { return false; } idx += 1; } return true; }); }); export default allPass; 复制代码
在这个例子中, import allPass
会导致 _curry1
的运行,因此它不会被当作一个单独的scope,因为它可能会有一些“副作用”,比如改变某个全部变量,对全局造成影响。 所以作者给了个方案,可以在这个函数前加 /*#__PURE__*/
,这样就会把这个函数视为无副作用的纯函数,如果我们没有 import allPass
,它引用的其他模块都会被去除。
最佳实践
首先,要用到tree-shaking,必然要保证引用的模块都是ES6规范的。这也是为什么我在前面的demo中,引入的是 lodash-es
而不是 lodash
。
在项目中,注意要把 babel
设置 module: false
,避免babel将模块转为CommonJS规范。引入的模块包,也必须是符合ES6规范,并且在最新的webpack中加了一条限制,即在 package.json
中定义 sideEffect: false
,这也是为了避免出现 import xxx
导致模块内部的一些函数执行后影响全局环境,却被去除掉的情况。
未来
当时跟这位插件作者沟通,他说将来有可能Tobias会把这个插件内置到webpack中,这也是符合webpack4零配置的趋势。但是我们也看得到,要将前端工程的dead code elimination做到和其他静态语言一样好,靠这些 工具 是远远不够的,模块自身也必须配合做到符合规范。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:- 前端 Docker 镜像体积优化
- 缩减Docker镜像体积历程总结
- 缩减Docker镜像体积历程总结
- APK体积优化的一些总结
- 重构之路:webpack打包体积优化(超详细)
- Laravel框架中缩小Vue应用的体积
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
C++ Primer 中文版(第 5 版)
[美] Stanley B. Lippman、[美] Josée Lajoie、[美] Barbara E. Moo / 王刚、杨巨峰 / 电子工业出版社 / 2013-9-1 / CNY 128.00
这本久负盛名的 C++经典教程,时隔八年之久,终迎来史无前例的重大升级。除令全球无数程序员从中受益,甚至为之迷醉的——C++ 大师 Stanley B. Lippman 的丰富实践经验,C++标准委员会原负责人 Josée Lajoie 对C++标准的深入理解,以及C++ 先驱 Barbara E. Moo 在 C++教学方面的真知灼见外,更是基于全新的 C++11标准进行了全面而彻底的内容更新。......一起来看看 《C++ Primer 中文版(第 5 版)》 这本书的介绍吧!