WebInfra 前端 @字節跳動 Web Infra
我們很高興地宣布 Rspack 1.5 已發布!
值得關注的變更如下:
新功能
其他
Rstack 進展
Barrel 檔案是一種常見的模組導出模式,它透過創建統一的入口檔案來重新導出多個模組,通常命名為 index.js
或 index.ts
,例如:
export { Button, Tab } from './components';
export * as utils from './utils';
export * from './hooks';
雖然這種模式簡化了模組導入的使用方式,但是在構建過程中會帶來性能問題:當導入 barrel 檔案中的某個模組時,Rspack 需要解析和構建 barrel 檔案依賴的所有模組,即使實際只使用了其中的一小部分。
為了解決這一問題,Rspack 1.5 引入了實驗性的 lazyBarrel 功能,它能夠自動識別無副作用的 barrel 檔案,對其中的重導出進行延遲構建優化,只在真正需要時才會解析和構建相關模組,從而顯著減少不必要的模組解析和構建開銷。這一優化對於包含大量 barrel 檔案的專案尤其有效,能夠帶來顯著的構建性能提升。
// rspack.config.mjs
export default {
experiments: {
lazyBarrel: true,
},
};
在實際測試中,開啟 barrel 檔案優化後,兩個不同規模的應用都獲得了明顯的構建性能提升:
指標 | 開啟前 | 開啟後 | 優化 |
---|---|---|---|
構建時間 | 1.47s | 1.19s | -20% |
模組路徑解析次數 | 39,675 次 | 20,071 次 | -49% |
模組構建次數 | 9,358 次 | 5,062 次 | -46% |
指標 | 開啟前 | 開啟後 | 優化 |
---|---|---|---|
構建時間 | 17.9s | 16.0s | -10% |
模組路徑解析次數 | 181,078 次 | 137,232 次 | -24% |
模組構建次數 | 38,046 次 | 29,405 次 | -23% |
我們已經在 Rsbuild 1.5 中默認開啟了 barrel 檔案優化,並計劃在 Rspack 1.6 中為所有專案默認啟用該功能。詳見 experiments.lazyBarrel 文檔。
此前,Rspack 使用檔案系統監聽器 watchpack 來監聽檔案變化。在實際使用中,我們發現 watchpack
存在性能瓶頸。例如,每次檔案變更都會重新創建實例,在大型專案中會消耗大量的 CPU 和記憶體資源(參見 #7490)。
為解決這一問題,我們基於 Rust 打造了原生的檔案系統監聽器,新的實現具有以下優勢:
你可以通過配置 experiments.nativeWatcher 來嘗試新版 watcher:
// rspack.config.mjs
export default {
experiments: {
nativeWatcher: true,
},
watchOptions: {
// Other watch options...
},
};
在先前的 Rspack 1.4 中,我們正式引入了 Wasm target 支援,這意味著 Rspack 可以在基於 WebContainers 的瀏覽器環境中運行,例如 StackBlitz。
現在你可以直接在任何現代瀏覽器中使用 Rspack了。新發布的 @rspack/browser 是專為純瀏覽器環境設計的版本,而無需依賴 WebContainers 或是特定平台。@rspack/browser
為 Web 專案的在線打包提供底層支援,提供了更輕量的問題復現、配置分享方案,並通過提供在線互動式 Demo 的方式幫助開發者入門和學習 Rspack。
@rspack/browser
提供的 API 和 @rspack/core
的 JavaScript API 是對齊的。在此基礎上,額外提供了為適配瀏覽器環境的特性和 API:
import { rspack, builtinMemFs } from '@rspack/browser';
// Write files to memfs
builtinMemFs.volume.fromJSON({
// ...project files
});
// Just like using JavaScript API of @rspack/core
rspack({}, (err, stats) => {
if (err || stats.hasErrors()) {
// ...
}
// Get output from memfs after bundling
const files = builtinMemFs.volume.toJSON();
});
目前 @rspack/browser
還處於實驗性階段,可能會引入不相容更新。未來我們將持續完善在線打包所需的能力,歡迎前往 Rspack Playground 進行體驗。
現在你可以直接使用 Rust 來擴展 Rspack 了!通過我們提供的倉庫模板,你能夠編寫自定義的 Rust plugin 和 Rust loader,並替換 Rspack 默認的原生 binding。
在 JavaScript 插件中,Rust 和 JavaScript 之間的數據傳遞和類型轉換會產生一定的性能開銷。通過定制 Rspack 的 binding,你的自定義代碼將直接與 Rspack Rust core 集成,消除了跨語言通信的開銷,同時保持對所有 Rspack JavaScript API 的支持。
這種方式適用於替換頻繁與 Rust 通信的 Hooks(如 compilation.hooks.processAssets
),以及計算密集型的自定義 loader,在這些場景下可以獲得更好的構建性能。
主要特性:
你可以使用 官方模板 快速開始,更多信息請參考 設計理念。需要注意的是,使用此方案會帶來額外的維護成本,建議僅在需要極致性能優化時使用。
在組織專案代碼時,我們通常會將常數集中管理,例如 constants.js
或者 TypeScript 專案中包含 enums 的 types.ts
檔案。
Rspack 引入了 experiments.inlineConst 和 experiments.inlineEnum 兩個實驗性功能,用於對常數進行跨模組內聯優化。這些優化可以幫助壓縮工具更加進行準確的靜態分析,消除無用代碼分支,從而進一步減小產物的體積。
inlineConst
能夠對模組圖中葉子節點模組中的常數進行跨模組內聯,例如以下示例:
// font-settings.js
export const bold = 0b001;
export const italic = 0b010;
// index.js
import { bold, italic } from './font-settings';
const fontStyle = {};
// MY_FONT is defined by DefinePlugin({ FONT: 0b001 })
if (MY_FONT & bold) fontStyle['font-weight'] = 'bold';
if (MY_FONT & italic) fontStyle['font-style'] = 'italic';
applyFont(fontStyle);
啟用 inlineConst
後,示例中的 if
分支能夠明確地被壓縮工具優化,生成更精簡的產物:
(() => {
'use strict';
let t = {};
t['font-weight'] = 'bold';
applyFont(t);
})();
詳見 experiments.inlineConst 文檔,該功能計劃在 v1.6 中默認啟用。
inlineEnum
會對 TypeScript 的 enums 進行跨模組內聯優化,工作原理與 inlineConst
類似。
// types.ts
export enum Kind {
A,
B,
}
// index.ts
import { Kind } from './types.ts';
console.log(Kind.A);
啟用 inlineEnum
後:
(() => {
console.log(0);
})();
需要注意的是,啟用 inlineEnum
後,Rspack 默認會內聯所有 enums。如果你希望只內聯 const enums,可以參考此 示例。
在 TypeScript 專案中,類型重導出是一種常見的模式:
// index.ts
export { MyType } from './types.ts';
// types.ts
export type MyType = {
name: string;
};
在之前的版本中,如果你在重導出類型時沒有添加 type
修飾符,Rspack 可能會拋出警告,例如 export 'MyType' (reexported as 'MyType') was not found
。
這是因為 Rspack 在解析模組時,採用獨立的方式處理每個模組,導致類型的導出(例子中的 MyType
)被錯誤識別為一個值而非類型,由於在 ./types.ts
中找不到相應的值導出,因此觸發了上述警告。
Rspack 1.5 引入了 experiments.typeReexportsPresence 配置,用於改進對 Typescript 類型導出的識別。開啟此配置後,Rspack 能夠正確識別跨模組分析類型重導出,從而避免誤报警告。
在 Rspack 1.4 中,我們引入了自定義 InputFileSystem 功能,配合 webpack-virtual-modules
插件可以支持虛擬模組。然而,當虛擬模組數量較大時,該方式仍然存在性能瓶頸。
為了更好地支持虛擬模組,Rspack 1.5 新增了內建的 VirtualModulesPlugin,它基於 Rust 實現,將虛擬模組的存儲和管理遷移到 Rust 層,有效減少了模組讀取和解析的開銷,在處理大量虛擬模組時能夠保持更好的性能表現。
VirtualModulesPlugin
與 webpack-virtual-modules
保持了一致的 API 設計,以便你可以輕鬆地遷移:
// rspack.config.mjs
import { rspack } from '@rspack/core';
export default {
plugins: [
new rspack.experiments.VirtualModulesPlugin({
'src/generated/config.js': 'export default { version: "1.0.0" };',
}),
],
};
此前 Module Federation 的運行時是通過在入口模組打補丁的形式運行起來的。新版的 Module Federation 插件通過將自己的運行時代碼和 Rspack 的運行時代碼合併,同時將 Module Federation 運行時代碼提升到了運行時 chunk 中。這樣能在應用啟動前就將 Module Federation 的運行時提前準備好了。
這樣改動帶來如下收益:
下面是一個演示專案在使用新版 Module Federation 插件所帶來的產物體積優化對比。
配置 | Before | After | 優化 |
---|---|---|---|
Multiple Entries (default) | 210kb | 210kb | 0% |
Multiple Entries + runtimeChunk: true | 210kb | 150kb | -29% |
Multiple Entries + runtimeChunk: 'single' | 210kb | 70kb | -67% |
關於本次 Module Federation 插件的改動詳情,請參閱 這裡。
自 1.4 以來,我們做了一系列優化來降低 Rspack 的安裝體積,安裝包大小從 Rspack 1.4.0 的 63.7MB
減少到了 Rspack 1.5.0 的 49.9MB
。
其中幾個效果顯著的優化點:
為了進一步優化安裝體積,我們集成了 自動體積檢查 到日常工作流中,持續關注該指標變化。
在構建性能方面,Rspack 1.5 針對 Seal 階段(代碼生成和優化的階段)進行了大量優化,通過優化數據結構、提升並行度、增加熱點代碼快取等手段,提升了大型專案的構建性能。得益於並行化優化,在多核機器上性能提升更為顯著。
以字節跳動的某大型應用為例,該專案包含約 40,000 個模組,整體 Seal 階段耗時降低約 50%,各主要階段均有顯著優化:
階段 | v1.4.0 | v1.5.0 | 優化 |
---|---|---|---|
Flag dependency exports | 394ms | 181ms | -54% |
Flag dependency usage | 1828ms | 454ms | -75% |
Code splitting | 2019ms | 777ms | -62% |
Bundle splitting | 1588ms | 712ms | -55% |
Module concatenation | 2645ms | 616ms | -76% |
Content hash calculation | 881ms | 404ms | -54% |
鑑於 Node.js 16 已於 2023 年 9 月 11 日停止維護,同時眾多社區 npm 包(如 webpack-dev-server
、css-loader
、sass-loader
等)也相繼停止了對 Node.js 16 的支援,為了降低維護開銷,Rspack 1.5 將不再支援 Node.js 16。
各包的 Node.js 版本要求變化:
包名 | v1.4 | v1.5 |
---|---|---|
@rspack/core | >=16.0.0 |
>=18.12.0 |
@rspack/cli | >=18.12.0 |
>=18.12.0 |
@rspack/dev-server | >=18.12.0 |
>=18.12.0 |
@rsbuild/core | >=16.10.0 |
>=18.12.0 |
⚠️ 這是一個破壞性變更。如果你目前使用的是 Node.js 16,需要升級到 Node.js 18.12.0 或更高版本才能使用 Rspack 1.5。
當前還使用 Node.js 16 的專案,請按照以下步驟進行升級:
為了讓 Rspack 使用者能夠更便捷地使用 Rspack 的模組解析功能,我們已將 rspack-resolver 集成到 Rspack 的 JavaScript API 中,它提供了類似 enhanced-resolve 的模組解析能力。
使用方法請參閱 Resolver API 文檔。
經過充分驗證,experiments.lazyCompilation 配置項已從實驗性特性升級為穩定特性,並移動到了 Rspack 配置頂層:
// rspack.config.mjs
export default {
- experiments: {
- lazyCompilation: true,
- },
+ lazyCompilation: true,
};
原有的
experiments.lazyCompilation
配置仍然可以繼續使用,但會打印一條廢棄警告。
Rspack 的 experiments.topLevelAwait 選項用於控制對 top level await 的支援,一直以來都是默認開啟的。經過觀察,我們發現沒有實際場景需要關閉 top level await 支援,因此我們決定廢棄這個選項,並計劃在 Rspack 2.0 中移除它,屆時將無法禁用 top level await 支援。
Rstack 是一個以 Rspack 為核心的 JavaScript 統一工具鏈,具有優秀的性能和一致的架構。
我們很高興地宣布 Rslint 的發布!
Rslint 是一個 TypeScript 優先的新一代 Linter,由 Go 編寫,並基於 typescript-go 提供的類型檢查能力。
它起源於 @auvred 開發的 tsgolint,並在此基礎上進行了擴展和優化。
Rslint 目前支持如下功能:
rslint --fix
一鍵修復代碼問題@typescript-eslint
規則typescript-eslint
測試套件,確保規則正確性Rslint 仍處於早期階段,我們正在積極開發更多功能和規則支持。歡迎大家嘗試使用,並給予寶貴的反饋,幫助我們一起打磨 Rslint!
開箱即用一直是 Rsbuild 的核心設計理念。在 Rsbuild 1.5 中,我們默認啟用了多項 Rspack 的最新特性,帶來更優秀的構建性能,包括:
將 Rsbuild 升級至最新版本後,上述特性將默認啟用,無需任何額外配置。
Rsbuild 1.5 還新增了 output.module 選項,用於輸出 ES modules 格式的構建產物。
目前該選項針對 Node.js bundles 提供了 ESM 格式支持,未來我們將繼續增加對 web 應用 ESM 格式的支持。
// rsbuild.config.ts
export default {
output: {
target: 'node',
module: true,
},
};
在 Rslib 0.12 版本中,我們在專案模板中集成了 Rstest 測試框架。如果需要,你可以使用 Rstest 來測試你的庫專案,通過統一的 Rstack 工具鏈進行開發和測試。
此外,我們正在積極設計並開發全新的 ESM 產物生成方案,旨在提供類似 esbuild 和 Rollup 的 ESM 產物質量,同時保持與 webpack 一致的 interop 行為以確保正確性。詳見 interop 測試。
Rspress 2.0 目前處於 beta 階段,開發工作接近尾聲,我們計劃在兩個月內發布正式版本。
最新 beta 版本新增了 Markdown 文本複製組件,方便使用者將文檔內容提供給大模型進行分析和處理,你可以在各個 Rstack 文檔站點體驗這個功能:
該功能基於 @rspress/plugin-llms 插件實現,自動生成符合 llms.txt 標準的檔案,使用方法請參考 @rspress/plugin-llms 文檔。
Rsdoctor 1.2 版本帶來了多項重要更新,新增了對聚合模組的精確分析能力,以及帶來全新的 Treemap 視圖。這些功能提升了構建產物分析的準確性和可視化體驗,可以幫助你更好地理解和優化專案的打包產物。
請查看 Rsdoctor 1.2 發布博客 了解更多。
經過兩個月的持續迭代和 10 多個版本的優化,Rstest 0.2 在功能和穩定性方面都有了顯著改進,帶來以下新特性: