webpack3.x升級4.x以後打包速度優化

前言

上一篇咱們講到,webpack3.x 升級 4.x 後打包大小優化,今天講一下 webpack 4.x(webpack 4.43.0) 的打包速度優化,其實在升級了 webpack4 以後對於打包速度就已經有了很大的提高,可是查找時間(縮小範圍)、loader 處理時間(多進程)、二次打包時間(緩存)仍有可優化的空間。css

打包分析

在優化以前咱們須要清楚項目打包的性能狀況,這裏咱們使用 speed-measure-webpack-plugin 插件來進行分析html

webpack.base.jsvue

+const SpeedMeasurePlugin = require('speed-measure-webpack-plugin');

+const smp = new SpeedMeasurePlugin({
+ outputFormat: 'humanVerbose'
+});

const webpackConfig = merge(baseWebpackConfig, {
  // ..
});

-module.exports = webpackConfig;
+module.exports = smp.wrap(webpackConfig);
複製代碼

打包看下,本次耗時 62,361 ms,列出了 PluginsLoaders 具體耗時的細節:node

圖比較長可是基本能看出,其中耗時較多的是 vue-loaderts-loaderwebpack

查找時間優化-exclude/include

webpack 從入口文件開始,根據依賴關係查找模塊,咱們要儘量少的處理模塊,最多見的就是排除 exclude: /node_modules/excludeinclude 同時使用時 exclude 優先級更高git

webpack.base.jsgithub

module.exports = {
  module: {
    rules: [
      {
        test: /\.vue$/,
        loader: "vue-loader",
      },
      {
        test: /\.js$/,
        loader: "babel-loader",
        // 確保 node_modules 中 Vue 單文件組件的 <script> 部分能被轉譯
        exclude: (file) => /node_modules/.test(file) && !/\.vue\.js/.test(file),
      },
      {
        test: /\.tsx?$/,
        loader: "ts-loader",
        exclude: /node_modules/,
        options: {
          // 禁用類型檢查,能夠經過插件的方式使用
          transpileOnly: true,
          experimentalWatchApi: true,
          appendTsSuffixTo: [/\.vue$/],
        },
      },
    ],
  },
};
複製代碼

ts 類型檢查,能夠單獨使用插件: ForkTsCheckerWebpackPluginweb

loader 處理時間優化-thread-loader

Each worker is a separate node.js process, which has an overhead of ~600ms. There is also an overhead of inter-process communication. 每一個 worker 都是一個單獨的 node.js 進程,其開銷約爲 600 毫秒。進程間通訊也有開銷。查看更多npm

webpack.base.js緩存

// ...
const isProduction = process.env.NODE_ENV === "production";

if (isProduction) {
  const threadLoader = require("thread-loader");
  // 預熱 worker
  threadLoader.warmup(
    {
      // pool options, like passed to loader options
      // must match loader options to boot the correct pool
    },
    [
      // modules to load
      // can be any module
      "babel-loader",
      "ts-loader",
      "vue-loader",
      "sass-loader",
    ]
  );
}

module.exports = {
  // ...
  module: {
    rules: [
      {
        test: /\.vue$/,
        use: isProduction ? ["thread-loader", "vue-loader"] : ["vue-loader"],
      },
      {
        test: /\.js$/,
        use: isProduction
          ? ["thread-loader", "babel-loader"]
          : ["babel-loader"],
        exclude: (file) => /node_modules/.test(file) && !/\.vue\.js/.test(file),
      },
      {
        test: /\.tsx?$/,
        loader: "ts-loader",
        exclude: /node_modules/,
        use: isProduction
          ? [
              "thread-loader",
              {
                loader: "ts-loader",
                options: {
                  happyPackMode: true,
                  transpileOnly: true,
                  experimentalWatchApi: true,
                  appendTsSuffixTo: [/\.vue$/],
                },
              },
            ]
          : [
              {
                loader: "ts-loader",
                options: {
                  transpileOnly: true,
                  experimentalWatchApi: true,
                  appendTsSuffixTo: [/\.vue$/],
                },
              },
            ],
      },
      {
        test: /\.s?css$/,
        use: isProduction
          ? [
              MiniCssExtractPlugin.loader,
              "css-loader",
              {
                loader: "thread-loader",
                options: {
                  workerParallelJobs: 2,
                },
              },
              "sass-loader",
            ]
          : ["vue-style-loader", "css-loader", "sass-loader"],
      },
    ],
  },
};
複製代碼

