webpack 性能優化


現狀

隨着項目不斷發展壯大,組件數量開始變得越來越多,項目也開始變得龐大,webpack 編譯的時間也會越來越久,我們現在的項目編譯一次在 40s ——70s 之間,這是一個效率非常低下的操作。優化的手段有很多,之前項目原本已經做了很多,本文從緩存的角度進行優化講解

以下僅介紹幾種緩存相關的優化手段,包括

  • babel-loader 的 cacheDirectory
  • cache-loader
  • dll 動態鏈接庫
  • HardSourceWebpackPlugin

先說結論,第一個是項目中已有的,第二第三個效果不大,第四個達到了預期的效果

我們的 webpack 版本:4.41.2,系統:mac os

瓶頸分析

優化的第一步,應該是分析目前的性能,這里我們使用 speed-measure-webpack-plugin 進行速度分析

// 安裝
npm install --save-dev speed-measure-webpack-plugin
// 使用方式
const SpeedMeasurePlugin = require("speed-measure-webpack-plugin");
 
const smp = new SpeedMeasurePlugin();
 
const webpackConfig = smp.wrap({
  plugins: [
    new MyPlugin(),
    new MyOtherPlugin()
  ]
});

結果類似如下,可以看到每一個 Loader 以及 Plugin 的耗時,有了這個,我們就可以“對症下葯”

 

但需要注意的是:HardSourceWebpackPlugin 和 speed-measure-webpack-plugin 不能一起使用,這一點讓我郁悶了很久

babel-loader 的 cacheDirectory

babel-loader 允許使用 Babel 和 webpack 轉譯 JavaScript 文件,有時候如果我們運行 babel-loader 很慢的話,可以考慮確保轉譯盡可能少的文件。你可能使用 /\.m?js$/來匹配,這樣有可能去轉譯 node_modules 目錄或者其他不需要的源代碼,導致性能下降

可以通過 exclude 排除掉一些不需要編譯的文件。比如下面就不會去轉義 node_modules bower_components 文件夾下面的內容

module: {
  rules: [
    {
      test: /\.m?js$/,
      exclude: /(node_modules|bower_components)/,
      use: {
        loader: 'babel-loader',
        options: {
          presets: ['@babel/preset-env'],
          plugins: ['@babel/plugin-proposal-object-rest-spread']
        }
      }
    }
  ]
}

你也可以通過使用 cacheDirectory 選項,將 babel-loader 提速至少兩倍。這會將轉譯的結果緩存到文件系統中。cacheDirectory 默認值為 false。當有設置時,指定的目錄將用來緩存 loader 的執行結果。之后的 webpack 構建,將會嘗試讀取緩存,來避免在每次執行時,可能產生的、高性能消耗的 Babel 重新編譯過程(recompilation process)。如果設置了一個空值 (loader: 'babel-loader?cacheDirectory')或者 true (loader: 'babel-loader?cacheDirectory=true'),loader 將使用默認的緩存目錄node_modules/.cache/babel-loader,如果在任何根目錄下都沒有找到 node_modules 目錄,將會降級回退到操作系統默認的臨時文件目錄。

{
  test: /\.js$/,
  use: 'babel-loader?cacheDirectory',
  include: [resolve('src'), resolve('test') ,resolve('node_modules/webpack-dev-server/client')]
}

cache-loader

除了 babel-loader,如果我們想讓其他的 loader 的處理結果也緩存,該怎么做呢?

答案是可以使用 cache-loader。在一些性能開銷較大的 loader 之前添加 cache-loader,以便將結果緩存到磁盤里

安裝

npm install --save-dev cache-loader

配置

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

⚠️ 請注意,保存和讀取這些緩存文件會有一些時間開銷,所以請只對性能開銷較大的 loader 使用此 loader

除了默認的配置,cache-loader 提供了其他一些選項,詳見 cache-loader[1]

dll 的緩存方案

什么是 DLL?

DLL 文件為動態鏈接庫,在一個動態鏈接庫中可以包含給其他模塊調用的函數和數據

為什么要用 DLL?

原因在於包含大量復用模塊的動態鏈接庫只需要編譯一次,在之后的構建過程中被動態鏈接庫包含的模塊將不會在重新編譯,而是直接使用動態鏈接庫中的代碼。由於動態鏈接庫中大多數包含的是常用的第三方模塊,例如 Vue react、react-dom,只要不升級這些模塊的版本,動態鏈接庫就不用重新編譯

如何使用?

