升級Webpack5實踐

JackySummer發表於2021-10-22

最近將公司專案由webpack4升級到了webpack5,配置了webpack5的長效快取後,二次構建速度直接提速了80%以上,同時打包體積也減少了,當然前提是要調研清楚坑多不多。

網上有一些做法是直接全部升級相關的包,而我是一個個包升級過來,如果有必要再升級,有的包沒有升級(如babel-loader等等),專案本身用的大多也是較新的版本。

Webpack5 新特性

首先先要了解下升級 Webpack5能給我們帶來什麼好處,先看看 Webpack5的新特性

  • 通過持久化硬碟快取能力來提升構建效能
  • 通過更好的演算法來改進長期快取(降低產物資源的快取失效率)
  • 通過更好的 Tree Shaking 能力和程式碼的生成邏輯來優化產物的大小
  • 改善 web 平臺的相容效能力
  • 清除了內部結構中,在 Webpack4 沒有重大更新而引入一些新特性時所遺留下來的一些奇怪的 state
  • 通過引入一些重大的變更為未來的一些特性做準備,使得能夠長期的穩定在 Webpack5 版本上

對我來說最吸引人的還是構建效能的提升和更好的tree shaking 能力來優化產物的大小。

Webpack5 版本上為什麼構建速度有了質的飛躍?

主要是因為:

webpack4是根據程式碼的結構生成chunkhash,新增了空白行或註釋,會引起chunkhash的變化,webpack5是根據內容生成chunkhash,改了註釋或者變數不會引起chunkhash的變化,瀏覽器可以繼續使用快取。

  • 優化了對快取的使用效率。在webpack4 中,chunkId與moduleId都是自增id。只要我們新增一個模組,那麼程式碼中module的數量就會發生變化,從而導致moduleId發生變化,於是檔案內容就發生了變化。chunkId也是如此,新增一個入口的時候,chunk數量的變化造成了chunkId的變化,導致了檔案內容變化。所以對實際未改變的chunk檔案不能有效利用。webpack5採用新的演算法來計算確定性的chunkId和moduleId。可以有效利用快取。在production模式下,optimization.chunkIds和optimization.moduleIds預設會設為’deterministic’。
  • 新增了可以將快取寫入磁碟的配置項, 在命令列終止當前構建任務,再次啟動構建時,可以複用上一次寫入硬碟的快取,加快構建過程。
以上三點解釋引用自Webpack5構建速度提升令人驚歎,早升級早受益

簡單瞭解上述後,簡單記錄下我在升級過程做的一些事情,要看完整的建議去官方升級文件查閱

直接升級依賴

以下一些庫在我們專案直接升級到最新版本後是不需要做相容的,本身配置不多,但是如果你專案外掛寫的配置項多就可能有需要做相容的地方。

  • webpack webpack-cli
  • html-webpack-plugin
  • terser-webpack-plugin
  • mini-css-extract-plugin
  • style-loader、css-loader、postcss、postcss-loader(升級)

相容

資源模組型別

在 webpack4及之前我們會用各種loader來處理一些資源,比如file-loader,url-loaderraw-loader等等,但webpack5內建了靜態資源構建能力,不再需要安裝這些額外的 loader,通過簡單的配置就能實現靜態資源的打包。

資源模組型別(asset module type),通過新增 4 種新的模組型別,來替換所有這些 loader:

  • asset/resource 傳送一個單獨的檔案並匯出 URL。之前通過使用 file-loader 實現。
  • asset/inline 匯出一個資源的 data URI。之前通過使用 url-loader 實現。
  • asset/source 匯出資源的原始碼。之前通過使用 raw-loader 實現。
  • asset 在匯出一個 data URI 和傳送一個單獨的檔案之間自動選擇。之前通過使用 url-loader,並且配置資源體積限制實現。

例子:

module: {
    rules: [
        {
            test: /\.(png|jpg|jpeg|gif)$/,
            type: `asset/resource`
        }
    ]
},

webpack-dev-server

需要注意的是:webpack-dev-server v4.0.0+ requires node >= v12.13.0

升級 webpack-dev-server 至 ^4(next) 版本,否則 HMR 會有異常

  • wbepack4 啟動方式為:webpack-dev-server
  • webpack5 修改為:webpack server

    // v4
    devServer: {
      contentBase: path.resolve(__dirname, '../dist'),
      compress: true,
      inline: true, // 在構建變化後的程式碼會通過代理客戶端來控制網頁重新整理
      host: '0.0.0.0',
      port: PORT,
      clientLogLevel: 'info',
      historyApiFallback: true,
      stats: 'errors-only',
      disableHostCheck: true,
    }
    // v5
    devServer: {
      // contentBase 變為 static 物件裡面來配置
      static: {
        directory: path.resolve(__dirname, '../dist'),
      },
      client: {
        logging: 'info',
      },
      compress: true,
      // inline: true, // 直接移除,沒有替代項
      host: '0.0.0.0',
      port: PORT,
      historyApiFallback: true,
      allowedHosts: 'all', // 代替 disableHostCheck: true
      // 新增中介軟體配置
      devMiddleware: {
        stats: 'errors-only',
      },
     },

相關文章:
webpack-dev-server v3 遷移 v4 指南

Webpack5 移除了 Node.js Polyfill(相容)

Webpack5 移除了 Node.js Polyfill,將會導致一些包變得不可用(會在控制檯輸出 'XXX' is not defined),如果需要相容 process/buffer 等 Nodejs Polyfill,則要安裝相關的 Polyfill:process,並在 Plugin 中顯式宣告注入。業務程式碼中是有使用的process 變數的,故需要相容,同時要安裝process/buffer 庫。

