Webpack4.x搭配vue-cli3.x配置优化编译速度

4,266 阅读6分钟

Webpack4.x搭配vue-cli3.x配置优化编译速度

cache 缓存

使用缓存永远都是优化的一个很常见的思想,下面列举一下在4.x版本中用到的方法,其他版本的话,额,理论上也是可以吧。。。

tip: 5.x版本可以直接使用自带的cache配置传送门

HardSourceWebpackPlugin 插件

HardSourceWebpackPlugin是webpack的插件,可为模块提供中间缓存步骤。为了查看结果,您需要使用此插件运行两次webpack:第一次构建将花费正常时间。第二个版本将明显更快。

configureWebpack:{
	...
	plugins: [
    	new HardSourceWebpackPlugin()
  	]
}


主要参数如下:

  1. cacheDirectory: 缓存的路径
  2. configHash: 转换webpack配置,根据不同的配置生成不同的缓存
  3. environmentHash:构建的依赖或者插件发送改变是使用心得缓存
  4. maxAge: 最大缓存时间
  5. sizeThreshold:最大缓存内存,超出就删除所有缓存

ps: 在构建过程中(build),HardSourceWebpackPlugin 也会对其产生作用,会降低build的时间,嗯,至于会不会对打包后的文件产生影响,暂时不知道,如果怕有问题,可以使用process.env.NODE_ENV对环境进行判断。

chainWebpack: config => {
	if(process.env.NODE_ENV === "development"){
    	...
    	config.plugin('HardSourceWebpackPlugin')
              .use(HardSourceWebpackPlugin);
    }else{
    	...
    }
}


cache-loader

使用 cache-loader 可以将编译结果写入硬盘缓存,Webpack 再次构建时如果文件没有发生变化则会直接拉取缓存

官方提示:请注意,保存和读取这些缓存文件会有一些时间开销,所以请只对性能开销较大的 loader 使用此 loader。

ps:vue-cli默认对vue,babel,ts 使用cache-loader,不需要手动配置

如果你想给别的loader添加cache-loader,怎么添加?其实也很简单,按照下面的步骤即可:

  1. 建议先在命令行输入vue inspect > output.js查看原来的配置(vue ui也是个不错的选择)
  2. 使用chainWebpack对配置进行链式操作(代码如下)

// 一开始我是这么写的
chainWebpack: config =>{
	...
    config.module
    	  .rule('images')
          .use('cache-loader')
          .loader('cache-loader')
          .end();
}

// 输入 vue inspect > output.js 查看配置文件发现(代码如下)
/* config.module.rule('images') */
{
  test: /\.(png|jpe?g|gif|webp)(\?.*)?$/,
  use: [
    {
      loader: 'url-loader',
      options: {
        limit: 4096,
        fallback: {
          loader: 'file-loader',
          options: {
            name: 'Static/img/[name].[hash:8].[ext]'
          }
        }
      }
    },
    {
      loader: 'cache-loader'
    }
  ]
},

// loader的调用顺序是反方向的(从右到左,从下到上),如果不把cache-loader放到第一个位置,是没办法对图片进行缓存

// 改进后的配置如下
const imagesRule = config.module.rule('images');
imagesRule.uses.clear(); // 先把之前的loader配置清除
imagesRule.use('cache-loader')
          .loader('cache-loader')
          .options({
            cacheDirectory: path.join(__dirname,'/node_modules/.cache/url-loader')
          })
          .end()
          .use('url-loader')
          .loader('url-loader')
          .options({
            limit: 4096,
            fallback: {
              loader: 'file-loader',
              options: {
                name: assetsDir+'/img/[name].[hash:8].[ext]'
              }
            }
          })
          .end()

多线程加快构建

适用于多线程打包的插件有happypackandthread-loader。由于happypack的作者已经弃坑了,所以笔者说一下如何使用thread-loader。

提示: thread-loader自身会消耗时间来启动,可以适用于大项目里比较耗时的loader。

在开始之前,我们先来安装一个speed-measure-webpack-plugin来打印具体的时间。有一点要注意的是,使用这个插件的话,configureWebpack 要使用对象的形式而不是函数形式。

// 使用 speed-measure-webpack-plugin
const SpeedMeasurePlugin = require("speed-measure-webpack-plugin");
const smp = new SpeedMeasurePlugin();
// 把配置用变量存起来
const configureWebpack = {
  externals: {
    vue: "Vue",
    "element-ui": "ELEMENT"
  },
  devtool: isDev? 'source-map':'none',
  ...
}
... //其他配置
configureWebpack: isDev?configureWebpack:smp.wrap(configureWebpack), 
// 这里配置configureWebpack,这个笔者不想在开发环境使用,所以就做了一个判断,当然你可以忽略它

好,下面我们就开始配置thread-loader。 提示:不要过于期待这玩意。

// thread-loader配置 

