我把祖傳專案的建置時間砍了 90%,主管以為我只是在「優化了一下」,結果隔壁組的 CI 都掛了來問我設定

本文不是教學,是一次「地下重構」的完整復盤。


事情是這樣的

公司有個三年前的 React 專案,Webpack 4 + Babel 7 + 一堆自訂 loader,建置時間穩定在 4 分 30 秒 左右。

每次改個文案,等熱更新要喝杯水。提個 PR,CI 跑完都能去一趟廁所再回來。

上週我實在是忍不了了。週五下午需求評審完,我 Jira 上只建了一張票:「優化建置設定」,估點 2sp

主管看了一眼:嗯,小優化,去吧。

他不知道的是,我週末在家幹了什麼。


週六:先搞清楚這 4 分半都在幹嘛

bash

ini 代碼解讀複製代碼# 先上正式 profiling
DEBUG=webpack* npm run build 2> webpack.log

然後寫了個腳本分析:

JavaScript

javascript 代碼解讀複製代碼// analyze-build.js
const fs = require('fs');
const log = fs.readFileSync('webpack.log', 'utf8');

// 提取每個 loader 的耗時
const loaderTimes = log.match(/(\d+)ms.*?loader/g) || [];
console.table(loaderTimes.map(s => {
  const [time, name] = s.match(/(\d+)ms.*?(\w+)-loader/).slice(1);
  return { loader: name, time: +time };
}).sort((a, b) => b.time - a.time));

結果讓我沉默了:

表格

Loader耗時備註babel-loader127s全量轉譯,包括 node_modules``ts-loader89s型別檢查跟編譯綁在一起sass-loader45s沒開 fiber,同步解析eslint-loader38s建置時全量 lint,包括第三方庫babel-loader 在轉譯 node_modules 裡的 lodash。

我盯著這個結果看了三分鐘。


週六晚上:第一刀,先砍最明顯的

1. 把 babel-loader 的 include 收緊

JavaScript

javascript 代碼解讀複製代碼// 之前:沒有 include,全量過 babel
{
  test: /.(js|jsx)$/,
  use: ['babel-loader']  // 127s
}

// 之後:只轉譯 src + 必要的 esm 套件
{
  test: /.(js|jsx)$/,
  include: [
    path.resolve(__dirname, 'src'),
    // 只轉譯那些發佈的是 esm 且需要相容的套件
    path.resolve(__dirname, 'node_modules/@company'),
    path.resolve(__dirname, 'node_modules/xxx-esm-pkg')
  ],
  use: ['babel-loader']
}

這一刀下去:127s → 34s。

但還不夠。ts-loader 的 89s 也很離譜。


2. 把型別檢查從建置流程裡拆出來

JavaScript

javascript 代碼解讀複製代碼// 之前:ts-loader 自己做型別檢查,阻塞編譯
{
  test: /.tsx?$/,
  use: [
    'babel-loader',  // 轉譯
    {
      loader: 'ts-loader',
      options: { transpileOnly: false } // 預設就是 false!
    }
  ]
}

// 之後:ts-loader 只負責轉譯,型別檢查交給 fork-ts-checker
{
  test: /.tsx?$/,
  use: ['babel-loader', 'ts-loader'] // ts-loader 預設 transpileOnly: false?
  // 不對,ts-loader 預設確實是 false,但我們可以顯式優化
}

實際上我換了思路:

JavaScript

yaml 代碼解讀複製代碼// webpack.config.js
const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin');

module.exports = {
  module: {
    rules: [{
      test: /.tsx?$/,
      use: [
        'babel-loader',
        { loader: 'ts-loader', options: { transpileOnly: true } }
      ]
    }]
  },
  plugins: [
    new ForkTsCheckerWebpackPlugin({
      typescript: { diagnosticOptions: { semantic: true, syntactic: true } }
    })
  ]
};

型別檢查移到子進程,不阻塞主建置流程。

這一刀:89s → 21s(ts-loader 部分),且熱更新不再等型別檢查。


3. sass-loader 開 fiber + 持久化快取

JavaScript

javascript 代碼解讀複製代碼{
  test: /.scss$/,
  use: [
    'style-loader',
    'css-loader',
    {
      loader: 'sass-loader',
      options: {
        implementation: require('sass'),
        sassOptions: { fiber: false } // sass 1.33+ 已棄用 fiber,但...
        // 實際上我升級了 sass-loader 並啟用了 webpack5 的持久化快取
      }
    }
  ]
}

這裡我直接上了 Webpack 5 的持久化快取

JavaScript

lua 代碼解讀複製代碼// webpack.config.js
module.exports = {
  cache: {
    type: 'filesystem',
    buildDependencies: {
      config: [__filename]
    }
  }
};

第二次建置:4 分 30 秒 → 38 秒。

冷啟動還有優化空間,但熱快取已經起飛了。


週日:我動了邪念

建置快了,但我看著 webpack.config.js 裡那坨三年前的設定,手癢了。

這專案當年是從 CRA eject 出來的,設定裡還有 eslint-loader(CRA 3 時代的產物)、url-loaderfile-loader 混用、optimize-css-assets-webpack-plugincssnano...

我打開文件看了一眼:Vite 5 已經穩定了。

但遷 Vite 風險太大,我不敢。而且 Jira 票上寫的是「優化建置設定」,不是「重構建置工具」。

所以我折中了一下:用 Rspack 做一次「無痛遷移」。

Rspack 是字節出的,Webpack API 相容,但用 Rust 重寫,號稱建置速度 5-10 倍。

我心想:反正都是 Webpack 設定,試試又不虧。

bash

sql 代碼解讀複製代碼npm install @rspack/core @rspack/cli --save-dev