要完成下面三步:

  • 抽離。把網頁依賴的基礎模塊抽離出來,打包到一個個單獨的動態鏈接庫中去。一個動態鏈接庫中可以包含多個模塊
  • 獲取。當需要導入的模塊存在於某個動態鏈接庫中時,這個模塊不能被再次被打包,而是去動態鏈接庫中獲取
  • 加載。頁面依賴的所有動態鏈接庫需要被加載

之前使用 DllPlugin 和 DllReferencePlugin 完成,但是其配置非常復雜,而且假如更新了文件,還需要手動重新生成 dll。這里選擇了 AutoDllPlugin[2],它會自動完成以上兩個插件的功能,這是 Vue-cli 曾經用過的一個插件

安裝:

webpack 4

npm install --save-dev autodll-webpack-plugin

webpack 2 / 3

npm install --save-dev autodll-webpack-plugin@0.3

基礎使用:

plugins: [
  new HtmlWebpackPlugin({
    inject: true,
    template: './src/index.html',
  }),
  new AutoDllPlugin({
    inject: true, // will inject the DLL bundles to index.html
    filename: '[name].js',
    entry: {
      vendor: [
        'react',
        'react-dom'
      ]
    }
  })
]

優化前

 

 

優化后

第一次編譯:

 

 

第二次編譯:

 

 

優化了幾s,成效不大

之所以成效不大,是因為 webpack4 的性能是足夠優秀的了,Vue-cli 也廢除了這個功能

HardSourceWebpackPlugin

安裝:

npm install --save-dev hard-source-webpack-plugin
# or
yarn add --dev hard-source-webpack-plugin

配置:

// webpack.config.js
var HardSourceWebpackPlugin = require('hard-source-webpack-plugin');

module.exports = {
  context: // ...
  entry: // ...
  output: // ...
  plugins: [
    new HardSourceWebpackPlugin()
  ]
}

優化前

 

 

可以看到,需要 50s

優化后

第一次啟動

 

 

第二次啟動

 

 

只需要 7 s,減少了 43 s,速度提升百分之八十左右。優化的目的達成!

熱更新速度

看到 issue 中提到了關於熱更新相關的,說是會慢一點,我利用我們的項目做了一些測試,下面是測試數據

優化前

js: 2443ms  1634ms 1844ms 2532ms 1443ms 1248ms

html: 1094ms 1232ms 1119ms 1490ms 1264ms

css: 1422ms 1186ms 1341ms  1562ms 1183ms

優化后

js: 2429ms 2436ms 2860ms 2528ms 1917ms 1487ms 1450ms 1450ms 1557ms 2198ms

html: 2855ms 1569ms 1400ms 1298ms 1204ms 1299ms 1578ms 1485ms 2028ms

css: 2035ms 1406ms 1415ms 1600ms 1773ms 1604ms

相比而言,有時候會慢了一些,但總體而言能夠接受。但也有了一些影響,所以項目中提高了兩個 npm script 命令,如果不希望開啟的話,可以直接 npm run dev:noCache

"scripts": {
  "dev": "webpack-dev-server --inline --progress --config build/webpack.dev.conf.js --cache=true",
  "dev:noCache": "webpack-dev-server --inline --progress --config build/webpack.dev.conf.js --cache=false"
}

 build/webpack.dev.conf.js 中

if (args.cache) {
  devConfig = merge(devConfig, {
    plugins: [new HardSourceWebpackPlugin()]
  })
}

再次強調:

HardSourceWebpackPlugin 和 speed-measure-webpack-plugin 不能一起使用

展望未來

webpack 5 已經發布,其中有一個很吸引人的功能——持久緩存(據說思想跟 HardSourceWebpackPlugin 是一致的)

通過 cache  緩存生成的 webpack 模塊和chunk,來改善構建速度。cache 會在開發模式被設置成 type: 'memory' 而且在生產模式中被禁用

module.exports = {
  cache: {
    // 1. 將緩存類型設置為文件系統
    type: 'filesystem',

    buildDependencies: {
      // 2. 將你的 config 添加為 buildDependency,以便在改變 config 時獲得緩存無效
      config: [__filename],

      // 3. 如果你有其他的東西被構建依賴,你可以在這里添加它們
      // 注意,webpack、加載器和所有從你的配置中引用的模塊都會被自動添加
    },
  },
};

總結

以上的探索,花費了筆者挺多的時間的,菜是原罪,還是要多積累。另外一個感慨就是前端的發展如此迅速,很多東西可能已經過時,唯有保持持續學習以及穩固基礎知識才是王道

 


文章就分享到這,歡迎關注“前端大神之路


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM