avatar
threadsinstagram

Rspack / Rsbuild / Rsdoctor 入門

Table of Contents

前言

當我們在評估新專案中 bundler 的選型,或有年代感的複雜大型專案的 bundler 遷移時,用一個簡單基本的專案來實驗其中有疑慮或想確認的設定都是最有效率的。以之前我想實驗 tree-shaking 設定為例,因為實驗設定時需要反覆嘗試重新打包,要是打包一次要五分鐘以上,那咖啡可能要泡很多杯。

因此用小專案做實驗,一方面能夠以好理解的方式學習,另一方面可以快速打包來 try & error。今天就試著從零啟一個新專案來實際入門一下 Rspack 這個工具,順便玩一玩它的生態系。

Rspack / Rsbuild 工具名詞定義

tool

以下這篇中雖然專案本身是 Rsbuild,但其底層的 bundler 是 Rspack,兩者在打包的設定上算是互相能參考但 Rsbuild 有做一些客製化而有所不同,關於 Rspack 有不同的設定方式會附在每個小節最下方,但這篇文章介紹時會以 Rsbuild 為主。

另外這篇文章的實驗以 macOS 執行,看文件有些地方提到 Windows 系統在部份設定上會需要使用 corss-env,有需要的讀者可以再另外參考文件

啟個新專案

參考文件環境建議使用 Node.js 高於 v16 的 LTS 版本,我自己用的是 v20.17.0。這裡可以選擇要只用 bundler 部份的 Rspack,或是連上層 dev server 都有的 Rsbuild。

這裡我選擇 Rsbuild 來測試,可直接用 CLI 工具快速建一個新專案:

$ pnpm create rsbuild@latest
 
  Create Rsbuild Project
  Project name or path
  hello-rsbuild
  Select framework
  React
  Select language
  TypeScript
  Select additional tools (Use <space> to select, <enter> to continue)
  Add Biome for code linting and formatting, Add ESLint for code linting, Add Prettier for code formatting
  Next steps ────╮

  cd hello-rsbuild
  pnpm i
  pnpm run dev

├────────────╯

  Done.

💡 或懶得從頭建立環境也可以考慮用 rspack-examples 這個 codebase 來找到適合的模板,以下為學習用途試著純手工一個一個加上去藉此熟悉一下基礎建設怎麼做。

建立好專案後用 VS Code 開啟專案,並做一些初始化後嘗試 run 起來:

$ pnpm i
$ pnpm run dev

成功的話會自動開啟網頁看到像下面的圖:

demo-1

導入各種基礎建設

其實官方文件算是蠻好懂的,這裡簡單用幾個基礎建設體驗一下:

  • CSS 相關語法
    • CSS Modules
    • CSS 預處理器
    • 什麼是 Lightning CSS?
  • 使用 TypeScript 的 plugin 做型別檢查

CSS 相關語法

文件上看起來 Rspack 底層預設支援最新版的 Lightning CSS 來提昇打包效率,但也提供彈性兼容過往的 PostCSS、各種 Webpack plugin 等。

CSS Modules

開箱即用的預設支援,不需要加設定,預設會自動識別 [name].module.css 與各種 CSS 預處理器副檔名如 [name].module.scss 為 CSS Modules。

CSS 預處理器

這裡用 Sass,其他版本可以參考文件

$ pnpm add @rsbuild/plugin-sass -D

調整設定檔案 rsbuild.config.ts 加上 Sass 的 plugin 讓 bundler 可以識別:

// rsbuild.config.ts
import { defineConfig } from '@rsbuild/core';
import { pluginReact } from '@rsbuild/plugin-react';
import { pluginSass } from '@rsbuild/plugin-sass';
 
export default defineConfig({
  plugins: [pluginReact(), pluginSass()],
});

實際測試一下,把原本 src 底下的 App.css 改成 App.module.scss,並調整 .content 內容為巢狀:

// src/App.module.scss
body {
  margin: 0;
  color: yellow;
  font-family: Inter, Avenir, Helvetica, Arial, sans-serif;
  background-image: linear-gradient(to bottom, #020917, #101725);
}
 
.content {
  display: flex;
  min-height: 100vh;
  line-height: 1.1;
  text-align: center;
  flex-direction: column;
  justify-content: center;
 
  h1 {
    font-size: 3.6rem;
    font-weight: 700;
  }
 
  p {
    font-size: 1.2rem;
    font-weight: 400;
    opacity: 0.5;
  }
}

原本的 src/App.tsx 也調整一下:

// src/App.tsx
import STYLES from './App.module.scss';
 
const App = () => {
  return (
    <div className={STYLES.content}>
      <h1>Rsbuild with React</h1>
      <p>Start building amazing things with Rsbuild.</p>
    </div>
  );
};

存檔後可以看到畫面字體有變成黃色,且可以看到有帶上 CSS Module 的 hash:

demo-2

💡 補充:什麼是 Lightning CSS?

文件也有提到 Rspack 底層預設會用 Lightning CSSloader 來轉譯與打包現代 CSS 程式 ,其實之前在看 Vite 時也一直看到有在實驗性支援 Lightning CSS,趁這個機會了解一下。

Lightning CSS

簡單說的話 Lightning CSS 是一套用 Rust 編寫的 CSS 綜合工具,可以拿來做 CSS 的解析 (parser)轉譯 (transformer)壓縮 (minifier)打包 (bundler)

在解析與轉譯的方面,它會去讀取專案中的 browserslist 幫忙把專案中比較現代的 CSS 語法,在打包時轉譯成想要支持的瀏覽器可以看懂的語法,並自動補上各款瀏覽器需要的前綴像是 -webkit--moz- 等,就像以前 PostCSS 的 autoprefixer 在做的事。如此可以讓你在開發時放心使用各種現代的 CSS 語法。

parcel

在做為壓縮與最小化方面,可以看到這個效能比較表,最右邊的 @parcel/css - 1.0.1 是 Lightning CSS,可以看到勝過 CSSNano 與 esbuild。話說也是看到這張表才發現原來 Lightning CSS 的核心團隊跟 Parcel 是同一組人。

另外回到正題,如果在使用 Rspack 時發現 Lightning CSS 太新在舊專案沒辦法兼容,也可以參考文件這段方式關掉:

// rsbuild.config.ts
import { pluginCssMinimizer } from '@rsbuild/plugin-css-minimizer';
 
export default {
  plugins: [pluginCssMinimizer()],
  tools: {
    lightningcssLoader: false,
  },
};

使用 TypeScript 做型別檢查

剛剛啟專案時有選要啟用 TypeScript,所以相關設定都做好了。但比較需要另外設定的,文件上有提到啟 server 時 Rspack 底層的 SWC 並沒有幫忙做型別檢查,關於這點來實驗下。

新增一個型別檔案在 src 底下:

// src/types.ts
interface Author {
  name: string;
  email: string;
  url: string;
}
 
export type { Author };

調整 App.tsx

// src/App.tsx
import STYLES from './App.module.scss';
import type { Author } from './types';
 
const App = () => {
  const author: Author = {
    name: 'codefarmer',
    email: 'codefarmer.tw@gmail.com',
    // url: 'https://codefarmer.tw',
  };
 
  return (
    <div className={STYLES.content}>
      <h1>Rsbuild with React</h1>
      <p>Start building amazing things with Rsbuild.</p>
      <p>Author: {author.name}</p>
    </div>
  );
};

當上面試著把 url 這個屬性註解掉時,會看到 IDE 會提示有型別錯誤:

error

但此時 terminal 上是沒有任何報錯的,所以為了安全起見要用 @rsbuild/plugin-type-check 加一下設定:

$ pnpm add @rsbuild/plugin-type-check -D

一樣調整 rsbuild.config.ts 設定檔,跟上面的 Sass 一樣也是加入 plugin:

// rsbuild.config.ts
import { pluginTypeCheck } from '@rsbuild/plugin-type-check';
 
export default defineConfig({
  plugins: [pluginReact(), pluginSass(), pluginTypeCheck()],
});

加上後能正確看到型別檢查的報錯就成功了:

start   Compiling...
ready   Compiled in 2.52 s (web)
Type Error in ./src/App.tsx:5:9
TS2741: Property 'url' is missing in type '{ name: string; email: string; }' but required in type 'Author'.
    3 |
    4 | const App = () => {
  > 5 |   const author: Author = {
      |         ^^^^^^
    6 |     name: 'codefarmer',
    7 |     email: 'codefarmer.tw@gmail.com',
    8 |     // url: 'https://codefarmer.tw',

執行打包試試

可以直接執行以下指令,這個是目前專案中 rsbuild build 的 alias:

$ pnpm run build

執行後可以看到能正確將打包的檔案放到 dist 資料夾下方:

> rsbuild build
 
info    Type checker is enabled. It may take some time.
 web ━━━━━━━━━━━━━━━━━━━━━━━━━ (100%) emitting after emit
 
  File                                    Size        Gzipped
  dist/static/css/index.43c217db.css      0.37 kB     0.26 kB
  dist/index.html                         0.37 kB     0.25 kB
  dist/static/js/index.ccf11837.js        1.6 kB      0.86 kB
  dist/static/js/lib-react.c79a76de.js    140.2 kB    45.0 kB
 
  Total size:  142.5 kB
  Gzipped size:  46.4 kB

套用舊版 Webpack 的套件分析工具

webpack-bundle-analyzer-1

如果你曾經嘗試在使用 Webpack 做為 bundler 的專案上分析過安裝套件的大小分佈,應該會看過上面這張圖,這個正是 webpack-bundle-analyzer 這個 plugin 幫忙做的工作,它會在打包後輸出一份如上圖的 HTML 報告檔,可供開發者分析是否有能夠減少的打包成本。

而身為 Webpack 兼容高手的 Rsbuild,裡面也有內建引入,但預設在打包時是關閉的,如果需要啟用的話參考文件可在設定中加入這段來開啟:

// rsbuild.config.ts
const environment = process.env.NODE_ENV;
const isDev = environment === 'development';
 
export default defineConfig({
  performance: {
    bundleAnalyze: {
      // 輸出成靜態 HTML 檔案
      analyzerMode: isDev ? 'disabled' : 'static',
 
      // 輸出報告檔名
      openAnalyzer: !isDev,
 
      // 輸出報告檔名
      reportFilename: `report-${environment}.html`,
    },
  },
});

這裡如果沒有另外根據 development 調整的話,會在 pnpm run dev 時也開啟,但一般來說習慣上打包分析會想在 production mode 才看。調整設定如上後,再試著執行 pnpm run build,應該會自動開啟網頁顯示如下圖:

webpack-bundle-analyzer-2

這裡因為第三方套件目前只有用上 React,所以看不出像上面影片那麼密密麻麻,隨著之後套件越裝越多且有在 source code 中用上就會顯示在這裡。如果想實驗看看的話可以試試 pnpm add moment lodash 安裝幾個套件後,嘗試在 App.tsx 隨意引入重 build 應該可以看到如下的效果:

webpack-bundle-analyzer-3

💡 如果是 Rspack 的話內建也有裝,只是開啟的部份更單純,只要使用 rspack build —analyze 指令就可以 (ref)

Rsdoctor

rsdoctor

基本安裝

上面講了以前在 Webpack 常使用的打包分析工具,就不能不提提其實在 Rspack 生態系中也有另外實作一個稱為 Rsdoctor 的工具專門拿來做視覺化的構建分析。這裡也參考文件來試玩一下:

$ pnpm add @rsdoctor/rspack-plugin -D

可以將以下 script 加到 package.json 中,因為文件上有警告目前此工具因為還在開發中,不建議被使用在 production build 中,因此先加在 dev 環境來測試:

{
  "scripts": {
    "dev:rsdoctor": "RSDOCTOR=true rsbuild dev",
  }
}

試著執行看看指令:

$ pnpm run dev:rsdoctor
 
... ...
 
info    Rsdoctor analyze server running on: http://localhost:8791/index.html

執行後會看到上面會自動 serve 起一個 Rsdoctor 分析頁面的網址,打開後應該能看到以下畫面:

rsdoctor-1

可以看到其中有提供許多方便分析打包過程的資訊,像是每個 loader 與 plugin 執行花費時間、bundle size、打包警告與錯誤建議等等,詳細全部功能可以參考文件

試試 Rsdoctor 可以偵測什麼

有個好奇不知道它有沒有強大到可以建議我替換更好的套件,因此來實驗裝裝看這些比較 legacy 的套件:

$ pnpm add moment lodash
$ pnpm add -D @types/lodash

💡 一般來說在現代網頁開發時,會用 date-fnsday.js 來取代 moment,而使用 lodash-es 來取代 CJS 版本的 lodash,都是為了讓 bundle size 更減小,網頁載入速度更快。

因為這個專案是使用 TypeScript,所以也裝一下 @types/lodash 型別才不會報錯。再調整 src/App.tsx 如下將套件引入:

import STYLES from './App.module.scss';
import type { Author } from './types';
import _ from 'lodash';
import moment from 'moment';
 
const App = () => {
  const author: Author = {
    name: 'codefarmer',
    email: 'codefarmer.tw@gmail.com',
    url: 'https://codefarmer.tw',
  };
 
  return (
    <div className={STYLES.content}>
      <h1>Rsbuild with React</h1>
      <p>Start building amazing things with Rsbuild.</p>
      <p>Author: {author.name}</p>
      <p>lodash/has: {_.has({ a: 1 }, 'a') ? 'true' : 'false'}</p>
      <p>moment: {moment().format('YYYY-MM-DD HH:mm:ss')}</p>
    </div>
  );
};

重新執行 pnpm run dev:rsdoctor 後再看看警告與 bundle size 頁面,似乎沒有做任何提示:

rsdoctor-2

因為原本印象中有看到文件有提到,結果發現是我之前看文件這段時會錯意,它只是建議可以用這些套件分析工具來確認套件大小,例如可以試著將 moment 換成 day.js 這些優化方法,並不是代表 Rsdoctor 可以幫忙偵測。

嘗試調整設定

看文件其實在分析頁面中也是可以顯示 Webpack 分析工具的瓦片圖,這裡來實驗開啟看看,先在設定中加上這段:

// rsbuild.config.ts
...
import { RsdoctorRspackPlugin } from '@rsdoctor/rspack-plugin';
 
const rsdoctorPlugin = new RsdoctorRspackPlugin({
  supports: {
    generateTileGraph: true,
  }
});
 
export default defineConfig({
  tools: {
    rspack: {
      plugins: [rsdoctorPlugin],
    },
  },
});

以上都調整完成後再實際執行一次 pnpm run dev:rsdoctor 試試應該可以看到如下圖的效果:

rsdoctor-3

Chunk Splitting

前面在《esbuild 是什麼?code splitting 是什麼?》這篇時有提到 esbuild 在 code splitting 的方面做的不完全,也因此 Rspack 團隊才決定重新打造這個工具。

因此如果看到文件關於將程式碼切分為小塊的這段說明中,會看到 Rsbuild 中甚至直接抽成一個特製的 performance.chunkSplit 設定,提供各種切塊策略直接根據需求開箱即用:

  • split-by-experience:自動將常用的 npm 套件拆為檔案大小適中的 chunk
  • split-by-module:每個 npm 套件對應一個 chunk,此方式有提到可能在沒使用 HTTP/2 時造成 request waterfall 的問題
  • split-by-size:另根據 minSizemaxSize 依照檔案大小切 chunk
  • all-in-one:全部都打包到同一個 chunk 中
  • single-vendor:將所有第三方套件包在同一個 chunk 中
  • custom:自訂設定

似乎多少能減少像學習 Webpack 中的 optimization.splitChunks 那樣複雜的設定的心智負擔。

以下來實驗其中 3 種試試。

split-by-experience

// rsbuild.config.ts
export default defineConfig({
  performance: {
    chunkSplit: {
      strategy: 'split-by-experience',
    }
  }
});
split-by-experience

split-by-module

// rsbuild.config.ts
export default defineConfig({
  performance: {
    chunkSplit: {
      strategy: 'split-by-module',
    },
  },
});
split-by-module

split-by-size

// rsbuild.config.ts
export default defineConfig({
  performance: {
    chunkSplit: {
      strategy: 'split-by-size',
      maxSize: 50000,
      minSize: 10000,
    },
  },
});
split-by-size

可以看到不同的切塊策略切出來的顆粒度也不同,可以依據自己的需求與使用環境決定能在權衡之後採取什麼切法。而也在使用這樣的套件分析工具後,我們可以更視覺化地看出在這個例子中故意去引入的兩個大套件 momentlodash 都是可被優化去改用 dayjslodash-es 來減少 bundle size。

💡 Rspack 關於 code splitting 的部份也有說明,看起來連文件都以 CC BY 4.0 參考 Webpack 文件,應該是目標在完全兼容。

今日程式碼

今天示範的程式碼也放在 GitHub 上,有興趣的讀者可以參考看看。

小結

今天利用 Rsbuild 入門專案實際測試了幾個打包設定、打包分析工具,試著引入 Rspack 生態系中的 Rsdoctor 來幫忙分析打包過程中的狀況,其中用上了許多圖表與視覺化資訊,比起過往 Webpack 生態系中單純只用 webpack-bundle-analyzer 看,可以感覺到現代工具真的是進步許多。

另外也看了前面 Rspack 生態號稱比 esbuild 厲害的 code splitting 特性,提供多個開箱即用的切塊策略,目前初步使用起來的開發體驗非常順暢,文件看起來沒有難讀難設定的,只是有許多部份如果要能與 Webpack 完全兼容應該還會需要再等等。

希望這篇的 Rspack / Rsbuild / Rsdoctor 入門文件導讀與實驗能讓在觀望的讀者們一點幫助,如果有看不懂或有寫錯的地方再麻煩留言告訴我,感謝你的閱讀!