升級優化Webpack4,使你的打包速度提升200%以上!

練習時長一年的前端工程師發表於2019-05-10

之前修改公司的運營系統,多年來積累下來的程式碼使得每次打包構建的時候時間異常漫長,非常難受,一氣之下將專案的Webpack從2.x升級到了4.x,原來在Jenkins上一套需要6分鐘的流程現在只需2分鐘,頓時感到身心舒暢( ̄▽ ̄)/
並且我順便把專案劃分成多入口專案,實現增量更新,這樣就不用每次都打包這麼多檔案啦!

ps:以下配置基於React,可以自行根據實際專案用到的框架和庫修改


1. 配置中可優化的點

先說一下我在升級中發現的可以優化的點,大家有什麼建議和想法可以一併提出。

1.1 優化第三方庫

優化第三方庫最簡單粗暴並且及其有效的一個方式就是使用webpack的DllPlugin。它可以將我們經常使用但是修改頻率極低的第三方庫與自己的程式碼完全分離開, 每次打包的時候會根據索引判斷是否需要重新打包第三方庫,大大提高了打包速度。用法我就不細說了,網上有很多教程,感興趣的可以去了解下。
庫的索引一般儲存在一個manifest.json檔案中。我們可以通過這個檔案看到自己專案對第三方庫的打包情況。

優化思路:

  • 專案裡安裝的第三方庫的需要評估必要性,如果很少使用到庫的不要在webpackvendor入口指定打包進來。
  • 同時可以升級某些第三方庫,如react v16對比react v15,加上react-dom,體積上降低了30%,因此果斷升級。
  • 分析manifest.json檔案後發現某些庫體積特別大,例如使用很廣泛的moment.js庫。研究後發現webpack把庫下面所有的locale檔案打包了進來。這是一些支援多語言的檔案。即我們可以通過ContextReplacementPlugin外掛來挑選我們需要的檔案。
plugins: [
    new webpack.ContextReplacementPlugin(/moment[\/\\]locale$/, /zh-cn/),
]
複製程式碼
  • 有時候通過依賴分析會發現專案裡打包了兩份第三方庫,一份是es module版的,另一份則是umd版的。如moment通過import 'moment'匯入的是es module版的,locale檔案中卻會用相對路徑匯入umd版的。這時我們可以通過配置別名將庫指向同一個版本。
resolve: {
    alias: {
        'moment$': path.resolve('node_modules/moment/moment'),
    },
}
複製程式碼
  • 有時候專案裡並不需要某個庫的全部功能,這時候可以考慮用更輕量的庫來替代,或者使用定製閹割版(如Echarts, 官網上可定製),並且用import()語法和react-loadable這個庫將react元件包裝成非同步元件,在需要時才進行載入,這樣webpack就會將非同步元件抽取為非同步載入的chunk,就不會多次載入。下面就說如何抽取。

1.2 抽取非同步載入的chunk中的公共程式碼

在webpack4之前,我們使用CommonsChunkPlugin來抽取非同步元件,但現在CommonsChunkPlugin已經被廢棄了,取而代之的是SplitChunksPlugin
使用方法有兩種,一種是像Webpack3那樣在plugins中配置,一種是在Webpack4中的optimization屬性中的splitChunks配置

    optimization: {
      splitChunks: { // 拆分程式碼規則(符合即單獨拆分到一個chunk中)
        maxInitialRequests: 4, // 在初始化載入時,請求數量大於4
        automaticNameDelimiter: '-',
        name: true, // 程式碼塊的名字,設定為true則表示根據模組和快取組祕鑰自動生成, 實現固化 chunkId,保持快取的能力
        
        /* 快取組,用於繼續細分程式碼。
        快取組預設將node_modules中的模組拆分帶一個叫做vendors的程式碼塊, 將最少重複引用兩次的模組放入default中。
        或者自定義將符合規則的模組單獨拆分進一個chunk中。*/
        
        cacheGroups: {
          default: false, // 禁用預設規則
          vendors: false, // 禁用預設規則
          polyfill: {
            chunks: 'initial',
            test: 'polyfill',
            name: 'polyfill',
          },
          vendor: {
            chunks: 'initial',
            test: 'vendor',
            name: 'vendor',
          },
          // 對非同步元件進行抽取
          'async-vendor': {
            name: 'async-vendor',
            chunks: 'async',
            test: (module) => { // 快取組的規則,表示符合條件的的放入當前快取組
              if (/[\\/]node_modules[\\/](echarts|zrender)/.test(module.context)) {
                return false;
              }
              return /[\\/]node_modules[\\/]/.test(module.context);
            },
          },
          'async-biz': {
            name: 'async-biz',
            chunks: 'async',
            minChunks: 2, // 被引用次數大於2時進行拆分
          },
          // css檔案單獨打包進一個檔案中
          'styles': {
            name: 'styles',
            test: /\.css$/,
            chunks: 'async',
            enforce: true,
          },
        },
      },
    },
複製程式碼

當抽取非同步載入元件時,我們需要在業務程式碼中使用下面的寫法,webpack才能識別:
基於react-loadable庫,簡介: React Loadable 簡介

import Loadable from 'react-loadable';
const loder1 = import(/* webpackChunkName: "chunkname2" */'path/to/module2')
const Component = Loadable({
    loader,
    loadingComponent, // loding時使用的元件
})
複製程式碼

1.3 合理使用非同步載入

若我們專案中的首屏也是使用非同步載入的方法,並且用到了async-vendor裡的元件,首屏載入時就會同時載入async-vendor並做快取,這樣之後的頁面使用到了async-vendor裡面的元件之後就無需再次載入新的程式碼。但是這樣就會可能拖慢我們的的首屏速度。
解決辦法一是拆分async-vendor的首屏部分;或是取消首屏頁面的非同步載入,將其打包到main中,這樣就避免了載入async-vendor,大大減少了首屏體積。

1.4 分離出webpack runtime程式碼

webpack在客戶端執行時會首先載入webpack相關的程式碼,例如require函式等,這部分程式碼會隨著每次修改業務程式碼後發生變化,原因是這裡面會包含chunk id等容易變化的資訊。如果不抽取出來將會被打包在vendor當中,導致vendor每次都要被使用者重新載入。抽離的配置寫在optimization屬性中的runtimeChunk配置中:

optimization: {
    runtimeChunk: {
        name: 'manifest',
    },
}
複製程式碼

1.5 webpack內部優化

webpack4之前,內部優化主要使用兩個外掛:HashedModuleIdsPluginModuleConcatenationPlugin
預設情況下,webpack會為每個模組用數字做為ID,這樣會導致同一個模組在新增刪除其他模組後,ID會發生變化,不利於快取。
為了解決這個問題,有兩種選擇:NamedModulesPluginHashedModuleIdsPlugin,前者會用模組的檔案路徑作為模組名,後者會對路徑進行md5處理。因為前者處理速度較快,而後者打包出來的檔案體積較小,所以應該開發環境時選擇前者,生產環境時選擇後者。
ModuleConcatenationPlugin主要是作用域提升,將所有模組放在同一個作用域當中,一方面能提高執行速度,另一方面也能降低檔案體積。前提是你的程式碼是用es模組寫的。
在 webpack4 中,只需要optimization的配置項中設定 moduleIdshashed或者named, 設定modeproduction即可。

mode:'production',
optimization : {
    moduleIds: 'hashed',
}
複製程式碼

1.6 babel-polyfill優化

目前本人專案裡用的方法是命名一個來源於babel-polyfill的polyfill的檔案,但是註釋掉了大部分的import,僅保留專案的基本需求,並在在專案入口檔案中引入。

// webpack 中配置
entry: {
  polyfill: path.join(process.cwd(), 'path/to/your/polyfill'),
},
複製程式碼

參考下別人一些對polyfill優化的的優秀文章:

2.參考文章

webpack4——SplitChunksPlugin使用指南
多頁應用 Webpack4 配置優化與踩坑記錄
webpack 配置筆記 (這是我同事的blog哈哈,裡面有我們現在使用的Webpack的具體配置,大家感興趣可以去裡面仔細看看學習,他是個高手,大家可以多多關注~)

3. 結語

上面的優化是基於對webpack有一定了解的情況下進行的,若你對webpack還不是很瞭解,我推薦到 webpack中文文件 裡學習基礎知識,並且到掘金上面搜尋更多關於webpack的優秀文章啦!

升級優化Webpack4,使你的打包速度提升200%以上!

相關文章