评论

收藏

[JavaScript] 我是如何把vue项目启动时间从70s优化到7秒的

开发技术 开发技术 发布于:2021-12-17 18:30 | 阅读数:551 | 评论:0

可怕的启动时间
公司的产品是一个比较大的后台管理系统,而且使用的是webpack3的vue模板项目,单次项目启动时间达到了70s左右
DSC0000.png
启动个项目都够吃一碗豆腐脑了,可是没有豆腐脑怎么办,那就优化启动时间吧!
考虑到升级webpack版本的风险还是比较大的,出了一点问题都得找我,想想还是先别冒险,稳妥为主,所以我选择了通过插件来优化构建时间。
通过查阅资料,提升webpack的构建时间有以下几个方向:
  • 多进程处理文件,同一时间处理多个文件
  • 预编译资源模块,比如把长时间不变的库提取出来做预编译,构建的时候直接取编译结果就好
  • 缓存,未修改的模块直接拿到处理结果,不必编译
  • 减少构建搜索和处理的文件数量
针对以上几种优化方向,给出以下几种优化方案。
多进程构建
happypack
happypack 的作用就是将文件解析任务分解成多个子进程并发执行。
子进程处理完任务后再将结果发送给主进程。所以可以大大提升 Webpack 的项目构件速度。
查看happypack的github,发现作者已经不再维护该插件,并且作者推荐使用webpack官方的多进程插件thread-loader,所以我放弃了happypacy,选择了thread-loader。
thread-loader
​​thread-loader​​是官方维护的多进程loader,功能类似于happypack,也是通过开启子任务来并行解析文件,从而提高构建速度。
把这个loader放在其他loader前面。不过该loader是有限制的。示例:
  • loader无法发出文件。
  • loader不能使用自定义加载器API。
  • loader无法访问网页包选项。
每个worker都是一个单独的node.js进程,其开销约为600毫秒。还有进程间通信的开销。在小型项目中使用​​thread-loader​​可能并不能优化项目的构建速度,反而会拖慢构建速度,所以使用该loader时需要明确项目构建构成中真正耗时的过程。
我的项目中我主要是用该loader用来解析vue和js文件,作用于​​vue-loader​​和​​babel-loader​​,如下代码:
const threadLoader = {
  loader: 'thread-loader',
  options: {
  workers: require('os').cpus().length - 1,
  }
}
module.exports = {
  module:{
  rules: [
    {
    test: /\.vue$/,
    use: [
      threadLoader, // vue-loader前使用该loader
      {
      loader: 'vue-loader',
      options: vueLoaderConfig
      }
    ],
    },
    {
    test: /\.js$/,
    use: [
      threadLoader, // babel-loader前使用该loader
      {
      loader: 'babel-loader',
      options: {
        cacheDirectory: true
      }
      }
    ]
    }
  ]
  }
}
配置了​​thread-loader​​后,重新构建试试,如下图所示,大概缩短了10秒的构建时间,还不错。
DSC0001.png
利用缓存提升二次构建的速度
虽然使用了多进程构建项目使构建时间缩短了10秒,但是一分钟的构建时间依然让人无法接受,这种挤牙膏似的优化方式多少让人有点不爽,有没有比较爽的方法来进一步缩短构建时间呢?
答案是有的,使用缓存。
缓存,不难理解就是第一次构建的时候将构建的结果缓存起来,当第二构建时,查看对应缓存是否修改,如果没有修改,直接使用缓存,由此,我们可以想象,当项目的变化不大时,大部分缓存都是可复用的,拿构建的速度岂不是会有质的飞跃。
cache-loader
说到缓存,当然百度一查,最先出现的就是​​cache-loader​​,github搜索下官方文档,得到如下结果:
该loader会缓存其他loader的处理结果,把该loader放到其他loader的前面,同时该loader保存和读取缓存文件也会有开销,所以建议在开销较大的loader前使用该loader。
文档很简单,考虑到项目中的​​vue-loader​​,​​babel-loader​​,​​css-loader​​会有比较大的开销,所以为这些loader加上缓存,那么接下来就把​​cache-loader​​加到项目中吧:
const cacheLoader = {
  loader: 'cache-loader'
}
const threadLoader = {
  loader: 'thread-loader',
  options: {
  workers: require('os').cpus().length - 1,
  }
}
module.exports = {
  module:{
  rules: [
    {
    test: /\.vue$/,
    use: [
      cacheLoader,
      threadLoader, // vue-loader前使用该loader
      {
      loader: 'vue-loader',
      options: vueLoaderConfig
      }
    ],
    },
    {
    test: /\.js$/,
    use: [
      cacheLoader,
      threadLoader, // babel-loader前使用该loader
      {
      loader: 'babel-loader',
      options: {
        cacheDirectory: true
      }
      }
    ]
    }
  ]
  }
}
在​​util.js​​文件中,该文件主要是生成css相关的webpack配置,找到​​generateLoaders​​函数,修改如下:
const cacheLoader = {
  loader: 'cache-loader'
  }
  
  function generateLoaders(loader, loaderOptions) {
  // 在css-loader前增加cache-loader
  const loaders = options.usePostCSS ? [cacheLoader, cssLoader, postcssLoader] : [cacheLoader, cssLoader]
  if (loader) {
    loaders.push({
    loader: loader + '-loader',
    options: Object.assign({}, loaderOptions, {
      sourceMap: options.sourceMap
    })
    })
  }
  // Extract CSS when that option is specified
  // (which is the case during production build)
  if (options.extract) {
    return ExtractTextPlugin.extract({
    use: loaders,
    fallback: 'vue-style-loader',
    // 添加这句配置解决element-ui的图标路径问题
    publicPath: '../../'
    })
  } else {
    return ['vue-style-loader'].concat(loaders)
  }
  }
如上配置完成后,再次启动项目,可以发现,现在的启动时间没什么变化,然后我们二次启动项目,可以发现现在的启动时间来到了30s左右,前面我们已经说过了,​​cache-loader​​缓存只有在二次启动的时候才会生效。
DSC0002.png
虽然项目启动时间优化了一半还多,但是我们的欲望是无限大的,30秒的时间离我们的预期还是有点差距的,继续优化!
hard-source-webpack-plugin
​​HardSourceWebpackPlugin​​是一个webpack插件,为模块提供中间缓存步骤。为了查看结果,您需要使用此插件运行webpack两次:第一次构建将花费正常的时间。第二次建设将大大加快。
话不多说,直接配置到项目中:
const HardSourceWebpackPlugin = require('hard-source-webpack-plugin');
module.exports = {
  //...
  plugins: [
    new HardSourceWebpackPlugin()
  ]
}
DSC0003.png
二次构建时,我们会发现构建时间来到了个位数,只有短短的7秒钟。
在二次构建中,我发现了一个现象,构建的进度会从10% 一下跳到 80%,甚至是一瞬间就完成了中间构建过程。这正验证了该插件为模块提供中间缓存的说法。
为模块提供中间缓存,我的理解是cache-loader缓存的是对应loader的处理结果 ,而这个插件甚至可以缓存整个项目全部的处理结果,直接引用最终输出的缓存文件,从而大大提高构建速度。

其他优化方法
babel-loader开启缓存
​​babel-loader​​自带缓存功能,开启​​cacheDirectory​​配置项即可,官网的说法是,开启缓存会提高大约两倍的转换时间。
module.exports = {
  module: {
  rules: [
    {
    test: /\.js$/,
    use: [
      ...
      {
      loader: 'babel-loader',
      options: {
        cacheDirectory: true // 开启缓存
      }
      }
    ]
    }
  ]
  }
}
uglifyjs-webpack-plugin开启多进程压缩
​​uglifyjs-webpack-plugin​​或是其他的代码压缩工具都提供了多进程压缩代码的功能,开启可加速代码压缩。

总结
至此,我们完成了项目构建时间从70s到7s的优化过程,文中主要使用:
工具
作用
优化效果
thread-loader
多进程解析文件
70s -> 60s
cache-loader
缓存部分高开销的loader
60s -> 30s
hard-source-webpack-plugin
缓存模块中间过程
30s -> 7s

最后
如果你觉得此文对你有一丁点帮助,点个赞。或者可以加入我的开发交流群:1025263163相互学习,我们会有专业的技术答疑解惑
如果你觉得这篇文章对你有点用的话,麻烦请给我们的开源项目点点star: ​​https://gitee.com/ZhongBangKeJi/CRMEB​​不胜感激 !


关注下面的标签,发现更多相似文章