我把公司大项目热更新从14s优化到2s【webpack热更新优化大全】

5,045 阅读10分钟

前言

   由于公司项目的启动时间和热更新时间都非常缓慢,所以我决定优化一下我们的项目。

   第一个突破口便是webpack,项目webpack当前使用的是2.2.1版本,可以说老的有点骨质疏松了,计划升级至4.46.0.在经历了一系列挖坑填坑的操作后,webpack4终于在项目中跑起来了。

   然后再经过一系列优化之后,冷构建也从原来的两分多钟优化到九十多秒,二次构建大概来到60秒。

  但,问题来了,发现开发时的热更新速度相比原来还是没啥进步,甚至觉得还慢了一点点。大概是14s!!!不知道有多同学的公司项目热更新都跟我的一样,慢的跟个鸡你太美一样!于是躁动的心蠢蠢欲动...

  在一番操作之后...热更新终于来到了2秒,很是开心...

  为此总结下关于热更新调优的心得,希望能帮助到有同样困扰的同学。

进入正题:

  在开发阶段,我们更应该关心热更新的速度,为此我们允许在启动时间做出一定的牺牲,对于产物的体积大小有更高的容忍度。应该专注于在修改代码后能够快速响应到浏览器,并且利于调试。基于这个优化角度,我总结了以下的一些优化方式:

热更新优化方式

星星代表提速的影响力【满分5星-仅供参考】

更高webpack版本 ✨✨✨✨✨

  尽量使用较高的版本,因为越高版本性能越高,且提供优化的手段越多。本文也是针对webpack4展开。不同webpack版本的热更新优化策略会略有不同,但差距不会很大,且优化思路也类似。

老少皆宜的source-map ✨✨✨✨

  有些同学可能还不清楚source-map是啥,这里简单说下:source-map可以理解成产物代码映射回源码的一种技术。在webpack配置文件中对应devtool字段进行配置。

  但是映射还原程度是可以调节的,越高的映射程度在调试时能够看到越接近源码的效果,但代价就是构建阶段速度的下降,以及产物体积变大。所以在正式环境,建议直接设为false或者设置为cheap-module-eval-source-map但在开发阶段就有意思了,又要构建的快,又要便于调试。又要马儿跑,又要马儿不吃草,又当又立的,实在纠结,在最后的权衡下,还是配置建议配置成:cheap-module-eval-source-map。

产物放在内存 ✨✨

  类似于webpack-dev-server这样的插件默认是将产物放置在内存的,更快的读改。但如果你觉得热更新慢了,也不要忘了这里是不是被动了手脚。

项目比较大的话,大概也能提高个一两秒。

【主要提升webpack的提交产物阶段,空间换时间的优化思路】

更少的loader ✨✨✨

  用一些版本高一点的loader可能会提升性能,但更重要的是去掉一些不必要的loader。且loader处理的范围应该要尽量小。 例如


rules: [
      {
        test: /.js$/,
        include: path.resolve(__dirname, 'src'),
        // 或者 exclude: /node_modules/
        loader: 'babel-loader'
      }
    ]

一些多进程构建插件如:HappyPack、ThreadLoader多进程工具也是要慎用,他可能对于启动速度有帮助,但对热更新来说反而是增加负担。

更少的plugin ✨✨✨✨

  项目热更新之所以跑到14s,插件的滥用占了很大功劳【因项目而异】。在开发环境中,像分包策略插件、压缩插件、css抽离插件等优化产物类的插件都是不需要的,因为咋们前面就说了,咋们怕的不是包体积过大之类的,咋们怕的是热更新慢!

特别提醒:慎用一些热度过低的插件,可以看看下载量之类的。谨防插件的性能出现问题

分包策略 ✨✨✨

  如果你的项目很大的话,优化分包的算法也有可能会非常耗时!!!

  但凡是不是绝对的,合适的分包还是有可能会提高热更新速度的。以降低入口chunk的大小作为分包方向,能适当提高时间,但前面也说到,优化分包的算法本身是很耗时的...

所以你认为你的分包小王子,可以尝试用自己的分包配置试试看,跟关掉优化配置对比下时间。

  不想折腾的同学直接关掉也是稳妥的。

  webpack>=4配置runtimeChunk:true小幅度提高。大概500毫秒。

【分包策略主要在webpack构建的seal阶段耗时,需要对庞大的module数量按照算法重新分配】

module.exports = {

  // ...
  optimization: {
    removeAvailableModules: false,
    removeEmptyChunks: false,
    splitChunks: false,
    runtimeChunk: true,
  }
};

减少打印信息 ✨

  如果在热更新的过程你看到控制台正在疯狂输出,要注意了。尝试关掉打印看看... 另外webpack内部有个stats构建信息对象,一般配置成errors-only,如果要的信息太多也会有一点点影响构建速度。

别用hash/chunkhash ✨/2

  这东西开发环境用了暂时看意义不大。

终极大招 ✨✨✨✨✨

  多配置一份路由文件,里面只放你这次开发需要用的入口。然后建多一个入口文件通过process.env.NODE_ENV === 'development'类似这样的判断控制使用哪个路由文件。这样代码量急速降低,能够有效提高热更新速度,是副良药。唯一问题是开发不同模块是需要人工去修改路由。但相信随着你经验的增加,你可以整理出一些常用路由放到一起;或者说你可以整理出多个不同功能的路由文件,例如一个是专门放门店管理的路由文件,一个是品牌管理的路由文件...然后通过变量去合并使用。

  • route.js // 现有的路由配置文件
  • routeBrand.js // 按业务区分 - 品牌路由
  • routeStore.js // 按业务区分 - 门店路由
  • routeEntry.js // 路由的统一入口

