如何提高 Webpack 打包速度

文/米酒

背景

前段时间在某个项目进行需求开发的时候,该项目是基于 webpack3 进行打包构建的。在开发过程当中我发现打包很慢,开发体验不佳,因而作了简单的优化并梳理了优化方案css

分析打包速度

进行优化的第一步须要知道咱们的构建到底慢在那里。经过 speed-measure-webpack-plugin 测量你的 webpack 构建期间各个阶段花费的时间:前端

// 分析打包时间
const SpeedMeasurePlugin = require("speed-measure-webpack-plugin")
const smp = new SpeedMeasurePlugin()
// ...
module.exports = smp.wrap(prodWebpackConfig)
复制代码

咱们能够看到打包速度慢主要是由于对样式文件和对 js 文件的处理 loader 耗时较久node

当文件发生修改,进行从新编译时,此时的打包各阶段时间以下webpack

从新编译时耗时其实并不久,可是在浏览器上实际上却花费了更多的时间在看到了新的修改,这是为什么呢web

分析 bundle 包

  • 打包后的 bundle 文件生成一个分析文件
"analyse": "webpack --config ./webpack.config.js --profile --json>states.json"复制代码
  • 对 json 文件进行分析

咱们能够看到在使用 webpack 3.5.6 进行打包的过程当中涉及到了 705 个模块,生成了 2 个 chunks, 耗时约 51sjson

  • 进一步分析 chunks 文件

打包出的两个 chunks 文件分别为 app 和 vendor 文件,其中 app.js 文件体积高达 6M浏览器

在绝大多数的状况下,应用刚开始工做时,并非全部的模块都是必需的。若是这些模块所有被打包到一块儿,即使应用只须要一两个模块工做,也必须先把 bundle.js 总体加载进来,并且前端应用通常都是运行在浏览器端,这也就意味着应用的响应速度会受到影响,也会浪费大量的流量和带宽。缓存

开发环境下的 bunlde 依赖表也能看出 node_modules 中大部份内容是随着 app.js 一块儿打包,这些就是引发咱们打包速度缓慢的元凶bash

  • 当咱们代码有更新时,HMR 会从新打包 app.js,也就是说那些没有被修改的 node_modules 中内容也会跟着从新打包到 app.js 中去

这意味着,每次代码修改,浏览器都会从新加载这个 6M 大的文件,因此为啥改了一点点内容,浏览器也须要好久才有反应,元凶在这app

  • Bundle optimize Helper 的优化建议
Entrypoints are code that are loaded on page load. To get best possible user experience, you should keep the total size of entrypoints to less than 200kb and load the rest dynamically by using code splitting.

咱们将 json 文件上传到 Bundle optimize Helper 获得的优化建议是去进行代码分割,入口文件的代码体积不要超过 200K

优化

代码分割

高达 6M 的入口文件显然是很是影响体验的,所以优化的第一步就是从代码分割开始。代码分割经过把项目中的资源模块按照咱们设计的规则打包到不一样的 bundle 中,从而下降应用的启动成本,提升响应速度。

  • 项目自己已经配置了多入口,将 lodash 等三方库文件单独进行打包,生成 vendor.js 文件
  • 将入口文件依赖的 node_modules 中内容打包到 common 中,将业务代码进行单独打包,这样能够有效减小 app.js 的体积
new webpack.optimize.CommonsChunkPlugin({
  name: 'common',
  minChunks: function(module) {
    return (
      module.resource &&
      /\.js$/.test(module.resource) &&
      module.resource.indexOf(
        path.join(__dirname, './node_modules')
      ) === 0
    )
  }
})
复制代码
  • 再使用一次 CommonsChunkPlugin 抽取 mainfest.js 文件,保证 common.js 的 hash 不会由于每次打包发生变化
new webpack.optimize.CommonsChunkPlugin({
  name: 'manifest',
  chunks: ['vendor', 'common', 'app']
}),
复制代码
  • 使用 HashedModuleIdsPlugin 来保持模块引用的 module_id 不变,CommonsChunkPlugin 提取入口指定的依赖独立打包,mainifest 则保存运行时的函数和模块标识

能够看到此时 app.js 中以来的一些三方库被单独抽取出来了,体积从 6M 降至 3.49 M

  • 当修改业务代码时咱们看下新的打包文件

当咱们的业务代码发生修改时,会从新进行打包,而依赖的三方库并不会从新打包,此时从新打包的业务代码 app.js 体积也为 3.49M

咱们能够看到没有变动的依赖包会走 304 协商缓存,而有变动的 app.js 的会从新请求而且由于体积比以前小,加载性能获得了优化

速度优化

在前面的速度分析中咱们已经知道了打包速度主要耗费在 loader 的处理上

很显然在开发过程当中进行 webpack 缓存是极其有必要的,咱们在处理样式文件和 js 文件的 loader 以前添加 cache-loader 将结果缓存到磁盘中,能够显著提高二次构建速度

module.exports = {
  module: {
    rules: [
      {
        test: /\.js$/,
        use: ['cache-loader', ...loaders],
        include: path.resolve('src'),
      },
      {
        test: /\.scss$/,
        use: ['cache-loader', ...loaders],
        include: path.resolve('src'),
      },
    ],
  },
};
复制代码

加入缓存后咱们能够看到打包速度有了显著的提高

进行优化后咱们能够看到:优化后入口包体积缩小 42%,打包速度从 51s 提高至 11s

Do More

wepack 的打包优化没有固定的模式,须要咱们针对项目去进行分块、拆包、压缩等,常见的优化思路主要分为四部分

  • 优化搜索时间,即开始打包时获取全部的依赖模块的时间
  • 优化解析时间,即根据配置的 loader 解析相应文件所花费的时间
  • 优化压缩时间,即 wepack 对代码进行优化压缩所花费的时间
  • 优化二次打包时间,即从新打包时所花费的时间

在当前的生产构建时会使用 UglifyJsPlugin 来进行代码压缩,但这个插件是单线程的,压缩时会将代码先解析为 AST 抽象语法树,而后根据规则去分析和处理 AST, 最后再将处理后的 AST 还原为 JS 代码,这种涉及大量运算的操做都是很是耗时的。在 Webpack4 中内置了 TerserPlugin 来处理 JS 代码的压缩,咱们能够开启多进程压缩模式,能够进一步优化咱们的打包速度

咱们对 chunks 进行代码分割,但目前 app.js 在未压缩的状况下体积为 3.49M,依然比较大,Webpack4 中 splitChunks 中提供了更为丰富的配置规则,咱们能够将代码中公共的部分抽取出来,以及异步加载的模块进行抽取,这样也能够进一步优化代码体积

考虑到项目的稳定性,咱们将延后进行 webpack 的升级改造。

以上是本次基于 webpack 优化项目开发体验的小结。了解 webpack 的打包原理,使用 webpack 新特性,必定能够给咱们带来更佳的开发体验。

相关文章
相关标签/搜索