webpack打包速度优化

事实证明,没有人逃得过 真香定律,前不久我刚在 一篇文章 里吐槽过 webpack ,但是没办法呀,人家家大业大,功能确实强大,该用还是得用。吐槽的点主要就是 webpack 的文档太不人性化,还有打包速度不够快。随着 webpack4.x 的推出,打包速度已经有了较大的提升,但是我们要精益求精,进一步优化项目的打包速度。顺便,为了防止没事就被 webpack 的文档恶心一下,这些知识点还是记录在自己的博客上吧。

一、缩小文件搜索范围

webpack 在打包时,会从配置的 entry 出发,解析入口文件的导入语句(原理是通过 AST 转化,这部分就不深入讲了),再递归的对导入文件进行解析。对于导入语句,webpack 会执行以下操作:

  • 根据导入语句检索对应的文件

    例如 require('vue') 语句导入的文件是 ./node_modules/vue/lib/vue.js , require('./utils') 对应的是 ./utils.js

  • 根据导入文件的后缀,匹配对应的 loader(我们配置的) 去处理文件

这两个操作看起来似乎花费不了多少时间,但是当项目变大之后,尤其是前端项目,文件数量会变的非常之多,这时候这两个操作耗费的时间就会增加很多。

那么,如何优化这个问题?

我们可以通过尽量避免这两个操作或者加快操作运行效率来优化这个问题:

1. 优化 loader 配置

前面已经提到了,webpack 会通过 loader 配置匹配并处理文件,在配置中对应的是 test, include, exclude 三个配置项,尽可能的优化这些配置,提高匹配效率。我们举一下具体的例子

项目中所有的 js 文件都在 ./src 下,那么我们配置 babel-loader 的时候,可以采用以下配置:

{
  test: /\.js$/, // 正则表达式尽可能精简,比如项目中只有js文件,没有jsx,那么不要配置 /\.(js|jsx)$/
  use: 'babel-loader',
  include: [path.resolve('./src')], // 通过 include 缩小文件范围,非 include 下的文件不会参与正则匹配
  exclude: /node_modules/ // 第三方库文件都是 ES5 语法,不需要再 babel 处理
}

2. 优化 resolve.module

resolve.module 用户配置 webpack 去哪里寻找第三方模块,默认值是 ['node_modules'] ,即当前目录下的 ./node_modules 目录下找第三方模块,找不到时再往父级目录寻找。一般而言,一个项目只会有一个 node_modules 目录,因此,我们可以直接将其配置为 node_modules 的绝对路径,减少检索过程。

resolve: {
  module: [path.resolve('node_modules')]
}

3. 优化 resolve.alias

很多朋友会误以为 resolve.alias 只是为了方便开发时导入模块,去除 ../../../ 这样的路径引入,其实 resolve.alias 的作用不止如此,它还有利于提高文件的检索效率。

resolve.alias 会被 webpack 缓存为字典,处理导入语句时先从字典查询,命中的话就可以很快检索到对应文件,加快寻址过程。

4. 优化 resolve.extensions

在导入语句没带文件后缀时,例如 require('src/utils') ,webpack 会根据 resolve.extensions 尝试询问对应的文件是否存在,因此,在配置 resolve.extensions 应尽可能注意以下几点:

  • resolve.extensions 列表要尽可能的小,不要把项目中不可能存在的情况写到后缀尝试列表中。
  • 频率出现最高的文件后缀要优先放在最前面,以做到尽快的退出检索过程。
  • 在源码中写导入语句时,要尽可能的带上后缀,从而可以避免寻找过程。

假如项目中只有 js 和 vue 文件会使用无后缀的导入语句,js 文件数量多于 vue 文件,那么对应的配置:

resolve: {
  extensions: ['.js', '.vue']
}

5. 优化 module.noParse

module.noParse 可以让 webpack 忽略一些模块的递归解析处理,这样做可以提高构建性能。原因是一些第三方库没有采用模块化标准而且体积庞大,解析这些文件既耗时又没有意义。比较出名的有:jQuerylodash 等。

