webpack4項(xiàng)目打包速度優(yōu)化之路

背景及現(xiàn)狀

新接手了一個(gè)項(xiàng)目,項(xiàng)目已經(jīng)兩年多了,使用了很多依賴,包括內(nèi)部開發(fā)的組件發(fā)布的npm包,導(dǎo)致打包速度越來越慢

分析

目前在本地打包普遍需要240S以上,在gitlab的CI/CD上則需要10分鐘左右。需要將CI/CD流水線上的構(gòu)建速度提高,從而更快的部署,那么我們只有兩個(gè)優(yōu)化方向:

  1. 項(xiàng)目打包本身優(yōu)化速度
  2. 運(yùn)維支持在CI/CD上做文章,做緩存

由于沒有運(yùn)維,無法獲得運(yùn)維在docker上的緩存支持,無奈只能在項(xiàng)目build本身下功夫

再次分析

我們優(yōu)化構(gòu)建速度的方法方式無外乎壓縮、多流程并行、減少不必要的消耗、升級(jí)版本、緩存等這些方式。而本項(xiàng)目的最終目標(biāo)是需要再CI/CD上構(gòu)建速度大幅提升,所以緩存的方式在本次優(yōu)化中不能起到作用

曲折的優(yōu)化之路

經(jīng)過近兩個(gè)星期的嘗試,以及踩各種坑最終使得CI/CD構(gòu)建的速度降低到4分鐘上下,節(jié)省60%。下面就來看看踩過的坑

  1. speed-measure-webpack-plugin,為了分析各個(gè)loader打包所占用的時(shí)間,此插件必不可少。但是啊但是!我們生產(chǎn)構(gòu)建的時(shí)候沒有必要開啟它,因?yàn)樗矔?huì)大幅拖慢打包速度。經(jīng)過不下10次的對(duì)比,開啟時(shí)比不開啟時(shí)多耗時(shí)近一分鐘!其配置也很簡單:
    在vue.config.js中首先聲明
const SpeedMeasurePlugin = require('speed-measure-webpack-plugin')
const smp = new SpeedMeasurePlugin()

然后

module.exports = {
...
  configureWebpack:smp.wrap({
  ...
  })
}
  1. cdn加速,項(xiàng)目中引入的如vue,vuex,vue-router等可以使用cdn加速來使用,避免被打包進(jìn)去,而且這些依賴一般比較龐大,如果使用得當(dāng),可以有比較明顯的效果。
    ——但是啊但是!本項(xiàng)目中沒有用vuex,vue-router的經(jīng)典模塊。。。用了vuex-module、router-module、vue-modx這些方式。。。蒼天??!可能真的是我才疏學(xué)淺,孤陋寡聞了。
    ——所以CDN這條路在本項(xiàng)目中沒什么卵用了
  2. webpack-parallel-uglify-plugin,此插件是用來開啟多進(jìn)程壓縮的,webpack4本身內(nèi)置了uglify,所以如果項(xiàng)目龐大,使用效果是可以的。
    ——問題就處在這里,開啟多進(jìn)程也是需要消耗時(shí)間的,如果項(xiàng)目不夠龐大,反而會(huì)拖慢速度。本項(xiàng)目就剛好是這么尷尬,使用之后反而拖慢了速度,打包時(shí)間竟然上升到300+秒,簡直是老奶奶鉆被窩,給爺整笑了。代碼如下:
    照樣先聲明
const ParallelUglifyPlugin = require('webpack-parallel-uglify-plugin')

然后

module.exports = {
...
  configureWebpack:({
  ...
    plugins: [
        ...
        new ParallelUglifyPlugin({
        // 傳遞給 UglifyJS 的參數(shù)
        cacheDir: '.cache/',
        uglifyJS: {
          output: {
            // 最緊湊的輸出
            beautify: false,
            // 刪除所有的注釋
            comments: false
          },
          warnings: false,
          compress: {
            // 在UglifyJs刪除沒有用到的代碼時(shí)不輸出警告
            // warnings: false,
            // 刪除所有的 `console` 語句,可以兼容ie瀏覽器
            drop_console: true,
            // 內(nèi)嵌定義了但是只用到一次的變量
            collapse_vars: true,
            // 提取出出現(xiàn)多次但是沒有定義成變量去引用的靜態(tài)值
            reduce_vars: true
          }
        }
      })
    ]
    ...
  })
}