//可以通过预热 worker 池(worker pool)来防止启动 worker 时的高延时。
  threadLoader.warmup({},[
    'babel-loader', 
    'vue-loader',
    'sass-loader'
  ])
  const babelRules = config.module.rule('js');
  babelRules.uses.clear();
  babelRules.use('thread-loader')
            .loader('thread-loader')
            .options({
              // 产生的 worker 的数量,默认是 cpu 的核心数
              workers: 2,

              // 一个 worker 进程中并行执行工作的数量
              // 默认为 20
              workerParallelJobs: 50,

              // 额外的 node.js 参数
              workerNodeArgs: ['--max-old-space-size=1024'],

              // 闲置时定时删除 worker 进程
              // 默认为 500ms
              // 可以设置为无穷大, 这样在监视模式(--watch)下可以保持 worker 持续存在
              poolTimeout: 2000,

              // 池(pool)分配给 worker 的工作数量
              // 默认为 200
              // 降低这个数值会降低总体的效率,但是会提升工作分布更均一
              poolParallelJobs: 50,
              name:'my-pool'
            })
            .end()
            .use('cache-loader')
            .loader('cache-loader')
            .options({
                cacheDirectory: path.join(__dirname,'/node_modules/.cache/babel-loader')
              })
            .end()
            .use('babel-loader')
            .loader('babel-loader')
            .end();

  const vueRules = config.module.rule('vue');
  vueRules.uses.clear();
  vueRules.use('thread-loader')
          .loader('thread-loader')
          .options({
            name:'my-pool'
          })
          .end()
          .use('cache-loader')
          .loader('cache-loader')
          .options({
                cacheDirectory: path.join(__dirname,'/node_modules/.cache/vue-loader')
            })
          .end()
          .use('vue-loader')
          .loader('vue-loader')
          .options({
            compilerOptions: {
              preserveWhitespace: false
            },
          })
          .end();

经过多次测试,发现加不加thread-loader并没有什么差距(由于电脑的不同,有时候启动会慢一点)

排除静态的依赖

对于一些极少变化的第三方插件依赖,可以排除在构建过程中,以此提高构建速度。其实这个的方法和插件也不少,这里笔者主要讲externalsDllPlugin

externals

externals 配置选项提供了「从输出的 bundle 中排除依赖」的方法。防止将某些 import 的包(package)打包到 bundle 中,而是在运行时(runtime)再去从外部获取这些扩展依赖(external dependencies)。

这个属性很好理解,而且使用起来也非常方便,非常的nice! 最简单的方法是配置名称,当然你也可以编写一些复杂的配置官方文档

//vue.config.js
...
configureWebpack:{
	externals: {
      "vue": "Vue",
      "element-ui": "ELEMENT"
    },
}

// 然后在 index.html 手动引入(或者用插件自动添加)

DllPlugin

这个插件是在一个额外的独立的 webpack 设置中创建一个只有 dll 的 bundle(dll-only-bundle)。 这个插件会生成一个名为 manifest.json 的文件,这个文件是用来让 DLLReferencePlugin 映射到相关的依赖上去的。

可以简单理解为把一些依赖从项目的bundle中拆分出去,通过映射关系用请求来加载。

配置DllPlugin,可以分为下面几个步骤:

  1. 新建webpack.dll.config.js文件(其他命名都可以),配置需要拆分的插件;
  2. 在package.json文件中新建一条命令来专门打包,"build:dll":"webpack --config webpack.dll.config.js"; 运行该命令;
  3. 在vue.config.js 文件中配置DllReferencePlugin,主要把dll引用到需要预编译的依赖;
  4. 在index.html手动引入拆分的bundle包
// webpack.dll.config.js

const path = require('path')
const webpack = require('webpack')
module.exports = {
  entry: { // 这里使用了多个入口配置,你也可以使用一个入口
    vueRouter: ['vue-router'],
    vuex:['vuex'],
    axios:['axios'],
  },
  //打包放到dll,这里不跟项目的打包文件放到同一个文件夹,是因为vue-cli打包会先把输出目录先清除一遍。。。
  output: { 
    path: path.resolve('./dll'),
    filename: '[name].dll.js',
    library: '[name]_library'
  },
  plugins: [
    new webpack.DllPlugin({
      path: path.resolve('./dll', '[name]-manifest.json'),
      name: '[name]_library'
    }),
  ]
}

// vue.config.js

...
configureWebpack:{
	plugins:[ // 由于是多个入口打包,所有这里初始化多次
    new webpack.DllReferencePlugin({
        context: __dirname,
        manifest: require("./dll/vueRouter-manifest.json")
    }),
    new webpack.DllReferencePlugin({
        context: __dirname,
        manifest: require("./dll/vuex-manifest.json")
    }),
    new webpack.DllReferencePlugin({
        context: __dirname,
        manifest: require("./dll/axios-manifest.json")
    }),
}


// index.html
<script src="/dll/vueRouter.dll.js"></script>
<script src="/dll/vuex.dll.js"></script>
<script src="/dll/axios.dll.js"></script>

总结

到这来其实就已经优化的差不多了,当然还是有其他一些细节是可以优化的,但是笔者觉得已经差不多了,再优化也无法带来更高的收益了,当然,还有一个办法就是升级webpack版本比任何方法都能提高的快(狗头)。