Webpack 4进阶--从前的日色变得慢 ,一下午只够打一次包

从前的日色变得慢,车,马,邮件都慢,一生只够爱一个人 -- 《从前慢》

近期在团队项目里把Webpack升级到4.4.1,过程中发现现存的升级文档十分有限,踩了不少坑,好在升级之后提升还算显著,production场景下第三方依赖打包速度提升76%,development场景下本地服务首次启动提升效果约46%,再次启动提升效果上升至63%。这里将这次升级过程中的点滴分享出来,希望对大家有所帮助。

理论部分

Webpack 4 发布之后,议论最多的两大特性,其一是零配置,其二是速度快(号称提速上限98%)。听起来十分美妙,在实地测试之前,首先从理论上分析一下可能性。

零配置

一言以蔽之,约定优于配置。通过mode属性将开发/生产(development/production)环境中常用的功能设置好默认值,用户即来即用。

打包速度快

Optimization

Webpack 4取消了四个常用的用于性能优化的plugin(UglifyjsWebpackPlugin,CommonsChunkPlugin,ModuleConcatenationPlugin,NoEmitOnErrorsPlugin),转而提供了一个名为optimization的配置项,用于接手以上四位的工作。

注:UglifyjsWebpackPlugin并不执行tree shaking操作,这里为了介绍sideEffects,故而将关系紧密的两者放在一起介绍了

  1. Tree Shaking & Minimize

废弃插件:UglifyjsWebpackPlugin

新增属性:sideEffects,minimize等

  • Tree Shaking

Tree shaking一直是一个美丽而遥不可及的话题。影响tree shaking的根本原因在于side effects(副作用),其中最广为人知的一条side effect就是动态引入依赖的问题。得益于ES6的模块化实现思路,所有的依赖必须位于文件顶部,静态引入(然而import()的出现打破了这个规则),Webpack可以在绘制依赖图的时候进行静态分析,从而将真正被引用的exports添加到bundle文件中,减少打包体积。然而很多热度较高的第三方库为了考虑兼容性往往采用UMD实现,而其所支持的动态引入依赖的功能则导致真实的依赖图可能要到运行时才能确定,使得静态分析难以发挥真正威力,tree shaking采用了保守策略,导致我们发现没有被用到的方法依然出现在了bundle文件中。

一个好消息是许多第三方库相继推出了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

Minimize属性就没啥可多说的了,混淆压缩文件。

  1. Scope hoisting

废弃插件: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的效果同样也依赖于静态分析,无奈命不由我。

  1. Code splitting

废弃插件:CommonsChunkPlugin

新增属性:splitChunks,runtimeChunk, occurrenceOrder等

  • splitChunks

splitChunks在Webpack 4里被用于取代我们熟悉CommonsChunkPlugin。读到这里不知道你有没有发现其中的端倪,这是否意味着DllPlugin和CommonsChunkPlugin(splitChunks)可以共存了呢?

在Webpack 4之前,两者并不能一起使用,原因有二

  • 一个相对没那么重要的原因是DllPlugin服务的目标场景是develop环境,因为第三方依赖(输出文件暂称为vendors)的变更频率较低,故而在每次启动本地服务或者rebuild的时候将第三方依赖重新打包一次实际上是一种浪费。通过DllPlugin,将第三方依赖的打包过程从业务代码的打包过程中独立出来,可以大大缩短develop环境下的启动时间。同时通过设置hash值,也可以充分的利用浏览器对这部分文件的缓存,提升加载效率。而在对加载效率更为苛刻的production环境,DllPlugin打包出的文件则稍显笨重,很多重复的内容被多次打包进了bundle文件。在这种场景下,CommonsChunkPlugin被视为更好的选择,因为我们不需要为打包时间操心过多,加载效率是我们唯一需要关注的内容。所以在webpack的开发者看来,这两者如同“I have an apple,I have a pen,Ah~~ Apple pen”一样,实际上并不存在什么交集。
  • 因此也引出了二者不兼容更为重要的第二个原因,没人实现

这块功能实际上通过CommonsChunkPlugin设置两个entry point也可以实现,一个作为业务代码的入口,一个作为vendors的入口。不过存在两个问题,第一个问题是,尽管vendors被单独设置了entry point,但是在每次启动本地服务的时候,尽管打包的结果不变,hash值不变,浏览器的缓存文件也被充分利用了,它的打包过程依然会执行,所以启动时间并不会缩短,第二个问题是,许多人在使用CommonsChunkPlugin的时候并没有注意到Webpack会将runtime一起打包进vendors文件,所以每次启动的时候,尽管你并没有修改任何第三方依赖,但是vendors文件的hash值却变了,导致浏览器缓存实际上并没有被利用起来。要解决这个问题,需要配置CommonsChunkPlugin将runtime单独打包成一个文件。