與 ts-loader 使用時報錯

ERROR in ./src/main.ts
Module build failed (from ./node_modules/thread-loader/dist/cjs.js):
Thread Loader (Worker 0)
Cannot read property 'errors' of undefined
    at PoolWorker.fromErrorObj (/Users/yourProjectPath/node_modules/thread-loader/dist/WorkerPool.js:262:12)
    at /Users/yourProjectPath/node_modules/thread-loader/dist/WorkerPool.js:204:29
    at mapSeries (/Users/yourProjectPath/node_modules/neo-async/async.js:3625:14)
    at PoolWorker.onWorkerMessage (/Users/yourProjectPath/node_modules/thread-loader/dist/WorkerPool.js:170:35)
    at successfulTypeScriptInstance (/Users/yourProjectPath/node_modules/ts-loader/dist/instances.js:119:28)
    at Object.getTypeScriptInstance (/Users/yourProjectPath/node_modules/ts-loader/dist/instances.js:34:12)
    at Object.loader (/Users/yourProjectPath/node_modules/ts-loader/dist/index.js:17:41)
複製代碼

ts-loader 設置 happyPackMode: true查看更多

與 node-sass 使用

webpack 文檔有提到說 node-sass 搭配 thread-loader 使用的時候須要設置 workerParallelJobs: 2,親測發現如今不用這樣設置了,速度會快一些

thread-loader@2.1.3 node-sass@4.13.1 node@12.16.2

與 sass-loader 使用時報錯

報錯信息以下:

Module build failed (from ./node_modules/thread-loader/dist/cjs.js):
Thread Loader (Worker 3)
this.getResolve is not a function
複製代碼

暫時沒有解決方案,將 sass-loader 降級到 v7.3.1 能夠正常運行,相關 issue

二次打包時間優化-cache

使用 webpack4 二次打包的時候,咱們會發現比第一次快來很多,這是由於 webpack 內置 terser-webpack-plugin 來最小化 JS 文件,默認會啓用多進程和緩存,第二次的時候直接讀取項目下 node_modules/.cache/terser-webpack-plugin 目錄,因此較第一次打包會快上很多

一樣的咱們在使用 babel-loader 的時候,也能夠將緩存啓用,設置 cacheDirectory: true 便可

若是你想緩存其餘 loader 的處理結果,你可使用 cache-loader

總結

項目不夠大,服務器邏輯核數(物理核心 * 每一個核心的線程數)不夠多,不要使用多進程打包,反向優化說的就是我,使用 thread-loader 多進程打包後,在本地構建反而增長了近 40s 的時間,以下圖:

這篇文章拖了好久,半個多月才寫完,總結一下拖延的點:

  • 一是中途看到【Webpack】538- 打包速度提高指南這篇文章,以爲寫的很好,一度都不想開始優化了,以爲寫了沒別人寫得好,還寫出來幹啥。後來硬着頭皮優化完了,發現文章有過期(HappyPack)和冗餘(terser-webpack-plugin 默認會啓用 多進程)的部分,並且也沒有實踐的部分,頓時就以爲有了本身的(這篇文章)存在有了意義;
  • 二是發現優化完以後發現效果很差,在本地構建甚至反向優化,一度懷疑還有沒有寫的必要,好在在測試服務器上經過 CI/CD 構建速度中位數 50s 左右,還算能夠,抱着善始善終的態度寫完了

最後仍是提一下,worker 啓動和進程間通訊的開銷都不小,結合項目實際狀況使用,不要爲了用而用,項目質量比性能更重要。

參考連接

相關文章
相關標籤/搜索