配置Tree Shaking来减少JavaScript的打包体积
译者按: 用Tree Shaking技术来减少JavaScript的Payload大小
为了保证可读性,本文采用意译而非直译。另外,本文版权归原作者所有,翻译仅用于学习。
小编推荐:Fundebug专注于JavaScript、微信小程序、微信小游戏,Node.js和Java线上bug实时监控。真的是一个很好用的bug监控服务,众多大佬公司都在使用。
如今一个网页应用可以体积很大,特别是JavaScript代码。2018年年中,HTTP Archive统计在移动端JavaScript文件的平均传输大小将近350KB。你要知道,这仅仅是传输的大小。在网络传输的时候,JavaScript往往是经过压缩的。也就是说,在浏览器解压缩之后,实际的大小会远远大于这个值。而这一点相当重要。如果考虑到浏览器处理数据的资源消耗,其中压缩是不得不考虑的。一个300KB的文件解压缩会达到900KB,并且在分析和编译的时候,体积依然是900KB。
其实,处理JavaScript是很耗资源的。不像图片只会在下载的时候有一点简单的解码处理,JavaScript需要分析,编译,然后再被执行。一个字节一个字节地处理,所以JavaScript的处理很贵。
为了优化JavaScript引擎,各种改进方法被提出来。提升JavaScript代码的性能,是开发者最擅长的事情。毕竟,有谁比架构师更擅长优化架构的性能呢?
Code splitting是其中一个用来提升性能的方法,通过将JavaScript应用拆分成一个个块,然后在需要的时候才下载。这个方法很好,但是有一个很常见的问题没有处理,那就是有很多打包的代码我们压根没有用到。为了解决这个问题,我们用tree shaking。
什么叫tree shaking ?
Tree shaking是一种消除无用代码(dead code)的方式。这个词是由最先从Rollup社区开始流行的,不过本身的理念很早就有了。在webpack中也有相同的理念,在本文我们会用一个例子来描述。
“tree shaking”这个词来自于应用的架构以及本身的依赖关系就像一个树形结构。树的每一个节点表示应用中一个唯一的功能。在现代网页应用中,依赖关系通常使用static import statement,如下所示:
// Import all the array utilities!
import arrayUtils from "array-utils";
当你的app还很小的时候,也许只有很少的依赖文件。而且应该几乎使用了所有你自己添加的依赖。但是,当你的app开发了一段时间,越来越多的依赖添加进去。由于各种原因,旧的依赖可能根本没有使用了,但是呢依然在你的代码库里面,没有被删除。最终导致你的app夹带了很多并没有使用的JavaScript。通过分析我们如何使用import语句,tree shaking会移除无用代码。注意:如果你不了解ES6,我强烈推荐你阅读Pony Foo上面的这篇文章。我们这篇文章假定你对ES6有一定的了解。如果没有,赶紧学学去吧。
// Import only some of the utilities!
import { unique, implode, explode } from "array-utils";
这个import语句和之前的区别在于,与其引入整个array-utils,而整个array-utils可能有非常多的函数,不如只引入我们需要的部分。在开发构建的时候,这两种使用方法并没有区别。但是在生产打包的时候,我们可以配置webpack来剔除不需要的函数,使得整个代码文件变小。在这篇文章中,我们会指导你如何做。
为了演示起见,我写了一个简单的单页应用。你可以克隆代码并跟着操作。我会详细描述每一步,所以克隆不是必备步骤。
示例是一个可以搜索吉他效果器的数据库。

应用在构建的时候,所有的JavaScript文件打包成了一个vendor和一个app文件。

上图中的文件是打包后的结果,已经经过uglification。21.1KB的大小完全可以接受。不过,当前是没有使用tree shaking来优化的结果。我们来看看如何进一步优化。
在任何应用中,寻找使用tree shaking优化的机会首先要寻找import语句。一般都在component文件的顶部,像这样:
import * as utils from "../../utils/utils";
如果你查看utils模块的源代码,你会发现真的很多。大概有1300行的代码量。也许你已经看过这样的语句。其实ES6中有多种导入模块的方法,不过这样的导入语句最值得注意。因为它意味着导入utils模块中的所有函数,并放到utils的命名空间下面。所有,一个最大的疑问是:在模块中到底有多少函数?
不过,别担心。也许所有的函数都在当前文件中使用了,对吧?我们真的需要所有的函数吗?我们来检查一下,通过查找utils.,看看有几处使用。结果呢:

好吧,总共只找到了3处。
我们再看看具体是哪个函数?如果我们一个一个地查看,会发现其实只用了一个函数,就是utils.simpleSort。
if (this.state.sortBy === "model") {
// Simple sort gets used here...
json = utils.simpleSort(json, "model", this.state.sortOrder);
} else if (this.state.sortBy === "type") {
// ..and here...
json = utils.simpleSort(json, "type", this.state.sortOrder);
} else {
// ..and here.
json = utils.simpleSort(json, "manufacturer", this.state.sortOrder);
}
当然,我们要承认这个例子为了演示目的,可能有故意之嫌。不过,它表述了一个事实,那就是在很多真实的应用中,存在着像这样需要优化的地方。那么如何做呢?也就是说,我们引入了一个1300行的文件,结果只使用了其中一个函数。
禁止Babel将ES6编译到CommonJS
Babel在很多应用中已经必不可少。不幸的是,它会让tree shaking变得困难。如果你使用babel-preset-env,它会将你的ES6编译到可兼容性更好的CommonJS。
问题在于对于CommonJS,tree shaking非常困难,而且webpack不知道哪些需要消除掉。不过呢,好在有一个很简单的解法:配置babel-preset-env,让其保持ES6不动,不要翻译。具体的配置放在你配置Babel的地方(.babelrc或则package.json):
{
"presets": [
["env", {
"modules": false
}]
]
}
简单地配置"modules":false即可,webpack会分析所有文件中模块的依赖关系,然后剔除那些没有使用的代码。并且,这个处理不会有兼容问题,因为webpack最终会将代码转换到兼容的版本。
另一个需要考虑的是:应用中使用模块是否有副作用。我举一个例子来说什么叫副作用(这个例子表述了在一个函数中去修改函数外部的变量):
let fruits = ["apple", "orange", "pear"];
console.log(fruits); // (3) ["apple", "orange", "pear"]
const addFruit = function(fruit) {
fruits.push(fruit);
};
addFruit("kiwi");
console.log(fruits); // (4) ["apple", "orange", "pear", "kiwi"]
只有当函数给定输入后,产生相应的输出,而不修改任何外部的东西,我们才可以安全的做shaking操作。在这个例子中,addFruit修改了fruit数组,而fruit数组是全局的。
所以,在webpack中,我们可以通过配置"sideEffects":false表示模块是安全的,没有副作用的。
{
"name": "webpack-tree-shaking-example",
"version": "1.0.0",
"sideEffects": false
}
或则,你可以告诉webpack哪些文件有副作用:
{
"name": "webpack-tree-shaking-example",
"version": "1.0.0",
"sideEffects": [
"./src/utils/utils.js"
]
}
在上面的配置中,webpack会假定其它文件都是无副作用的。如果你不想添加到package.json文件中,你可以配置module.rules。
按需导入
我们可以只导入我们需要使用的函数,在示例中,我么只需要simpleSort:
import { simpleSort } from "../../utils/utils";
使用上面的语法,我们就只会将simpleSort函数导出,我们只需要将utils.simpleSort改为simpleSort:
if (this.state.sortBy === "model") {
json = simpleSort(json, "model", this.state.sortOrder);
} else if (this.state.sortBy === "type") {
json = simpleSort(json, "type", this.state.sortOrder);
} else {
json = simpleSort(json, "manufacturer", this.state.sortOrder);
}
接下来我们看看执行效果,首先回顾之前的打包效果:

接下来看使用了tree shaking后的效果:

两个模块都变小了,特别是main文件。通过将utils中无用代码删掉,整个体积削减了60%。这不仅节省了下载时间,而且节省了处理时间。
其他情况
在大多数情况下,上面的方法就足够了。但是,总有例外的情况会让你抓耳挠腮。比如,Lodash就不行。因为Lodash当时的架构就不支持,所以需要一些额外的工作:a) 安装lodash-es来替代lodash;b) 使用稍微不同的语法(叫做cherry-picking):
// This still pulls in all of lodash even if everything is configured right.
import { sortBy } from "lodash"; // This will only pull in the sortBy routine.
import sortBy from "lodash-es/sortBy";
如果有些模块使用CommonJS格式(module.exports),那么webpack无法使用tree shaking。一些插件(webpack-common-shake)为CommonJS提供tree shaking。但是,因为有些CommonJS的模式是无法做tree shaking的。如果你想很保险地剔除掉没有使用的依赖,ES6才是你最佳的选择。如果你倾向于使用一致的import语法,你可以使用标准的lodash包,然后安装babel-plugin-lodash。
关于Fundebug
Fundebug专注于JavaScript、微信小程序、微信小游戏、支付宝小程序、React Native、Node.js和Java实时BUG监控。
自从2016年双十一正式上线,Fundebug累计处理了6亿+错误事件,得到了Google、360、金山软件等众多知名用户的认可。欢迎免费试用!
版权声明:
转载时请注明作者Fundebug以及本文地址:
https://blog.fundebug.com/2018/08/15/reduce-js-payload-with-tree-shaking
配置Tree Shaking来减少JavaScript的打包体积的更多相关文章
- webpack指南(一)HRM+Tree Shaking
参考:https://www.cnblogs.com/PasserByOne/p/12084323.html https://blog.csdn.net/qq593249106/article/det ...
- Webpack 4教程 - 第七部分 减少打包体积与Tree Shaking
转载请注明出处:葡萄城官网,葡萄城为开发者提供专业的开发工具.解决方案和服务,赋能开发者.原文出处:https://wanago.io/2018/08/13/webpack-4-course-part ...
- 深入浅出的webpack构建工具---tree shaking打包性能优化(十二)
阅读目录 1. 什么是tree-shaking? 2. 在webpack中如何使用 tree-shaking 呢? 3. 使用webpack-deep-scope-plugin 优化 回到顶部 1. ...
- Webpack 4 Tree Shaking 终极优化指南
几个月前,我的任务是将我们组的 Vue.js 项目构建配置升级到 Webpack 4.我们的主要目标之一是利用 tree-shaking 的优势,即 Webpack 去掉了实际上并没有使用的代码来减少 ...
- webpack使用tree shaking的问题。及关于UglifyJs不支持ES6的解决方案。
webpack: plugins:[ new webpack.optimize.UglifyJsPlugin({ compress:{warning:true} }) ] 是的,一些dead code ...
- Angular2+typescript+webpack2(支持aot, tree shaking, lazy loading)
概述 Angular2官方推荐的应该是使用systemjs加载, 但是当我使用到它的tree shaking的时候,发现如果使用systemjs+rollup,只能打包成一个文件,然后lazy loa ...
- webpack4 系列教程(九): CSS Tree Shaking
教程所示图片使用的是 github 仓库图片,网速过慢的朋友请移步原文地址 有空就来看看个人技术小站, 我一直都在 0. 课程介绍和资料 本次课程的代码目录(如下图所示): >>> ...
- Webpack 的 Tree Shaking
为什么要使用 Tree Shaking? 当从某文件模块中导出(某一个或几个变量.函数.对象等),然而这个文件模块还有许多其它(我们这次并不需要)的导出,webpack会不管三七二十一简单粗暴的将整个 ...
- 原来rollup这么简单之 tree shaking篇
大家好,我是小雨小雨,致力于分享有趣的.实用的技术文章. 内容分为翻译和原创,如果有问题,欢迎随时评论或私信,希望和大家一起进步. 分享不易,希望能够得到大家的支持和关注. 计划 rollup系列打算 ...
随机推荐
- ReactNative学习笔记(四)热更新和增量更新
概括 关于RN的热更新,网上有很多现成方案,但是一般都依赖第三方服务,我所希望的是能够自己管控所有一切,所以只能自己折腾. 热更新的思路 热更新一般都是更新JS和图片,也就是在不重新安装apk的情况下 ...
- 设置Acad2008默认启动 win10设置默认cad2008启动 调试设置.
Windows Registry Editor Version 5.00 [HKEY_CLASSES_ROOT\AutoCAD.Drawing.17\shell\open\command]@=&quo ...
- cad.net cad启动慢? cad2008启动慢? cad启动延迟? cad卡住? cad98%卡? 默认打印机!!
默认打印机是不是联网打印机,如果cad找不到这个打印机将会很慢才打开cad的界面(它真的不是卡死了,而是找不到...) 奇妙的是桌子至今都没有利用新建线程的方式来控制这个打印机等待,而是直接在主程序上 ...
- 漏洞应急响应之批量poc验证
1.文章难易度 [★★★] 2.文章知识点: python,poc验证; 3.文章作者: 野驴 4.本文参与 i春秋学院原创文章奖励计划,未经许可禁止转载! 0x01前言 当互联网爆出高危漏洞,或者团 ...
- 日志查看技巧之筛选&去重[排查篇]
引语:相信大家都会偶尔遇到要排查问题发生的原因的情况,那这种时候,我们最有力后盾就是日志文件了,所以谨记日志记录真的很重要.但是日志文件往往是很大的文件,而且里面有太多的东西可能不是我们需要的,如无数 ...
- Java匹马行天下——开篇
个人感言: 匹马行天下是我高中时候看过一部叫<九鼎记>的小说中的其中一个大章节标题,在整个这一章中,讲的是是主人公滕青山历经艰险,又心如磐石,一心修行,最后巅峰归来的故事.现在回想,依旧心 ...
- javaScript 二分查找
什么是二分查找的,举个栗子: var arr = [1, 3, 5, 7, 9, 11, 14, 15, 17, 19, 20]; 上面有序数组, 随便给你一位 9 ,输出该数在数组中的索引. 当 ...
- 常用的评价指标:accuracy、precision、recall、F1-score、ROC-AUC、PR-AUC
- 深度解析 Vue 响应式原理
深度解析 Vue 响应式原理 该文章内容节选自团队的开源项目 InterviewMap.项目目前内容包含了 JS.网络.浏览器相关.性能优化.安全.框架.Git.数据结构.算法等内容,无论是基础还是进 ...
- 判断URL中的中文参数是GB2312还是Utf-8编码
如两个URL字符串: &q=%E8%A3%99%E5%AD%90&style=grid&seller_type=taobao &q=%CE%D0%C2%D6%D4%F6 ...