如何提高 Webpack 打包速度

文/米酒

背景

前段時間在某個項目進行需求開發的時候,該項目是基於 webpack3 進行打包構建的。在開發過程當中我發現打包很慢,開發體驗不佳,因而作了簡單的優化並梳理了優化方案css

分析打包速度

進行優化的第一步須要知道咱們的構建到底慢在那裏。經過 speed-measure-webpack-plugin 測量你的 webpack 構建期間各個階段花費的時間:前端

// 分析打包時間
const SpeedMeasurePlugin = require("speed-measure-webpack-plugin")
const smp = new SpeedMeasurePlugin()
// ...
module.exports = smp.wrap(prodWebpackConfig)
複製代碼

咱們能夠看到打包速度慢主要是由於對樣式文件和對 js 文件的處理 loader 耗時較久node

當文件發生修改,進行從新編譯時,此時的打包各階段時間以下webpack

從新編譯時耗時其實並不久,可是在瀏覽器上實際上卻花費了更多的時間在看到了新的修改,這是爲什麼呢web

分析 bundle 包

  • 打包後的 bundle 文件生成一個分析文件
"analyse": "webpack --config ./webpack.config.js --profile --json>states.json"複製代碼
  • 對 json 文件進行分析

咱們能夠看到在使用 webpack 3.5.6 進行打包的過程當中涉及到了 705 個模塊,生成了 2 個 chunks, 耗時約 51sjson

  • 進一步分析 chunks 文件

打包出的兩個 chunks 文件分別爲 app 和 vendor 文件,其中 app.js 文件體積高達 6M瀏覽器

在絕大多數的狀況下,應用剛開始工做時,並非全部的模塊都是必需的。若是這些模塊所有被打包到一塊兒,即使應用只須要一兩個模塊工做,也必須先把 bundle.js 總體加載進來,並且前端應用通常都是運行在瀏覽器端,這也就意味着應用的響應速度會受到影響,也會浪費大量的流量和帶寬。緩存

開發環境下的 bunlde 依賴表也能看出 node_modules 中大部份內容是隨着 app.js 一塊兒打包,這些就是引發咱們打包速度緩慢的元兇bash

  • 當咱們代碼有更新時,HMR 會從新打包 app.js,也就是說那些沒有被修改的 node_modules 中內容也會跟着從新打包到 app.js 中去

這意味着,每次代碼修改,瀏覽器都會從新加載這個 6M 大的文件,因此爲啥改了一點點內容,瀏覽器也須要好久纔有反應,元兇在這app

  • Bundle optimize Helper 的優化建議
Entrypoints are code that are loaded on page load. To get best possible user experience, you should keep the total size of entrypoints to less than 200kb and load the rest dynamically by using code splitting.

咱們將 json 文件上傳到 Bundle optimize Helper 獲得的優化建議是去進行代碼分割,入口文件的代碼體積不要超過 200K

優化

代碼分割

高達 6M 的入口文件顯然是很是影響體驗的,所以優化的第一步就是從代碼分割開始。代碼分割經過把項目中的資源模塊按照咱們設計的規則打包到不一樣的 bundle 中,從而下降應用的啓動成本,提升響應速度。

  • 項目自己已經配置了多入口,將 lodash 等三方庫文件單獨進行打包,生成 vendor.js 文件
  • 將入口文件依賴的 node_modules 中內容打包到 common 中,將業務代碼進行單獨打包,這樣能夠有效減小 app.js 的體積
new webpack.optimize.CommonsChunkPlugin({
  name: 'common',
  minChunks: function(module) {
    return (
      module.resource &&
      /\.js$/.test(module.resource) &&
      module.resource.indexOf(
        path.join(__dirname, './node_modules')
      ) === 0
    )
  }
})
複製代碼
  • 再使用一次 CommonsChunkPlugin 抽取 mainfest.js 文件,保證 common.js 的 hash 不會由於每次打包發生變化
new webpack.optimize.CommonsChunkPlugin({
  name: 'manifest',
  chunks: ['vendor', 'common', 'app']
}),
複製代碼
  • 使用 HashedModuleIdsPlugin 來保持模塊引用的 module_id 不變,CommonsChunkPlugin 提取入口指定的依賴獨立打包,mainifest 則保存運行時的函數和模塊標識

能夠看到此時 app.js 中以來的一些三方庫被單獨抽取出來了,體積從 6M 降至 3.49 M

  • 當修改業務代碼時咱們看下新的打包文件

當咱們的業務代碼發生修改時,會從新進行打包,而依賴的三方庫並不會從新打包,此時從新打包的業務代碼 app.js 體積也爲 3.49M

咱們能夠看到沒有變動的依賴包會走 304 協商緩存,而有變動的 app.js 的會從新請求而且由於體積比以前小,加載性能獲得了優化

速度優化

在前面的速度分析中咱們已經知道了打包速度主要耗費在 loader 的處理上

很顯然在開發過程當中進行 webpack 緩存是極其有必要的,咱們在處理樣式文件和 js 文件的 loader 以前添加 cache-loader 將結果緩存到磁盤中,能夠顯著提高二次構建速度

module.exports = {
  module: {
    rules: [
      {
        test: /\.js$/,
        use: ['cache-loader', ...loaders],
        include: path.resolve('src'),
      },
      {
        test: /\.scss$/,
        use: ['cache-loader', ...loaders],
        include: path.resolve('src'),
      },
    ],
  },
};
複製代碼

加入緩存後咱們能夠看到打包速度有了顯著的提高

進行優化後咱們能夠看到:優化後入口包體積縮小 42%,打包速度從 51s 提高至 11s

Do More

wepack 的打包優化沒有固定的模式,須要咱們針對項目去進行分塊、拆包、壓縮等,常見的優化思路主要分爲四部分

  • 優化搜索時間,即開始打包時獲取全部的依賴模塊的時間
  • 優化解析時間,即根據配置的 loader 解析相應文件所花費的時間
  • 優化壓縮時間,即 wepack 對代碼進行優化壓縮所花費的時間
  • 優化二次打包時間,即從新打包時所花費的時間

在當前的生產構建時會使用 UglifyJsPlugin 來進行代碼壓縮,但這個插件是單線程的,壓縮時會將代碼先解析爲 AST 抽象語法樹,而後根據規則去分析和處理 AST, 最後再將處理後的 AST 還原爲 JS 代碼,這種涉及大量運算的操做都是很是耗時的。在 Webpack4 中內置了 TerserPlugin 來處理 JS 代碼的壓縮,咱們能夠開啓多進程壓縮模式,能夠進一步優化咱們的打包速度

咱們對 chunks 進行代碼分割,但目前 app.js 在未壓縮的狀況下體積爲 3.49M,依然比較大,Webpack4 中 splitChunks 中提供了更爲豐富的配置規則,咱們能夠將代碼中公共的部分抽取出來,以及異步加載的模塊進行抽取,這樣也能夠進一步優化代碼體積

考慮到項目的穩定性,咱們將延後進行 webpack 的升級改造。

以上是本次基於 webpack 優化項目開發體驗的小結。瞭解 webpack 的打包原理,使用 webpack 新特性,必定能夠給咱們帶來更佳的開發體驗。

相關文章
相關標籤/搜索