从前的日色变得慢,车,马,邮件都慢,一辈子只够爱一我的 -- 《从前慢》css
近期在团队项目里把Webpack升级到4.4.1,过程当中发现现存的升级文档十分有限,踩了很多坑,好在升级以后提高还算显著,production场景下第三方依赖打包速度提高76%,development场景下本地服务首次启动提高效果约46%,再次启动提高效果上升至63%。这里将此次升级过程当中的点滴分享出来,但愿对你们有所帮助。html
Webpack 4 发布以后,议论最多的两大特性,其一是零配置,其二是速度快(号称提速上限98%)。听起来十分美妙,在实地测试以前,首先从理论上分析一下可能性。react
一言以蔽之,约定优于配置。经过mode属性将开发/生产(development/production)环境中经常使用的功能设置好默认值,用户即来即用。webpack
Webpack 4取消了四个经常使用的用于性能优化的plugin(UglifyjsWebpackPlugin,CommonsChunkPlugin,ModuleConcatenationPlugin,NoEmitOnErrorsPlugin),转而提供了一个名为optimization的配置项,用于接手以上四位的工做。 git
废弃插件:UglifyjsWebpackPlugingithub
新增属性:sideEffects,minimize等web
Tree shaking一直是一个美丽而高不可攀的话题。影响tree shaking的根本缘由在于side effects(反作用),其中最广为人知的一条side effect就是动态引入依赖的问题。得益于ES6的模块化实现思路,全部的依赖必须位于文件顶部,静态引入(然而import()的出现打破了这个规则),Webpack能够在绘制依赖图的时候进行静态分析,从而将真正被引用的exports添加到bundle文件中,减小打包体积。然而不少热度较高的第三方库为了考虑兼容性每每采用UMD实现,而其所支持的动态引入依赖的功能则致使真实的依赖图可能要到运行时才能肯定,使得静态分析难以发挥真正威力,tree shaking采用了保守策略,致使咱们发现没有被用到的方法依然出如今了bundle文件中。typescript
一个好消息是许多第三方库相继推出了es版,配合tree-shaking食用,口感更佳,这也是官方号称提速98%的重要前提之一(冷漠脸)。坏消息是ES6其实也提供import()方法支持动态引入依赖,因此如下写法其实也是彻底行的通的。。。还记得那些年咱们追过的沈佳宜说过的话么,“人生原本就有不少事情是徒劳无功的啊”。浏览器
if(Math.random() > 0.5) {
import('./a.js').then(() => {
...
})
} else {
import('./b.js').then(() => {
...
})
}
复制代码
除此之外,为了防止用户不当心修改输出元素的属性,有些库会将最终的输出元素用Object.freeze方法包裹起来,这也属于side effects之一,一样也会对tree shaking产生影响。缓存
回到Webpack 4,官方提供了sideEffects属性,经过将其设置为false,能够主动标识该类库中的文件只执行简单输出,并无执行其余操做,能够放心shaking。除了能够减少bundle文件的体积,同时也可以提高打包速度。为了检查side effects,Webpack须要在打包的时候将全部的文件执行一遍。而在设置sideEffects以后,则能够跳过执行那些未被引用的文件,毕竟已经明确标识了“我是平民”。所以对于一些咱们本身开发的库,设置sideEffects为false大有裨益。
Minimize属性就没啥可多说的了,混淆压缩文件。
废弃插件:ModuleConcatenationPlugin
新增属性:concatenateModules
//开启前
[
/* 0 */
function(module, exports, require) {
var module_a = require(1)
console.log(module_a['default'])
}
/* 1 */
function(module, exports, require) {
exports['default'] = 'module A'
}
]
//开启后
[
function(module, exports, require) {
var module_a_defaultExport = 'module A'
console.log(module_a_defaultExport)
}
]
复制代码
concatenateModules开启以后,能够看出bundle文件中的函数声明变少了,于是能够带来的好处,其一,文件的体积比以前更小了,其二,运行代码时建立的函数做用域变少了,开销也随之变少了。不过scope hoisting的效果一样也依赖于静态分析,无奈命不禁我。
废弃插件:CommonsChunkPlugin
新增属性:splitChunks,runtimeChunk, occurrenceOrder等
splitChunks在Webpack 4里被用于取代咱们熟悉CommonsChunkPlugin。读到这里不知道你有没有发现其中的端倪,这是否意味着DllPlugin和CommonsChunkPlugin(splitChunks)能够共存了呢?
在Webpack 4以前,二者并不能一块儿使用,缘由有二
这块功能实际上经过CommonsChunkPlugin设置两个entry point也能够实现,一个做为业务代码的入口,一个做为vendors的入口。不过存在两个问题,第一个问题是,尽管vendors被单独设置了entry point,可是在每次启动本地服务的时候,尽管打包的结果不变,hash值不变,浏览器的缓存文件也被充分利用了,它的打包过程依然会执行,因此启动时间并不会缩短,第二个问题是,许多人在使用CommonsChunkPlugin的时候并无注意到Webpack会将runtime一块儿打包进vendors文件,因此每次启动的时候,尽管你并无修改任何第三方依赖,可是vendors文件的hash值却变了,致使浏览器缓存实际上并无被利用起来。要解决这个问题,须要配置CommonsChunkPlugin将runtime单独打包成一个文件。
然而到了Webpack 4,在CommonsChunkPlugin变成splitChunks以后,出于某些未知的缘由,二者兼容性的问题被解决了。。。Happy coding。
runtimeChunk之因此被单独设置为一个配置项,应该就是为了主动帮助用户避免上文所述的问题吧。
occurrenceOrder应用的场景是若是不手动设置chunk的名字,而采用默认值的话,Webpack将会用更短的名字去命名引用频度更高的chunk。
废弃插件:NoEmitOnErrorsPlugin
新增属性:noEmitOnErrors
noEmitOnErrors在编译出现错误时,用来跳过输出阶段。
Webpack 4同时实现了一套新的plugin机制,与性能相关的改进点是消除了对arguments的滥用。如同咱们推崇开发时定义类型,从而能够避免JIT过程当中产生过多的重载函数,以及下降从新编译的几率。
讲了这么多,最后分享一下个人实操经历。Webpack 4为用户描绘的场景当然美好,然而带来便利的同时也给开发者留下了很多麻烦。首当其冲的就是兼容性的问题,不少咱们经常使用的loader,plugin还没有对此次升级作好准备,找到合适的替代工具以及积极改造自研的工具将成为升级过程当中一场重要战役。接下来我会针对在此次项目升级中我所遇到的兼容性问题以及最终采用的解决方案作一个总结,常规的Webpack 4配置能够在官方demo 中找到答案。
Nothing special,主要仍是一个分类问题,如何识别存在公共依赖的第三方依赖,并将其分配到不一样的entry中。例如antd和react都依赖了react,则应该将二者分配到不一样的entry中。以及如何均匀的分配依赖到不一样的entry中,使得打包以后的每一个entry大小相近。能够说十分考验一名配置工程师的功力和对源码库的了解程度。
module: {
rule: {
test: /\.tsx?$/,
use: [
'cache-loader',
{
loader: 'thread-loader',
options: {
workers: require('os').cpus().length - 1,
}
},
{
loader: 'ts-loader',
options: {
happyPackMode: true,
transpileOnly: true
}
}
]
}
}
plugins: [
new ForkTsCheckerWebpackPlugin()
]
复制代码
最后秀一下数据吧
在展现最终结果以前须要声明的一点是,因为升级Webpack的同时,还解决了诸多兼容性问题,因此最终结果的表现不管优劣,都不只仅是Webpack的功过,loader以及plugin替换带来的性能影响一样不可忽略。至于如何到达提速98%,若是全部依赖所有更新成为es版本的话。。。
DllPlugin + CommonsChunkPlugin对第三方依赖打包场景(production场景) Webpack 3.8.1的打包时长为57411ms,Webpack 4的打包时长为13959ms,提高效果约76%,详情以下图所示。
本地启动(development场景) Webpack 3.8.1的启动时长(仅包含业务代码打包过程)为42890ms,Webpack 4的首次启动(cache文件还没有产生)时长为23017ms,Webpack 4的再次启动(cache文件已经存在,并不是watch模式下的rebuild场景)时长为15827ms,首次启动提高效果约46%,再次启动提高效果上升至63%,详情以下图所示。
在不纠结到底是Webpack仍是替换loader&plugin的功劳,以及升级过程当中遭遇的懵逼,躁郁,崩溃的状况下,此次升级仍是为项目带来了正反馈。若是你也是一名追求极致开发体验的配置工程师的话,此次Webpack升级仍是值得尝试的。最后但愿文章中的内容可以有所帮助。