{
    plugins: [
        new webpack.ProvidePlugin({
          process: 'process/browser',
        }),
    ]
}

升級廢棄的配置項

未改動前的專案配置項含義(config/webpack_prod.ts):

splitChunks: {
  chunks: 'all',
  minSize: 30000, // 模組要大於30kb才會進行提取
  minChunks: 1, // 最小引用次數,預設為1
  maxAsyncRequests: 5, // 非同步載入程式碼時同時進行的最大請求數不得超過5個
  maxInitialRequests: 3, // 入口檔案載入時最大同時請求數不得超過3個
  automaticNameDelimiter: '_', // 模組檔名稱字首
  name: true, // 自動生成檔名
  cacheGroups: {
    // 將來自node_modules的模組提取到一個公共檔案中 
    vendors: {
      test: /[\\/]node_modules[\\/]/,
      priority: -10, // 執行優先順序,預設為0
    },
    // 其他不是node_modules中的模組,如果有被引用不少於2次,那麼也提取出來
    default: {
      minChunks: 2,
      priority: -20,
      reuseExistingChunk: true, // 如果當前程式碼塊包含的模組已經存在,那麼不在生成重複的塊
    },
  },
},
// v5
splitChunks: {
  cacheGroups: {
    // vendors ——> defaultVendors
    defaultVendors: {
      test: /[\\/]node_modules[\\/]/,
      priority: -10, // 執行優先順序,預設為0
    },
  },
},

這個在(config/webpack_prod.ts)裡面改個名字即可

splitChunks.name(移除)

splitChunks.name 表示抽取出來檔案的名字

  • 在 v4 中該配置預設為 true 表示自動生成檔名
  • v5 移除了 optimization.splitChunks 的 name: true:不再支援自動命名

遷移:使用預設值。chunkIds: "named" 會為你的檔案取一個有用的名字,以便於除錯

output 配置(相容)

(node:58337) [DEP_WEBPACK_TEMPLATE_PATH_PLUGIN_REPLACE_PATH_VARIABLES_HASH] DeprecationWarning: [hash] is now [fullhash] (also consider using [chunkhash] or [contenthash], see documentation for details)

hash已經不推薦使用了,改為了fullhash,這個名字比起原來的hash更清楚
config/webpack_dev.ts

// v4
output: {
    filename: 'js/[name].[hash].js',
    chunkFilename: 'js/[name].[hash].js',
},
// v5
output: {
    filename: 'js/[name].[fullhash].js',
    chunkFilename: 'js/[name].[fullhash].js',
},

優化配置項(廢棄移除)

(config/webpack_prod.ts)

// webpack v4,在 v5 已被廢棄,故需移除

new webpack.HashedModuleIdsPlugin()

HashedModuleIdsPlugin 作用:實現持久化快取。模組 ID 通過 HashedModuleIdsPlugin來進行計算,它會把基於數字增量的 ID 替換成模組自身的 hash。這樣的話,一個模組的 ID 只會在重新命名或者移除的時候才會改變,新模組不會影響到它的 ID 變化。

webpack5 增加了確定的 moduleId,chunkId 的支援,如下配置:

optimization.moduleIds = 'deterministic'
optimization.chunkIds = 'deterministic'

此配置在生產模式下是預設開啟的,它的作用是以確定的方式為 module 和 chunk 分配 3-5 位數字 id,替代 v4 版本的 HashedModuleIdsPlugin。

全域性變數寫法(相容)

module.exports = () => {
  return {
    // ...
    plugins: [
      // webpack5 定義環境變數的寫法變了
      new webpack.DefinePlugin({
        'process.env.NODE_ENV': JSON.stringify('production'),
      }),

    // webpack4的寫法
    //   new webpack.DefinePlugin({
    //    'process.env': {
    //       NODE_ENV: JSON.stringify('production'),
    //     },
    //   }),
    ],
  };
};

使用cache屬性,快取進行優化(新增)

預設情況下 webpack5 不會啟用持久化快取,不自己手動加 cache 配置,webpack5等於沒優化,直接升級二次構建反而更慢;並不是升級就自動提速,還是要手動配置
當設定 cache.type: "filesystem" 時,webpack 會在內部以分層方式啟用檔案系統快取和記憶體快取。 從快取讀取時,會先檢視記憶體快取,如果記憶體快取未找到,則降級到檔案系統快取。 寫入快取將同時寫入記憶體快取和檔案系統快取。
檔案系統快取不會直接將對磁碟寫入的請求進行序列化。它將等到編譯過程完成且編譯器處於空閒狀態才會執行。 如此處理的原因是序列化和磁碟寫入會佔用資源,並且我們不想額外延遲編譯過程。

cache: {
    // 將快取型別設定為檔案系統,預設為memory
    type: 'filesystem',

    buildDependencies: {
      // 當構建依賴的config檔案(通過 require 依賴)內容發生變化時,快取失效
      config: [__filename],
      // 如果你有其他的東西被構建依賴,你可以在這裡新增它們
    },
},

為了防止快取過於固定,導致更改構建配置無感知,依然使用舊的快取,預設情況下,每次修改構建配置檔案都會導致重新開始快取。當然也可以自己主動設定 version 來控制快取的更新。

最終優化成果

第一次編譯:

image.png

第二次編譯:

image.png

效果:二次構建提速85%以上

參考文章

相關文章