routeEntry.js伪代码:

const standardRoute = require('./route.js') 
let DEV_ROUTE = {
    routeBrand: 'routeBrand.js',
    routeStore: 'routeStore.js',
}

// 配置开发环境使用哪些路由文件
let useDevRouteCofig = [DEV_ROUTE.routeBrand, DEV_ROUTE.routeBrand];
let devRoute;
useDevRouteCofig.map(routePah=> {
    devRoute.push(...require(routePah))
})

// 判断使用哪个路由
let router = process.env.NODE_ENV === 'development' ? devRoute : standardRoute;
export default router;

  考虑到每个开发需要配置不同的useDevRouteCofig,避免改动后更新上去影响别人,应该还要再优化,这里就不展开讲了。

关于网上的一些优化方案

使用插件:dynamic-import-node

  这种方式的原理是将动态导入的代码在AST的时候转成require,即动态导入变静态导入。这就有意思了,有些人说这样一搞,热更新3分钟变3秒。有人说没卵用。

  那到底有没有用呢?

  答案是:

  建议试试,说不定可以看到想要的效果。

  起不起作用就根据项目特点而定。比如我公司项目这么搞就没用【公司项目特点:有两个入口,一个是登录代码不多,另一个入口包含了所有功能,代码量超巨大,且路由都是通过动态方式导入】,在抱着侥幸的心理去尝试之后,发现热更新速度没有提升,并且浏览器打开项目loading了二三十秒才出来界面。并不意外,因为入口chunk超级巨大!所以对于这个项目,即使有效果也不会用这个配置【如果还有不懂webpack的chunk知识的话就去看看这篇文章吧:# webpack原理解析【长文万字】

html-webapck-plugin的性能问题

要注意网上有不少对这个插件提出性能的疑问

使用 HardSourceWebpackPlugin、cache-loader

  不建议使用,这些缓存module/chunk/文件的一些插件,在热更新的过程中得不偿失。但webpack内部的cache选项是不能关的喔,跟这些插件的缓存不是同一回事。

ts-loader

  如果你的项目中使用了ts,那么做一些优化配置是必要的。 如使用插件fork-ts-checker-webpack-plugin,ts-loader把happyPackMode / transpileOnly设置true

我的热更新优化过程

  在优化的过程中,我曾遇到瓶颈,在做完以上的一系列优化步骤后,热更新从14s到了9s左右,就一直找不到其他着手点。

  难过的是,在我试图使用speed-measure-webpack-plugin来分析耗时点的时候,触发热更新时报错,它似乎与webpack-dev-middleware之类的插件不兼容。在尝试换了几个不同版本之后仍然没解决这个问题,就这样我失去了一个可能能提供我最直接优化方向的插件。

  所以,如果你的项目使用speed-measure-webpack-plugin跟热更新构建没有冲突的话,你应该认真看看这个插件提供的一些信息。以此为依据分析优化方向。

  于是情急之下我写了一个简单的替代插件:它最重要的功能就是帮我打印webpack内部构建各个关键hook的耗费时间,如:make、buildModule、optimize...等等

  webpack的hooks大概几百个,有时候只能凭借经验去打印一些关键hooks的耗费时间,然后缩小范围,直到我发现了hooks.beforeHash到hooks.beforeHash这个过程,耗费了将近6秒之后。我就意识到了项目中应该出现了一些不友好的hash配置,直觉告诉我有个插件在监听webpack的关于hash的hooks。所以我先去package.json搜索hash关键字,果不其然,出现了一个webpack-md5-hashXXX的插件。实际上,测试结果也验证了我的猜想是正确的。

  去掉了这一插件后,重新启动项目,修改代码,保存;然后触发热更新,看了下构建时间:2s。这个插件去的一点都不冤枉,文件命名都不需要hash了居然这个插件还给我的hash套个md5加密。

  在找到矛头的那一刻,心情瞬间舒坦....

  2s的耗时,对于热更新的速度来说,还有进步的空间。然而对于从14s到2s的跃进,现在已经满足了。

  但本着卷死大家的态度,这2s我仍然想着有空了再把他优化到1s。想看的别忘了插眼~

  tips:目前看这2s主要耗时在make阶段以及seal阶段。

  至此,热更新的优化工作先告一段段落。

  关于一些配置对于速率的影响可能并不明显,但配置的方向是对的。当别人问起你这里为什么这么配置的时候,才显得你足够专业!

如何制定你自己的优化策略?

  可以按照【热更新优化方式】下列举的方式去进行配置,看是否达到你的目标速度。如果未满足的话,则可以选择speed-measure-webpack-plugin插件进行速度分析,如果热更新跟这个也有插件冲突的话你可以at我,试下我写的那个破烂版插件,然后分析思考问题所在。个人认为如果你的热更新久到离谱的话,那应该优先考虑插件的问题,其次是loader,然后再是删代码跑路。

  再然后是参考【关于网上的一些优化方案】;另外【终极大招】这种方式应该每个项目都能起到良好效果,就是要改下路由文件有点繁琐。

最后

如果大家想了解更多关于webpack及热更新的原理,可以看些我写的其他文章。

之后也会考虑抽时间写一篇关于webpack启动优化/包体积优化方式的文章,先插个眼...

image.png