然而到了Webpack 4,在CommonsChunkPlugin变成splitChunks之后,出于某些未知的原因,两者兼容性的问题被解决了。。。Happy coding。

  • runtimeChunk

runtimeChunk之所以被单独设置为一个配置项,应该就是为了主动帮助用户避免上文所述的问题吧。

  • occurrenceOrder

occurrenceOrder应用的场景是如果不手动设置chunk的名字,而采用默认值的话,Webpack将会用更短的名字去命名引用频度更高的chunk。

  • noEmitOnErrors

废弃插件:NoEmitOnErrorsPlugin

新增属性:noEmitOnErrors

noEmitOnErrors在编译出现错误时,用来跳过输出阶段。

New Plugin

Webpack 4同时实现了一套新的plugin机制,与性能相关的改进点是消除了对arguments的滥用。如同我们推崇开发时定义类型,从而可以避免JIT过程中产生过多的重载函数,以及降低重新编译的概率。

实践部分

讲了这么多,最后分享一下我的实操经历。Webpack 4为用户描绘的场景固然美好,然而带来便利的同时也给开发者留下了不少麻烦。首当其冲的就是兼容性的问题,很多我们常用的loader,plugin尚未对这次升级做好准备,找到合适的替代工具以及积极改造自研的工具将成为升级过程中一场重要战役。接下来我会针对在这次项目升级中我所遇到的兼容性问题以及最终采用的解决方案做一个总结,常规的Webpack 4配置可以在官方demo 中找到答案。

  1. CommonsChunkPlugin DllPlugin

Nothing special,主要还是一个分类问题,如何识别存在公共依赖的第三方依赖,并将其分配到不同的entry中。例如antd和react都依赖了react,则应该将两者分配到不同的entry中。以及如何均匀的分配依赖到不同的entry中,使得打包之后的每个entry大小相近。可以说十分考验一名配置工程师的功力和对源码库的了解程度。

  1. Ts-loader 因为awesome-typescript-loader(ATL)还没有合并支持Webpack 4的pr。所以ts-loader是ts爱好者们目前最好的选择。曾经ATL之所以能够战胜ts-loader,成为不少人的选择,原因有二,其一是ATL会新开一个独立的进程执行类型检查操作,因此不会影响编译时间,其二是ts的编译结果会被缓存,rebuild场景下可以提速。目前ts-loader也已经支持这两方面功能了,所以替换时并不需要担心。
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()
]
  • ForkTsCheckerWebpackPlugin用于新建进程执行类型检查,为此你需要关闭ts-loader自身的类型检查功能,即设置transpileOnly为true。
  • thread-loader允许新建一个worker进程去分担一些昂贵的loader操作;cache-loader则可以将loader的运行结果缓存在本地。然而两者同时也会带来额外的开销(进程管理,I/O操作),自行评估后使用。
  1. MiniCssExtractPlugin 通过名字不难猜出它的功能,由于ExtractTextWebpackPlugin尚不支持Webpack 4,而且未来很可能被吸收为配置项,Mini-css-extract-plugin可以作为过渡期的一个选择。除了常规的css抽取合并功能外,它还会在合并时清理重复的css副本,而这也是ExtractTextWebpackPlugin尚未实现的功能,所以理论上css的打包效果更优。
  2. InlineChunkWebpackPlugin(Webpack 4尚未支持) 虽然Webpack 4尚未支持这个插件,但还是把它加在了这里,只是因为它确实有用。上文说到通过配置runtimeChunk为true,可以将运行时打包成独立的chunk,然而这个chunk体积很小,单独占用一个http请求稍显浪费,inline显然是更好的选择。InlineChunkWebpackPlugin可以帮助我们将指定的chunk通过inline的形式写入index.html文件。在Webpack 4尚不支持的情况下,只好在http和ctrl a&ctrl c&ctrl v中选择一个更合适您口味的方法了。
  3. CleanWebpackPlugin 首先我要说明,这是一个玄学plugin,用或不用完全取决于脸黑不黑,手脏不脏。用处就是可以在打包前清理指定目录的文件,譬如说旧的bundle文件。开始我也不信,后来的结果你们也看到了。

最后秀一下数据吧