module: {
  noParse: /jquery|lodash/
}

以上是通过缩小文件检索范围来优化构建速度的几个点,可以根据自己项目的需要按照上述方法改造,可以有效提升构建速度。

二、使用 HardSourceWebpackPlugin 缓存构建过程

HardSourceWebpackPlugin 是 webpack 的一个插件,可以用来缓存模块构建的中间步骤。当然,通过缓存来优化只有在第二次构建的时候才能看到构建速度的提升(大概80%以上的提升)。

plugins: [
  new HardSourceWebpackPlugin({
    // Either an absolute path or relative to webpack's options.context.
    cacheDirectory: 'node_modules/.cache/hard-source/[confighash]',
    // Either a string of object hash function given a webpack config.
    configHash: function(webpackConfig) {
      // node-object-hash on npm can be used to build this.
      return require('node-object-hash')({sort: false}).hash(webpackConfig);
    },
    // Either false, a string, an object, or a project hashing function.
    environmentHash: {
      root: process.cwd(),
      directories: [],
      files: ['package-lock.json', 'yarn.lock'],
    },
    // An object.
    info: {
      // 'none' or 'test'.
      mode: 'none',
      // 'debug', 'log', 'info', 'warn', or 'error'.
      level: 'debug',
    },
    // Clean up large, old caches automatically.
    cachePrune: {
      // Caches younger than `maxAge` are not considered for deletion. They must
      // be at least this (default: 2 days) old in milliseconds.
      maxAge: 2 * 24 * 60 * 60 * 1000,
      // All caches together must be larger than `sizeThreshold` before any
      // caches will be deleted. Together they must be at least this
      // (default: 50 MB) big in bytes.
      sizeThreshold: 50 * 1024 * 1024
    },
  })
]

上面是大概的使用示例,具体使用可以查看 文档,这里不做过多介绍。

三、使用 dll

一般而言项目中都会有一些公共模块,这些公共模块一般情况下不会变更,我们可以将其抽出来打包成单独的库,这样就不需要每次都重复打包。

1. 配置 dll 打包配置

// webpack.vendor.config.js
module.exports = {
  entry: {
    vendor: ['lodash', 'vue', 'moment'] // 公共模块
  },
  output: {
    path: path.resolve(__dirname, '../static/js'),
    filename: '[name].dll.js',
    library: '[name]_library'
  }
     plugins: [
    new webpack.DllPlugin({
      context: __dirname,
      name: '[name]_library', // 和 output.library 保持一致,DllReferencePlugin 是根据这个来索引的
      path: path.join(__dirname, '.', '[name]-manifest.json'),
    })
  ] 
}

我们可以为其添加一个打包命令,比如 npm run build:dll。打包后会生成一个 vendor-manifest.jsonvendor.dll.js 存放在对应的目录下。vendor-manifest.json 中记录着不同库的引用地址和id,这个文件的生成是为了提供给 DllReferencePlugin 插件用的。

2. 添加 DllReferencePlugin 插件

// webpack.app.config.js
plugins: [
  new webpack.DllReferencePlugin({
    context: path.resolve(__dirname, '..'),
    sourceType: 'commonjs2',
    manifest: require('./vendor-manifest.json')
  })
]

3. 引入 vendor.dll.js

<script src="<%= webpackConfig.output.publicPath %>static/js/vendor.dll.js"></script>

四、多进程打包

我们都知道,webpack 是运行在 Node 环境中的,也就是单线程模式,打包的时候只能一个一个的进行,在编译的时候,这是影响速度的很大一个原因。我们知道,node 其实是可以 fork 子进程的,那么这里自然就有优化的余地,这里可以用到两个插件:

  • happypack 多进程并发编译:文档
  • webpack-parallel-uglify-plugin 多进程并发压缩文件:文档

具体配置自行翻阅文档,不赘述。

0%