然後把 webpack.config.js 改成 rspack.config.js,API 基本不用動:

JavaScript

css 代碼解讀複製代碼// rspack.config.js
const rspack = require('@rspack/core');

module.exports = {
  // 90% 的設定直接複製過來就能跑
  module: {
    rules: [
      // babel-loader 換成 @rspack/plugin-react
      {
        test: /.(js|jsx|ts|tsx)$/,
        use: {
          loader: 'builtin:swc-loader', // Rspack 內建 SWC,比 babel 快得多
          options: {
            jsc: {
              parser: { syntax: 'typescript', tsx: true },
              transform: { react: { runtime: 'automatic' } }
            }
          }
        }
      }
    ]
  },
  plugins: [
    new rspack.HtmlRspackPlugin({ template: './public/index.html' }),
    // 其他插件大部分都能直接用
  ]
};

跑了一下:

bash

 代碼解讀複製代碼npx rspack build

冷建置:28 秒。

有快取:8 秒。

我反覆確認了三遍,沒報錯,產物正常,Source Map 也在。


週一:我撒了個小謊

晨會上,主管問:「建置優化那件事做得怎麼樣了?」

我說:「嗯,改了一些 loader 設定,建置快了一點。」

主管:「好,繼續推進需求吧。」

我點點頭,把 rspack.config.js commit 上去,PR 標題寫的是:

chore: optimize build config and enable persistent cache

程式碼裡確實也有 Webpack 5 快取的設定(我保留了雙份設定,Rspack 是主設定,Webpack 5 的作為 fallback),不算完全說謊。


週三:事情開始失控

PR merge 的第二天,我收到一條飛書訊息:

隔壁組後端 @我:「你們組 CI 怎麼跑這麼快?我們前端專案建置要 6 分鐘,能參考下你們的設定嗎?」

我還沒回,又一條:

B 業務線前端負責人:「聽說你們建置優化了?能把 rspack.config.js 發我看看嗎?」

然後架構群開始有人 @我:

「xxx 你們那個 Rspack 遷移有文件嗎?我們組也想試試。」

我慌了。

因為我 根本沒有寫遷移文件。那個 rspack.config.js 是我週末邊試邊改的,裡面還有幾行我自己都忘了幹嘛的註解:

JavaScript

arduino 代碼解讀複製代碼// TODO: 這個 plugin 好像不加也行?先留著
// FIXME: 生產環境這裡可能有問題,週末再測

而且最尷尬的是:我們的 CI 設定還是用的 webpack 命令,Rspack 是我本地開發用的。

也就是說,CI 上跑的還是優化後的 Webpack 5(28 秒左右),本地開發用的是 Rspack(8 秒)。

但隔壁組以為我全鏈路都切了。


週四:被迫寫「文件」

下午被拉進了一個臨時會議,標題是「《前端建置優化經驗分享》」。

參會人:3 個業務線的前端負責人 + 架構組 + 我的主管。

主管看著我:「你優化得不錯啊,給大家講講?」

我:"..."

然後我把週末幹的事全抖了:

  1. Webpack 5 持久化快取 + loader 範圍收緊
  2. ts-loader transpileOnly + fork-ts-checker
  3. 本地開發切 Rspack(CI 還是 Webpack 5)

架構組的人問:「CI 為什麼不一起切 Rspack?」

我說:「Rspack 的 copy-webpack-plugin 替代品有個 bug,生產環境我還沒測完...」

其實是我週末沒時間測了。

主管聽完,沉默了一下,說:「那你這週把 CI 也切了吧,寫個遷移文件,給其他組參考。」

我:"...好的。"


現在的情況

  • 我們組:本地 Rspack(8s),CI Rspack(15s),已穩定運行兩週
  • A 業務線:抄了我的設定,但遇到 SWC 和 babel-plugin-import 的相容問題,我來修的
  • B 業務線:直接上 Vite 了,說「反正都要遷,不如一步到位」
  • C 業務線:還在 Webpack 5,但用我的優化方案,建置從 5 分鐘降到 40 秒

我的 Jira 票還是 2sp,狀態「已完成」。

但我的飛書簽名改成了: 「建置優化諮詢請先發紅包。」


一些正經的總結

表格

優化手段收益風險Webpack 5 持久化快取二次建置 10x 提升快取失效策略要配好loader include 收緊減少 60%+ 無效編譯要確認哪些套件需要轉譯ts-loader + fork-ts-checker編譯和型別檢查並行型別錯誤不會阻塞建置,需配 CI 檢查Rspack/SWC冷建置 5-10x 提升部分 babel plugin 不相容,需逐個驗證---

最後

重構這件事,有時候不需要「立項評審」、「技術方案評審」、「排期」。

你只需要:

  • 一個忍不了的痛點
  • 一個不用加班的週末
  • 一張只寫「優化一下」的 Jira 票

然後,等同事來找你要程式碼。


你有過類似的「地下重構」經歷嗎?留言區聊聊。


如果本文對你有幫助,歡迎按讚收藏。如果建置優化遇到問題,可以留言,我看到了會回(但不一定及時,因為可能在幫隔壁組修 CI)。


原文出處:https://juejin.cn/post/7655348005602361363


精選技術文章翻譯,幫助開發者持續吸收新知。

共有 0 則留言


精選技術文章翻譯,幫助開發者持續吸收新知。
🏆 本月排行榜
🥇
站長阿川
📝13   💬2   ❤️1
573
🥈
我愛JS
📝1   ❤️1
71
評分標準:發文×10 + 留言×3 + 獲讚×5 + 點讚×1 + 瀏覽數÷10
本數據每小時更新一次
📢 贊助商廣告 · 我要刊登