在展示最终结果之前需要声明的一点是,由于升级Webpack的同时,还解决了诸多兼容性问题,所以最终结果的表现无论优劣,都不仅仅是Webpack的功过,loader以及plugin替换带来的性能影响同样不可忽略。至于如何到达提速98%,如果所有依赖全部更新成为es版本的话。。。

  1. DllPlugin CommonsChunkPlugin对第三方依赖打包场景(production场景) Webpack 3.8.1的打包时长为57411ms,Webpack 4的打包时长为13959ms,提升效果约76%,详情如下图所示。

    webpack3.8.1
    webpack4.4.1

  2. 本地启动(development场景) Webpack 3.8.1的启动时长(仅包含业务代码打包过程)为42890ms,Webpack 4的首次启动(cache文件尚未产生)时长为23017ms,Webpack 4的再次启动(cache文件已经存在,并非watch模式下的rebuild场景)时长为15827ms,首次启动提升效果约46%,再次启动提升效果上升至63%,详情如下图所示。

    webpack3.8.1
    webpack4.4.1(首次启动,无缓存)
    webpack4.4.1(非首次启动,有缓存)

结束语

在不纠结究竟是Webpack还是替换loader&plugin的功劳,以及升级过程中遭遇的懵逼,躁郁,崩溃的情况下,这次升级还是为项目带来了正反馈。如果你也是一名追求极致开发体验的配置工程师的话,这次Webpack升级还是值得尝试的。最后希望文章中的内容能够有所帮助。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/366954.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

编码Java时的10个微妙的最佳实践

这是10个最佳实践的列表,这些最佳实践比您的平均Josh Bloch有效Java规则要微妙得多。 尽管Josh Bloch的列表很容易学习,并且涉及日常情况,但此处的列表包含了涉及API / SPI设计的较不常见的情况,但可能会产生很大的影响。 我在编…

Vue 实现微信 jssdk 扫码, 上传图片

流程 1: 配置微信公众号JS域名 2:前端发送URL后台获取JSSDK配置, 后台Service代码如下, 修改2处位置: WeixinUtil.APPID > 当前公众号APPID WeixinUtil.getAccessToken() > 当前公众号access_token public interface IWxJssdkService {Map<String, Object> getJssd…

使用Storm进行可扩展的实时状态更新

在本文中&#xff0c;我将说明如何借助Storm框架以可扩展且无锁定的方式在数据库中维护实时事件驱动流程的当前状态。 Storm是基于事件的数据处理引擎。 它的模型依赖于基本原语&#xff0c;例如事件转换&#xff0c;过滤&#xff0c;聚合……&#xff0c;我们将它们组合成拓扑…

【干货】十分钟读懂浏览器渲染流程

在之前写过的一篇《"天龙八步"细说浏览器输入URL后发生了什么》一文中&#xff0c;和大家分享了从在浏览器中输入网址URL到最终页面展示的整个过程。部分读者向我反馈对于最后的浏览器渲染布局这块不是很清晰&#xff0c;所以本文就浏览器渲染流程单独开篇讲解&#…

控制台资费管理主菜单java_java毕业设计_springboot框架的高速公路收费管理系统...

今天介绍一个java毕设题目, 题目内容为springboot框架的高速公路收费管理系统, 是一个采用b/s结构的javaweb项目, 采用java语言编写开发工具eclipse, 项目框架jspspringbootmybatis, 高速公路收费管理系统的信息存储于mysql中, 并基于mybatis进行了orm封装, 该高速公路收费管理…

在Amazon EMR上运行Hadoop MapReduce作业

不久前&#xff0c;我发布了如何使用CLI设置EMR群集的信息。 在本文中&#xff0c;我将展示如何使用适用于AWS的Java SDK来设置集群。 展示如何使用Java AWS开发工具包执行此操作的最佳方法是展示完整的示例&#xff0c;因此&#xff0c;让我们开始吧。 设置一个新的Maven项目…

[JSConf EU 2018] 大脑控制 Javascript

先解释&#xff0c;本人为前端菜鸟&#xff0c;之前也未参加过类似的活动&#xff0c;没有翻译过什么文章&#xff0c;此次是好奇心使然&#xff0c;也是想尝试下&#xff0c;学习学习&#xff0c;英文很烂&#xff0c;全靠有道&#xff0c;但是视频整个看下来&#xff0c;还是…

JavaScript中不得不说的断言?

断言主要应用于“调试”与“测试” 一、前端中的断言 仔细地查找一下JavaScript中的API&#xff0c;实际上并没有多少关于断言的方法。唯一一个就是console.assert&#xff1a; // console.assert(condition, message)const a 1console.assert(typeof a number, a should be…

Java EE状态会话Bean(EJB)示例

在本文中&#xff0c;我们将了解如何在简单的Web应用程序中使用状态会话Bean来跟踪客户端会话中的状态。 1.简介 有状态会话Bean通常保存有关特定客户端会话的信息&#xff0c;并在整个会话中保留该信息&#xff08;与无状态会话Bean相对&#xff09;。 有状态EJB实例仅与一个…

起点海外版 Hybrid App-内嵌页优化实践

本文作者&#xff1a;刘文涛 原创声明&#xff1a;本文为阅文前端团队 YFE 成员出品&#xff0c;请尊重原创&#xff0c;转载请联系公众号 (id: yuewen_YFE) 获取授权&#xff0c;并注明作者、出处和链接。 今年年初我司开启了起点品牌的海外之旅&#xff0c;名为「 Webnovel 」…

aix 卸载mysql_AIX 删除数据库及集群软件

一、 删除数据库1、用dbca自动删库在CRT上无法打开dbca图形界面&#xff0c;要安装一个Xmanage软件&#xff0c;用Xstart连接终端&#xff0c;并修改oracle用户的.profile&#xff0c;加上“export DISPLAY192.168.8.120:0.0”Xstart配置信息如下&#xff1a;2、手工删除数据库…

如何在github中的readme.md加入项目截图

1. 先在之前的本地项目文件夹里创建一个存放截图的文件夹。&#xff08;如img文件夹&#xff09; 2. 将新增的内容通过github desktop上传到github中 3. 在github中立马能看到刚刚上传的图片&#xff0c;打开图片&#xff0c;点击Download 4. 直接复制地址栏的网址 5. 最后在RE…

记表格设计规范整理与页面可视化生成工具开发

前言 公司有一个项目在维护&#xff0c;大概有300左右&#xff0c;其中表单与表格的页面占比大概百分之五六十&#xff0c;为了节省开发时间&#xff0c;避免多人协作时&#xff0c;出现多套冗余代码&#xff0c;我们尝试写了一下表单和表格的生成工具&#xff0c;从梳理到规范…

java仿qq空间音乐播放_完美实现仿QQ空间评论回复特效

评论回复是个很常见的东西&#xff0c;但是各大网站实现的方式却不尽相同。大体上有两种方式1.像优酷这种最常见&#xff0c;在输入框中要回复的人&#xff0c;这种方式下&#xff0c;用www.cppcns.com户可以修改。新浪微博则是在这个基础上&#xff0c;弹出好友菜单。这种方式…

使用签名保护基于HTTP的API

我在EMC上的一个平台上可以构建SaaS解决方案。 与越来越多的其他应用程序一样&#xff0c;该平台具有基于RESTful HTTP的API。 使用像JAX-RS这样的开发框架&#xff0c;构建这样的API相对容易。 但是&#xff0c; 正确构建它们并不容易。 建立基于HTTP的API的问题 问题不仅…

Python开发【模块】:Celery 分布式异步消息任务队列

前言&#xff1a; Celery 是一个 基于python开发的分布式异步消息任务队列&#xff0c;通过它可以轻松的实现任务的异步处理&#xff0c; 如果你的业务场景中需要用到异步任务&#xff0c;就可以考虑使用celery&#xff0c; 举几个实例场景中可用的例子: 你想对100台机器执行一…

iOS开发者的一些前端感悟

很多前端工程师会把自己比作“魔法师”&#xff0c;而对于JavaScript这门语言&#xff0c;我也想把它唤作一门“有魔力的语言”。因为这群有无限想法的人&#xff0c;真的在用它创造各种让你惊叹的事物。 Web三件套一、前言 几年前&#xff0c;笔者还是一名初涉编程的学生&…

windows下github 出现Permission denied (publickey)

github教科书传送门:http://www.liaoxuefeng.com/wiki/0013739516305929606dd18361248578c67b8067c8c017b000 再学习到"添加远程仓库"的时候遇到了 Permission denied (publickey) 这个问题&#xff0c; 总结来说以前的步骤如下所示&#xff1a; 1、git config --glo…

[UE4]嵌套Canvas

转载于:https://www.cnblogs.com/timy/p/9090642.html

写博客的这几个月,获益良多

1.前言 也将近过年了&#xff0c;看了那么多人搞了年会总结。自己活跃社区这几个月&#xff0c;改变了不少&#xff0c;收获也不少。就想写下这段时间写文章的一些总结&#xff0c;统计下‘成绩’&#xff0c;说下感想&#xff0c;就写了这篇文章。这次总结的关键词就是&#x…