所以如果項(xiàng)目本身不是特別龐大,不能用這個(gè)

  1. terser-webpack-plugin,其是webpack5內(nèi)置的壓縮插件,但是webpack4還是需要先安裝
    npm install terser-webpack-plugin --save-dev
    其和ParallelUglifyPlugin相似,開啟多進(jìn)程,在webpack4下,項(xiàng)目不大的情況下不要使用,本項(xiàng)目使用前后差別不大,聊勝于無。代碼如下:
    照樣先聲明
const TerserPlugin = require('terser-webpack-plugin')

然后

module.exports = {
...
  configureWebpack:({
    ...
    optimization: {
      minimizer: [
        new TerserPlugin({
          parallel: true
        })
      ]
    }
    ...
  })
}

parallel: true的意思是開啟進(jìn)程,數(shù)量比計(jì)算機(jī)內(nèi)核數(shù)少1。true也可以替換成具體的數(shù)字;

  1. thread-loader,其也是webpack內(nèi)置的插件,可以直接使用,配置也相當(dāng)簡單。代碼如下:
module.exports = {
...
  configureWebpack:({
    ...
    module: {
      rules: [
        {
          test: /\.js$/,
          exclude: /node_modules/,
          use: [
            {
              loader: 'thread-loader'
            },
            'babel-loader',
            'cache-loader'
            ... //其他占用打包時(shí)間較大的loader
          ]
        }
        ...    
      ]
    }
    ...
  })
}

其效果較為可以,可以從10分鐘優(yōu)化到近6分鐘,提升40%。

  1. include & exclude精確匹配路徑,效果不明顯,聊勝于無,比較雞肋。代碼如下:
module.exports = {
...
  chainWebpack(config) {
  ...
  config.module
      .rule('vue-loader')
      .test(/\.js$/)
      .include.add(resolve('src')).end()
      .exclude.add(/node_modules\/(?!(autotrack|dom-utils))|vendor\.dll\.js/).end()
    config.module
      .rule('babel-loader')
      .test(/\.vue$/)
      .include.add(resolve('src')).end()
      .exclude.add(/node_modules/).end()
  ...
  }
}
  1. esbuild-loader,其采用go編寫,速度大幅提升,也是我最終采用的方式,最終優(yōu)化到了4分鐘上下。不過其也有確定,由于其目前剛出來,才一年左右。目前生態(tài)尚不完善,文檔較少。如果膽子夠大,可以考慮項(xiàng)目整體采用vite,其是基于esbuild,性能很好。上代碼:
    首先需要安裝esbuild-loader,npm i -D esbuild-loader
    然后聲明一下
const { ESBuildMinifyPlugin } = require('esbuild-loader')

然后

module.exports = {
...
  chainWebpack(config) {
  ...
  const rule = config.module.rule('js')

    // 清理自帶的 babel-loader
    rule.uses.clear()

    // 添加 esbuild-loader
    rule
      .use('esbuild-loader')
      .loader('esbuild-loader')
      .options({
        loader: 'jsx', // 如果使用了 ts, 或者 vue 的 class 裝飾器,則需要加上這個(gè) option 配置, 否則會(huì)報(bào)錯(cuò): ERROR: Unexpected "@"
        target: 'es2015',
        jsxFactory: 'h', //項(xiàng)目中使用了JSX語法,必須聲明jsxFactory和jsxFragment,否則會(huì)報(bào)錯(cuò)can not find react
        jsxFragment: 'Fragment'
      })

    // 刪除底層 terser, 換用 esbuild-minimize-plugin
    config.optimization.minimizers.delete('terser')

    // 使用 esbuild 優(yōu)化 css 壓縮
    config.optimization
      .minimizer('esbuild')
      .use(ESBuildMinifyPlugin, [{ minify: true, css: true }])
  ...
  }
}
  1. 最后說一下,再好的方法不如升級(jí)webpack版本。只是老項(xiàng)目畢竟相互的依賴過于復(fù)雜,牽一發(fā)動(dòng)全身,不敢輕易妄動(dòng)。

題外話

雖然緩存不能在CI/CD起作用————不過,我也嘗試了使用緩存進(jìn)行嘗試,一共兩種方式:

  1. 使用hard-source-webpack-plugin,代碼很簡單,webpack4自帶hard-source-webpack-plugin插件
    在vue.config.js中首先聲明
const HardSourceWebpackPlugin = require('hard-source-webpack-plugin')

然后

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

對(duì)比了效果,如果是在本地構(gòu)建,速度提升還是很明顯的。第一次時(shí)間比較長,第二次直接從240S變成40S,大約提升了80%

  1. 使用cache,能到80S左右,效果差一些,能節(jié)省70%左右,也是webpack4自帶的。代碼如下:
    在vue.config.js中
module.exports = {
...
  chainWebpack(config) {
  ...
    config.cache()
  }
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容