引言
隨著前端項(xiàng)目規(guī)模和復(fù)雜度的增長,Monorepo(單倉庫管理多項(xiàng)目)的模式受到越來越多團(tuán)隊(duì)的青睞。如何有效地管理一個(gè)包含眾多子項(xiàng)目的 Monorepo,是開發(fā)者面臨的一大挑戰(zhàn)。Rush 是 Microsoft 開源的一款 Monorepo 管理工具,旨在為大型倉庫和團(tuán)隊(duì)提供高效的依賴管理和構(gòu)建支持 (Lerna vs Turborepo vs Rush: Which is better in 2023?)。本文將深入介紹 Rush 的概念、適用場景,并通過嚴(yán)謹(jǐn)?shù)倪壿嫹治銎鋬?yōu)勢和適用性。同時(shí),我們將對比 Rush 與其他常見 Monorepo 工具(如 Lerna、Nx)的優(yōu)缺點(diǎn),演示 Rush 的基本用法和高級特性,并提供示例代碼幫助讀者實(shí)踐。
Rush 是什么?
基本概念
Rush 是一個(gè)用于管理 JavaScript/TypeScript Monorepo 的工具,由 Microsoft 于 2016 年為 SharePoint Framework 開發(fā)并開源 (Lerna vs Turborepo vs Rush: Which is better in 2023?)。它在前端生態(tài)中充當(dāng)“統(tǒng)一調(diào)度器”(unified orchestrator)的角色,可以統(tǒng)一執(zhí)行安裝依賴、鏈接包、構(gòu)建項(xiàng)目、生成變更日志、發(fā)布組件以及版本號升級等任務(wù) (Rush)。簡單來說,如果一個(gè)倉庫下有多個(gè)相互依賴的 npm 包,Rush 能夠協(xié)調(diào)它們的依賴安裝和構(gòu)建順序,免去人工逐個(gè)處理的繁瑣。
在 JS/TS 生態(tài)中的角色
在傳統(tǒng)的多包管理方式下,開發(fā)者可能需要手動(dòng)運(yùn)行 npm install、npm link 和 npm run build 等命令來組裝各個(gè)子項(xiàng)目,并確保它們按正確順序執(zhí)行。這種方式在項(xiàng)目增多時(shí)非常低效,容易出現(xiàn)依賴遺漏或版本不一致的問題。例如,沒有使用 Monorepo 工具時(shí),開發(fā)者需要依次在每個(gè)包中安裝和構(gòu)建,并通過 npm link 將包彼此連接 (Setting up a new repo | Rush) (Setting up a new repo | Rush)。Rush 正是為了解決這些痛點(diǎn)而生:它通過集中管理,讓開發(fā)者只需一條命令就能完成上述所有步驟,自動(dòng)處理包之間的鏈接和構(gòu)建順序,從而提升開發(fā)效率并降低出錯(cuò)概率。
Rush 解決的問題
Rush 針對大型前端項(xiàng)目的常見難題提供了解決方案:
依賴地獄與版本不一致:當(dāng)一個(gè)倉庫包含多個(gè)相互依賴的包時(shí),保持依賴版本一致非常重要。Rush 提供了倉庫級的依賴版本策略配置(如 common-versions.json),可強(qiáng)制所有子項(xiàng)目使用統(tǒng)一版本的依賴庫,避免因?yàn)榘姹静环麑?dǎo)致的問題。同時(shí),Rush 默認(rèn)使用 PNPM 作為包管理器,通過去重和符號鏈接機(jī)制消除“幽靈依賴”(未在當(dāng)前項(xiàng)目中聲明但由于其他包的依賴被錯(cuò)誤引用)等問題 (Rush)。這意味著每個(gè)子項(xiàng)目只能訪問自己聲明的依賴,增強(qiáng)了依賴管理的可靠性。
構(gòu)建順序與效率:在沒有工具支持時(shí),開發(fā)者需要人為確定構(gòu)建順序,以確保某些基礎(chǔ)庫先于依賴它的項(xiàng)目構(gòu)建。Rush 通過分析項(xiàng)目間依賴關(guān)系自動(dòng)確定正確的構(gòu)建順序,并支持并行構(gòu)建來利用多核 CPU 提升速度 (Rush)。對于未改變的項(xiàng)目,Rush 還支持跳過重復(fù)構(gòu)建(增量構(gòu)建),從而減少不必要的編譯工作。
發(fā)布與版本演進(jìn):大型 Monorepo 往往需要發(fā)布多個(gè) npm 包,并維護(hù)它們的版本號和變更日志。Rush 內(nèi)置了變更日志生成和版本管理功能,例如通過
rush change收集變更描述、rush version決定版本號遞增策略,最后使用rush publish一鍵發(fā)布更新的包。這些工具集成使得在一個(gè)倉庫中管理多包的發(fā)布流程更加可控。新成員上手與協(xié)作:Rush 提供了一系列機(jī)制幫助大型團(tuán)隊(duì)協(xié)作。例如,倉庫策略(Repo Policies)功能可以在有人引入新依賴時(shí)要求提交審核,避免隨意添加不受控的第三方庫 (Lerna vs Turborepo vs Rush: Which is better in 2023?)。又如,通過在倉庫配置中鎖定 Rush 本身的版本,每個(gè)開發(fā)者在自己環(huán)境下都會(huì)自動(dòng)使用倉庫指定的 Rush 版本,保證構(gòu)建環(huán)境的一致性 (Rush)。這些特性降低了大團(tuán)隊(duì)協(xié)作時(shí)的溝通和維護(hù)成本。
綜上,Rush 核心定位是為“大型、多包、多人協(xié)作”的前端項(xiàng)目提供一個(gè)高效穩(wěn)定的管理方案。它通過統(tǒng)一的指令和配置,解決了 Monorepo 模式下依賴混亂、構(gòu)建緩慢、版本難控等問題,為開發(fā)者提供更佳的開發(fā)體驗(yàn) (Rush)。
Rush 適用于哪些場景?
大型 Monorepo 項(xiàng)目
Rush 生來就是為大型代碼倉庫設(shè)計(jì)的。如果你的項(xiàng)目倉庫中包含幾十甚至上百個(gè)子包(Packages),并且這些包之間存在復(fù)雜的依賴關(guān)系,那么 Rush 能發(fā)揮最大的價(jià)值 (Rush)。在這種場景下,手工管理依賴和構(gòu)建幾乎是不可能完成的任務(wù),而 Rush 提供的并行構(gòu)建、子集構(gòu)建和增量構(gòu)建能力可以確保即便倉庫規(guī)模龐大,構(gòu)建過程依然高效可控 (Rush)。實(shí)際上,Rush 的開發(fā)初衷就是為了解決 Microsoft 內(nèi)部如 SharePoint、OneDrive 等擁有數(shù)百個(gè)組件的大型倉庫的構(gòu)建問題,因此非常適合此類大規(guī)模 Monorepo 項(xiàng)目 (Rush) (Lerna vs Turborepo vs Rush: Which is better in 2023?)。
團(tuán)隊(duì)協(xié)作和 CI/CD 環(huán)境
當(dāng)多個(gè)團(tuán)隊(duì)或開發(fā)者在同一個(gè)倉庫中協(xié)作開發(fā)時(shí),保持開發(fā)環(huán)境和構(gòu)建結(jié)果的一致性至關(guān)重要。Rush 在這方面提供了多種保障機(jī)制。比如,它可以為大型團(tuán)隊(duì)定制協(xié)作策略:新增依賴需要經(jīng)過審批,所有項(xiàng)目依賴版本統(tǒng)一,甚至不同子項(xiàng)目可以采用鎖步(Lockstep)或獨(dú)立(Independent)的版本策略以適應(yīng)不同發(fā)布需求 (Rush)。同時(shí),Rush 確保每次安裝和構(gòu)建都是確定性的,不會(huì)因?yàn)闄C(jī)器或環(huán)境不同而產(chǎn)生“此地可現(xiàn)彼地不現(xiàn)”的問題 (Rush)。對于 CI/CD,Rush 生成的配置(如 ci.yml)可以直接用于持續(xù)集成流程,在拉取代碼后自動(dòng)執(zhí)行安裝與構(gòu)建 (Setting up a new repo | Rush)。此外,Rush 支持將構(gòu)建產(chǎn)物緩存到云端,以便在 CI 中復(fù)用——每當(dāng)主分支構(gòu)建完成后將產(chǎn)物上傳,新拉取代碼的環(huán)境便可直接下載緩存,加速構(gòu)建 (Enabling the build cache | Rush)。這些特性使 Rush 非常適合在 CI/CD 流水線中使用,保證團(tuán)隊(duì)協(xié)作下構(gòu)建結(jié)果的一致和高效。
復(fù)雜的依賴管理
如果項(xiàng)目中存在錯(cuò)綜復(fù)雜的依賴關(guān)系(例如 A 項(xiàng)目依賴 B,B 又依賴 C,不同項(xiàng)目還可能引用不同版本的某個(gè)庫),Rush 提供的依賴管理功能尤其有用。通過 Rush 的“共享版本”策略(common-versions)可以強(qiáng)制特定庫在整個(gè)倉庫中使用同一版本,避免出現(xiàn)“同庫多版本”而導(dǎo)致冗余體積或潛在沖突。此外,Rush 結(jié)合 PNPM 的工作方式,會(huì)將所有依賴安裝到統(tǒng)一的 common/temp 目錄,再為每個(gè)項(xiàng)目建立獨(dú)立的 node_modules 符號鏈接。這種方式確保了項(xiàng)目間的隔離:一個(gè)包無法意外引用到另一個(gè)包的依賴,從根本上杜絕了經(jīng)典 npm/Yarn 工作區(qū)模式下可能出現(xiàn)的幽靈依賴問題 (Rush)。因此,對于依賴關(guān)系復(fù)雜且要求嚴(yán)格控制的倉庫,Rush 提供了一個(gè)干凈可靠的管理層。
需要注意的是,Rush 的強(qiáng)大也伴隨著一定的復(fù)雜性。如果你的倉庫規(guī)模較?。ɡ缰挥袔讉€(gè)包)或者團(tuán)隊(duì)規(guī)模不大,Rush 提供的一些高級功能可能用不到,此時(shí)更輕量的工具(如 npm/yarn 工作空間、微型的 monorepo 腳本等)也許就能勝任。因此,是否采用 Rush,取決于項(xiàng)目規(guī)模和復(fù)雜度:它在“大而全”的場景中表現(xiàn)卓越,但對“小而簡”的情況來說可能有些大材小用。
Rush 與其他 Monorepo 工具對比
目前社區(qū)常用的 Monorepo 管理工具除了 Rush 之外,還有 Lerna、Nx 等等。它們各有優(yōu)缺點(diǎn),針對的使用場景也略有差異。下面我們將 Rush 分別與 Lerna 和 Nx 作對比分析,幫助讀者理解 Rush 的獨(dú)特優(yōu)勢。
Lerna:老牌發(fā)布管理工具
Lerna 是 2016 年推出的老牌 Monorepo 工具之一,它率先提供了在單倉庫下鏈接和控制多個(gè) npm 包的能力 (Lerna vs Turborepo vs Rush: Which is better in 2023?)。使用 Lerna,開發(fā)者可以通過簡單的命令同時(shí)執(zhí)行所有子包的腳本,例如運(yùn)行 npx lerna run build 會(huì)自動(dòng)依次構(gòu)建所有包 (Lerna vs Turborepo vs Rush: Which is better in 2023?)。Lerna 的優(yōu)勢在于上手簡單、發(fā)布流程成熟:它內(nèi)置了版本號統(tǒng)一管理和批量發(fā)布到 npm 的功能,非常適合需要發(fā)布多個(gè)相關(guān)庫的項(xiàng)目。此外,Lerna 對現(xiàn)有項(xiàng)目的侵入性小,幾乎不需要額外配置即可使用。然而,Lerna 本身并不處理高階的構(gòu)建優(yōu)化,例如增量緩存或復(fù)雜的依賴圖分析。在性能方面,裸用 Lerna 進(jìn)行全量構(gòu)建并不突出,需要借助其他工具提升效率。近年來社區(qū)的主流做法是將 Nx 集成到 Lerna 中,以利用 Nx 的智能緩存機(jī)制加速構(gòu)建 (Lerna vs Turborepo vs Rush: Which is better in 2023?)??梢哉J(rèn)為,Lerna 更專注于管理和發(fā)布層面,而并未深耕于構(gòu)建性能優(yōu)化或開發(fā)體驗(yàn)。相比之下,Rush 提供了更全面的構(gòu)建管理功能:它不僅能像 Lerna 一樣統(tǒng)一執(zhí)行腳本、管理版本,還內(nèi)建了對并行/增量構(gòu)建的支持,并通過策略機(jī)制在大型團(tuán)隊(duì)協(xié)作上更有優(yōu)勢。
Nx:面向開發(fā)體驗(yàn)的現(xiàn)代工具
Nx 是由 Nrwl 公司推出的一套擴(kuò)展性 Monorepo 開發(fā)工具,它的理念是通過一系列插件和生成器(schematics)來提升開發(fā)效率 (Rush vs Nx: A Comparison of TypeScript Monorepo Management Tools | elliott.dev)。Nx 最初誕生于 Angular 社區(qū),但如今已擴(kuò)展支持 React、Node.js 等眾多技術(shù)棧。與 Rush 不同,Nx 默認(rèn)采用更統(tǒng)一的工作區(qū)結(jié)構(gòu):通常使用 Yarn/npm 工作空間,所有項(xiàng)目共享一個(gè)頂層的 node_modules。這種方式下,各子項(xiàng)目依賴由倉庫根目錄統(tǒng)一管理(通過根 package.json),某種程度上降低了項(xiàng)目隔離性 (Rush vs Nx: A Comparison of TypeScript Monorepo Management Tools | elliott.dev)。但 Nx 強(qiáng)調(diào)通過工具智能來保證依賴管理和構(gòu)建的正確,比如 Nx 提供了可視化的依賴圖命令 (nx dep-graph) 來幫助理解項(xiàng)目關(guān)系,并支持影響范圍分析 (nx affected:build 等) 以只重建受改動(dòng)影響的項(xiàng)目 (Rush vs Nx: A Comparison of TypeScript Monorepo Management Tools | elliott.dev)。在構(gòu)建加速方面,Nx 擁有強(qiáng)大的本地緩存和分布式遠(yuǎn)程緩存功能,即使是大倉庫也能獲得出色的增量構(gòu)建性能。實(shí)際上,結(jié)合 Nx 的緩存后,Lerna 的性能在某些基準(zhǔn)測試中甚至優(yōu)于其他工具 (Lerna vs Turborepo vs Rush: Which is better in 2023?)。Nx 還提供了大量社區(qū)插件,可一鍵創(chuàng)建新模塊、組件,并預(yù)置測試、打包配置等 (Rush vs Nx: A Comparison of TypeScript Monorepo Management Tools | elliott.dev)。這使它在開發(fā)體驗(yàn)上非常友好,適合希望快速起步并遵循最佳實(shí)踐的團(tuán)隊(duì)。然而,Nx 相對Rush而言缺少內(nèi)建的發(fā)布管理功能,對版本一致性、依賴審核等企業(yè)級需求支持不明顯。另外,由于 Nx 采用約定式的插件結(jié)構(gòu),項(xiàng)目自定義靈活性略遜于 Rush —— Rush 允許開發(fā)者完全按照自己的方式編寫構(gòu)建腳本和配置,而 Nx 則傾向于在其框架內(nèi)完成這些任務(wù) (Rush vs Nx: A Comparison of TypeScript Monorepo Management Tools | elliott.dev) (Rush vs Nx: A Comparison of TypeScript Monorepo Management Tools | elliott.dev)。總結(jié)而言,Nx 更像一站式的前端工程體系,突出敏捷開發(fā)和緩存性能,而 Rush 則更專注于大型倉庫的秩序和穩(wěn)定。
Rush 的獨(dú)特優(yōu)勢
通過上述對比可以看出,Rush 相較 Lerna、Nx 具有以下獨(dú)特優(yōu)勢:
豐富的企業(yè)級特性:Rush 擁有非常多樣的功能點(diǎn),例如倉庫策略(限制哪些依賴可被使用、制定代碼規(guī)范等)、插件機(jī)制(可擴(kuò)展 Rush 功能)、多版本策略(同倉庫內(nèi)不同項(xiàng)目組可以采用不同的版本發(fā)布策略)等。這些特性專注于管理超大型倉庫,許多是 Lerna 和 Nx 所不具備或需要額外工具實(shí)現(xiàn)的 (Lerna vs Turborepo vs Rush: Which is better in 2023?) (Lerna vs Turborepo vs Rush: Which is better in 2023?)。在功能完備性方面,Rush 明顯領(lǐng)先,是名副其實(shí)的“瑞士軍刀”工具集 (Rush)。
嚴(yán)格的依賴和版本管理:Rush 可以在倉庫范圍內(nèi)強(qiáng)制依賴版本統(tǒng)一,避免團(tuán)隊(duì)各自引入不同版本庫導(dǎo)致的沖突。同時(shí),它支持鎖步版本發(fā)布——例如某一組包總是共同提升版本號,保證相互兼容;也支持獨(dú)立版本發(fā)布以滿足不同演進(jìn)速度的需求 (Rush)。Rush 自帶的變更日志和發(fā)布流程工具,使得多包發(fā)布像單一項(xiàng)目一樣順暢。而 Lerna 僅提供基本的版本統(tǒng)一或獨(dú)立模式,Nx 則沒有內(nèi)建這方面功能(需要借助其他方案實(shí)現(xiàn))。
構(gòu)建性能與增量構(gòu)建:在默認(rèn)配置下,Rush 會(huì)針對未改動(dòng)的項(xiàng)目自動(dòng)跳過重復(fù)的構(gòu)建步驟(本地增量分析),這一點(diǎn)與 Nx 的受影響構(gòu)建有異曲同工之妙。而進(jìn)一步地,Rush 提供了實(shí)驗(yàn)性的構(gòu)建緩存功能,將構(gòu)建產(chǎn)物壓縮后緩存,無論本地還是在 CI 上都可以重用這些產(chǎn)物,從而達(dá)到更高層次的加速 (Lerna vs Turborepo vs Rush: Which is better in 2023?) (Lerna vs Turborepo vs Rush: Which is better in 2023?)。舉例來說,開啟緩存后,一個(gè)耗時(shí) 30 分鐘的全量構(gòu)建在緩存命中時(shí)可縮短到約 30 秒 (Enabling the build cache | Rush)!Nx 在這方面也有遠(yuǎn)程緩存(通過 Nx Cloud 服務(wù))支持,Lerna 則需要借助 Nx 才能實(shí)現(xiàn)類似功能。可以說,在極限性能優(yōu)化上,Rush 和 Nx 各有千秋;但 Rush 同時(shí)提供了穩(wěn)健的基礎(chǔ)性能(并行構(gòu)建、子項(xiàng)目隔離)以及圍繞構(gòu)建的一系列輔助工具,這是它相對競爭對手的一大優(yōu)勢。
大團(tuán)隊(duì)協(xié)同支持:Rush 針對多人協(xié)作設(shè)計(jì)了一些細(xì)節(jié)。例如前文提到的 approvedPackages 策略,要求新增依賴必須加入白名單配置,防止引入未經(jīng)批準(zhǔn)的庫 (Lerna vs Turborepo vs Rush: Which is better in 2023?)。再如,當(dāng)團(tuán)隊(duì)規(guī)模和倉庫變得龐大時(shí),難免需要對工程工具本身進(jìn)行擴(kuò)展,Rush 提供插件機(jī)制允許注入自定義命令或行為。相比之下,Nx 更偏重于框架和插件生態(tài)的使用,而在團(tuán)隊(duì)規(guī)范約束方面不如 Rush 來得系統(tǒng)。對于非常注重規(guī)范和可控性的團(tuán)隊(duì),Rush 提供的這些保障顯得尤為寶貴 (Lerna vs Turborepo vs Rush: Which is better in 2023?)。
當(dāng)然,Rush 也有其劣勢。例如它的上手成本相對更高,初始化配置步驟比 Lerna 或 Nx 繁瑣,需要編寫配置文件、維護(hù)額外的倉庫結(jié)構(gòu) (Lerna vs Turborepo vs Rush: Which is better in 2023?)。因此社區(qū)有一種聲音:“小團(tuán)隊(duì)用 Nx,大團(tuán)隊(duì)用 Rush” (Lerna vs Turborepo vs Rush: Which is better in 2023?)。選擇何種工具最終取決于項(xiàng)目規(guī)模、團(tuán)隊(duì)需求以及對開發(fā)體驗(yàn)和管控力度的偏好。
如何安裝和使用 Rush
接下來,我們一步步介紹 Rush 的安裝和基本使用方法,并通過一個(gè)示例演示如何創(chuàng)建和管理一個(gè) Rush 項(xiàng)目。
1. 安裝 Rush
Rush 可以通過 npm 快速安裝。由于 Rush 在倉庫層面運(yùn)行,一般推薦全局安裝它的命令行工具。在終端中運(yùn)行以下命令安裝最新版本的 Rush:
npm install -g @microsoft/rush
安裝完成后,可以使用 rush -V 來驗(yàn)證版本。確保你的 Node.js 版本滿足 Rush 的要求(通常需要相對新的 LTS 版本)。
2. 初始化 Rush 倉庫
在一個(gè)新的(或已有的)代碼倉庫中啟用 Rush 管理,需要進(jìn)行初始化。在倉庫的根目錄執(zhí)行:
rush init
此命令會(huì)在當(dāng)前目錄下生成 Rush 所需的一系列配置文件,包括主配置文件 rush.json、常用配置模板(如 common-versions.json 用于依賴版本管理,build-cache.json 用于構(gòu)建緩存配置等)以及 .gitignore、CI 模板等 (Setting up a new repo | Rush) (Setting up a new repo | Rush)。這些文件都帶有詳盡的注釋說明,首次使用時(shí)建議閱讀以了解可配置項(xiàng)。完成初始化后,將這些新文件提交到版本庫。這一步相當(dāng)于為倉庫“啟用”了 Rush 管理能力。
3. 創(chuàng)建和注冊子項(xiàng)目
現(xiàn)在,可以將實(shí)際的子項(xiàng)目添加到倉庫中。假設(shè)我們打算管理兩個(gè)簡化的 npm 包:“alpha”和“beta”(beta 將依賴 alpha)。首先,在倉庫中新建目錄存放這些子項(xiàng)目,例如在根目錄下創(chuàng)建一個(gè) packages 文件夾,然后在其中建立 alpha 和 beta 兩個(gè)子文件夾。接著,在每個(gè)子項(xiàng)目文件夾內(nèi)創(chuàng)建各自的 package.json 文件和代碼文件。例如:
-
packages/alpha/package.json定義 alpha 包的信息(名稱、版本、依賴等)。 -
packages/alpha/index.js實(shí)現(xiàn) alpha 包的功能(例如導(dǎo)出一個(gè)簡單函數(shù))。 -
packages/beta/package.json定義 beta 包的信息,并聲明對 alpha 的依賴。 -
packages/beta/index.js調(diào)用 alpha 提供的功能。
創(chuàng)建好子項(xiàng)目后,需要讓 Rush 知道它們的存在。在 rush.json 主配置文件中,有一個(gè) "projects" 數(shù)組,用于列出倉庫內(nèi)管理的所有子項(xiàng)目。我們需要在其中加入 alpha 和 beta 的配置項(xiàng),例如:
// rush.json(節(jié)選)
{
"projects": [
{
"packageName": "alpha",
"projectFolder": "packages/alpha"
},
{
"packageName": "beta",
"projectFolder": "packages/beta"
}
]
}
每個(gè)項(xiàng)目包含兩個(gè)屬性:packageName 應(yīng)與該項(xiàng)目 package.json 中的 "name" 字段一致,projectFolder 為相對于倉庫根目錄的子項(xiàng)目路徑。保存配置后,Rush 就知道如何定位我們的子項(xiàng)目了。
4. 安裝依賴
子項(xiàng)目注冊完畢后,執(zhí)行一次 Rush 的依賴安裝命令。在 Rush 中,通常使用 rush update 來安裝和更新依賴。它會(huì)讀取所有子項(xiàng)目的 package.json,根據(jù)其中的依賴聲明計(jì)算出整體依賴樹,然后使用選定的包管理器(默認(rèn) PNPM)統(tǒng)一安裝依賴。與直接運(yùn)行 npm/yarn 不同,rush update 會(huì)在 common/temp 下生成一個(gè)統(tǒng)一的鎖定文件(如 pnpm-lock.yaml),并確保每個(gè)子項(xiàng)目的依賴正確地符號鏈接到它們各自的 node_modules 下 (GitHub - brandhaug/rush-example: A basic example showing the power and simplicity of Rush.js) (GitHub - brandhaug/rush-example: A basic example showing the power and simplicity of Rush.js)。如果倉庫中已有鎖定文件且不希望更新版本,可以使用 rush install 命令,它會(huì)跳過更新步驟,直接依據(jù)已有鎖定文件安裝,以保證完全一致的依賴版本。
在我們的例子中,運(yùn)行 rush update 后,Rush 會(huì)安裝 alpha 和 beta 所需的依賴包。如果 beta 依賴 alpha,Rush 還會(huì)自動(dòng)處理將 alpha 鏈接到 beta 的 node_modules,就像手動(dòng)運(yùn)行了 npm link 一樣,但這一切都是由 Rush 自動(dòng)完成的。
5. 構(gòu)建與運(yùn)行項(xiàng)目
依賴安裝完畢后,就可以使用 Rush 來統(tǒng)一構(gòu)建所有子項(xiàng)目。執(zhí)行:
rush build
Rush 將按照依賴順序依次構(gòu)建 alpha 和 beta。默認(rèn)情況下,它會(huì)調(diào)用每個(gè)子項(xiàng)目 package.json 中定義的 "build" 腳本。因此,我們需要確保 alpha 和 beta 的 package.json 中都有 "build" 腳本。例如,在 alpha 包中可以將 build 腳本設(shè)為執(zhí)行 Babel 編譯(若有)或者簡單地 echo 一條消息,在 beta 包中則可以讓 build 腳本運(yùn)行它自身(例如 Node.js 執(zhí)行 index.js)。Rush 在運(yùn)行構(gòu)建時(shí)會(huì)并行執(zhí)行不互相依賴的多個(gè)項(xiàng)目,以最大化利用多核資源,加速整體構(gòu)建過程。
在我們的示例里,我們?yōu)楹喕菔?,?alpha 的構(gòu)建腳本僅打印消息,beta 的構(gòu)建腳本執(zhí)行它的代碼。構(gòu)建完成后,可以看到 beta 項(xiàng)目運(yùn)行時(shí)成功調(diào)用了 alpha 包提供的功能。下面是這一最簡示例的代碼結(jié)構(gòu)和內(nèi)容:
// packages/alpha/package.json
{
"name": "alpha",
"version": "1.0.0",
"main": "index.js",
"scripts": {
"build": "echo Building alpha"
}
}
// packages/alpha/index.js
function add(x, y) {
return x + y;
}
module.exports = { add };
// packages/beta/package.json
{
"name": "beta",
"version": "1.0.0",
"main": "index.js",
"scripts": {
"build": "node index.js"
},
"dependencies": {
"alpha": "1.0.0"
}
}
// packages/beta/index.js
const alpha = require('alpha');
console.log('2+3=' + alpha.add(2, 3));
在這個(gè)例子中,beta 包通過 require('alpha') 引用了 alpha 包導(dǎo)出的函數(shù)。在執(zhí)行 rush build 時(shí),Rush 會(huì)確保先構(gòu)建 alpha(因?yàn)?beta 依賴它),然后再構(gòu)建 beta。由于 Rush 利用了 PNPM 的符號鏈接機(jī)制,beta 的 node_modules/alpha 實(shí)際指向我們倉庫內(nèi)的 alpha 項(xiàng)目源碼。因此,beta 能正確調(diào)用到最新的 alpha 代碼。運(yùn)行 rush build 后,我們預(yù)期會(huì)在控制臺(tái)看到 beta 腳本的輸出,例如 2+3=5。這證明 Rush 順利地協(xié)調(diào)了兩個(gè)子項(xiàng)目的依賴和構(gòu)建。
6. 常用命令簡介
通過上述示例,我們已經(jīng)使用了 Rush 的幾個(gè)基本命令:rush init(初始化倉庫)、rush update(安裝依賴)和 rush build(構(gòu)建項(xiàng)目)。除了這些,Rush 還有許多實(shí)用的命令:
rush add: 為某個(gè)子項(xiàng)目添加依賴。例如在 beta 包目錄下執(zhí)行rush add -p <package>,會(huì)將<package>安裝為 beta 的依賴并更新相關(guān)的配置。這比手動(dòng)修改 package.json 再運(yùn)行安裝更加安全可靠,因?yàn)?Rush 會(huì)確保版本統(tǒng)一并避免引入重復(fù)依賴。rush remove: 從子項(xiàng)目移除某個(gè)依賴,并更新鎖定文件。rush rebuild: 強(qiáng)制重新構(gòu)建所有項(xiàng)目,忽略增量優(yōu)化。有時(shí)在調(diào)整配置或遇到問題時(shí)需要全量重建,可以使用這個(gè)命令。rush lint/rush test: 如果配置了這些自定義命令(通過 command-line.json),可以一鍵讓所有包執(zhí)行 lint 或測試腳本。rush change: 生成變更文件,用于記錄某次提交對各個(gè)包的影響(通常在將要發(fā)布前執(zhí)行,用于生成更新日志)。rush publish: 根據(jù)變更文件為各包更新版本號并發(fā)布到 npm 注冊表。它會(huì)自動(dòng)處理依賴關(guān)系、遵循鎖步或獨(dú)立版本策略,并生成對應(yīng)的變更日志條目。
上述命令使得日常開發(fā)、維護(hù)到發(fā)布的各環(huán)節(jié)都能在 Rush 的統(tǒng)一工具鏈下完成。當(dāng)然,根據(jù)項(xiàng)目需要,可以只使用其中一部分命令。例如有的團(tuán)隊(duì)暫時(shí)不需要發(fā)布功能,就可以不管 rush change/publish。Rush 的設(shè)計(jì)是模塊化的,開發(fā)者可以按需選用。
Rush 的高級特性和最佳實(shí)踐
當(dāng)項(xiàng)目規(guī)模和團(tuán)隊(duì)協(xié)作達(dá)到一定程度時(shí),Rush 的許多高級特性就能展現(xiàn)出價(jià)值。在本節(jié)中,我們探討 Rush 提供的增量構(gòu)建與緩存優(yōu)化,以及在大型團(tuán)隊(duì)中使用 Rush 的最佳實(shí)踐,并說明如何將 Rush 與 CI/CD 系統(tǒng)高效集成。
增量構(gòu)建與緩存優(yōu)化
Rush 支持兩種層次的增量構(gòu)建策略:輸出保留和構(gòu)建緩存。
輸出保留(本地增量):這是 Rush 默認(rèn)啟用的機(jī)制。當(dāng)運(yùn)行
rush build時(shí),Rush 會(huì)比較每個(gè)項(xiàng)目自上次構(gòu)建以來源文件是否發(fā)生變化。如果某個(gè)項(xiàng)目的源碼和其依賴均未修改,則跳過對該項(xiàng)目的重新構(gòu)建,從而加快整個(gè)構(gòu)建流程 (Enabling the build cache | Rush)。例如,你修改了倉庫中的一個(gè)庫項(xiàng)目,那么與之無關(guān)的其他項(xiàng)目將不會(huì)重復(fù)編譯,大大減少了無效工作量。這種基于狀態(tài)檢測的增量構(gòu)建在本地開發(fā)時(shí)尤其有用,避免每次都耗費(fèi)時(shí)間編譯所有內(nèi)容。構(gòu)建緩存(本地/遠(yuǎn)程緩存):為了進(jìn)一步加速,Rush 引入了構(gòu)建緩存功能。開啟該功能后,Rush 會(huì)在構(gòu)建完成時(shí)將每個(gè)項(xiàng)目的構(gòu)建產(chǎn)物(如編譯后的文件)打包存儲(chǔ)起來 (Enabling the build cache | Rush)。下次構(gòu)建時(shí),如果發(fā)現(xiàn)某項(xiàng)目的輸入(源碼、依賴)與緩存中記錄的完全一致,就直接跳過實(shí)際構(gòu)建并還原緩存產(chǎn)物 (Enabling the build cache | Rush)。這意味著即使切換 Git 分支或重新克隆倉庫,只要緩存可用,許多項(xiàng)目都無需重新構(gòu)建。測試表明,啟用緩存可將一次完整構(gòu)建從 30 分鐘縮短到不到 1 分鐘的量級 (Enabling the build cache | Rush)。Rush 的緩存既可以存儲(chǔ)在本地磁盤,也可以配置遠(yuǎn)程共享存儲(chǔ)(如云端的 Azure Blob 或 Amazon S3) (Enabling the build cache | Rush)。在典型的 CI 設(shè)置中,CI 服務(wù)器會(huì)將主分支每次構(gòu)建的產(chǎn)物上傳到云緩存;開發(fā)者本地則設(shè)置為只讀地從云端拉取緩存 (Enabling the build cache | Rush)。這樣,即使是第一次從主分支拉取代碼的新人,執(zhí)行
rush build也能因緩存命中而快速完成 (Enabling the build cache | Rush)。需要注意,Rush 的遠(yuǎn)程構(gòu)建緩存目前還標(biāo)記為實(shí)驗(yàn)特性,需在 build-cache.json 中開啟相應(yīng)選項(xiàng)并進(jìn)行云端存儲(chǔ)配置。
通過結(jié)合本地增量和遠(yuǎn)程緩存,Rush 在保證構(gòu)建結(jié)果一致性的同時(shí),將冗余構(gòu)建降到最低。這對大型項(xiàng)目的持續(xù)集成和持續(xù)部署十分友好:頻繁的構(gòu)建任務(wù)不再重復(fù)浪費(fèi)時(shí)間,開發(fā)體驗(yàn)和CI反饋速度都顯著提升。
大型團(tuán)隊(duì)的最佳實(shí)踐
在大型團(tuán)隊(duì)采用 Rush 時(shí),除了工具本身提供的功能外,還應(yīng)遵循一些最佳實(shí)踐以獲得最佳效果:
嚴(yán)格管理依賴版本:利用 Rush 提供的 common-versions.json 文件,可以明確指定某些關(guān)鍵依賴在倉庫中的統(tǒng)一版本,防止不同子項(xiàng)目各自升級導(dǎo)致版本沖突。對于不希望統(tǒng)一管理的依賴,也可以通過此文件允許不同版本并存。合理地配置版本策略,有助于在團(tuán)隊(duì)協(xié)作中減少“它在我這能跑,在你那卻不行”的情況。
審查引入的新依賴:啟用 Rush 的 Approved Packages Policy(已批準(zhǔn)依賴策略)。具體做法是在 Rush 配置中列出允許使用的第三方包清單。當(dāng)有人嘗試添加未在清單中的依賴時(shí),Rush 會(huì)給出警告或錯(cuò)誤,提示需要更新清單。這一流程相當(dāng)于在技術(shù)層面建立了一道審核機(jī)制,確保所有新引入的庫都經(jīng)過團(tuán)隊(duì)認(rèn)可 (Lerna vs Turborepo vs Rush: Which is better in 2023?)。對于關(guān)注穩(wěn)定性和安全性的項(xiàng)目,這是非常值得推行的實(shí)踐。
制定版本發(fā)布策略:如果倉庫中的子項(xiàng)目需要發(fā)布為獨(dú)立的庫,建議為它們配置 Rush 的 版本策略(Version Policy)。通過 version-policies.json 文件,可以將某些包歸為一組,設(shè)定它們采用統(tǒng)一版本(Lockstep)發(fā)布,或者各自獨(dú)立版本(Independent)發(fā)布 (Rush)。Lockstep 適用于一組緊密相關(guān)、應(yīng)同步升級的包,而 Independent 則賦予每個(gè)包自由演進(jìn)的空間。在大團(tuán)隊(duì)協(xié)作下,明確版本策略能避免發(fā)布流程的混亂。同時(shí),養(yǎng)成在每次功能或修復(fù)提交時(shí)編寫變更日志(使用
rush change)的習(xí)慣,這會(huì)為后續(xù)自動(dòng)生成發(fā)布說明提供依據(jù)。充分利用 Rush 插件和自定義命令:Rush 允許定義自定義的批處理命令(通過 command-line.json)。團(tuán)隊(duì)可以根據(jù)自身需求添加一些方便的命令,比如一鍵運(yùn)行所有單元測試、生成整體文檔等。這些命令可以封裝復(fù)雜的工作流,降低使用門檻。此外,如果 Rush 的默認(rèn)功能無法滿足需求,考慮編寫 Rush 插件來擴(kuò)展。插件可以掛鉤 Rush 的流程,在特定階段執(zhí)行自定義邏輯。例如,有團(tuán)隊(duì)開發(fā)了插件來將構(gòu)建狀態(tài)上報(bào)到自定義儀表盤,以監(jiān)控大型倉庫的構(gòu)建健康度。
保持代碼庫整潔:Monorepo 容易因?yàn)闅v史包袱變得臃腫難以維護(hù)。大型團(tuán)隊(duì)?wèi)?yīng)定期清理長期未更新或廢棄的子項(xiàng)目,移除不再使用的依賴,并關(guān)注 Rush 提示的任何問題(如重復(fù)的依賴版本、不符合策略的配置等)。Rush 本身提供了
rush purge來清理本地的依賴和緩存,以及rush scan來掃描倉庫中潛在的問題。這些工具結(jié)合良好的團(tuán)隊(duì)溝通,可以讓倉庫始終保持健康可持續(xù)的發(fā)展?fàn)顟B(tài)。
與 CI/CD 集成
前文已經(jīng)多次提到,Rush 非常適合集成到 CI/CD 流程中。這里總結(jié)幾點(diǎn)實(shí)踐,幫助你在持續(xù)集成環(huán)境下充分利用 Rush:
使用 CI 模板:
rush init創(chuàng)建的 CI 配置文件(如 .github/workflows/ci.yml)是一個(gè)良好的起點(diǎn) (Setting up a new repo | Rush)。如果使用 GitHub Actions,它示范了如何在每次 Pull Request 提交時(shí)跑 Rush 的安裝和構(gòu)建??梢愿鶕?jù)自家項(xiàng)目需要調(diào)整觸發(fā)條件和流程,比如在合并主干時(shí)運(yùn)行發(fā)布腳本等。緩存依賴和構(gòu)建產(chǎn)物:在 CI 平臺(tái)上,可以利用緩存功能減少重復(fù)安裝和構(gòu)建的開銷。對于依賴安裝階段,可以緩存 common/temp 下的 pnpm 模塊目錄或整個(gè) pnpm-store(PNPM 的全局包存儲(chǔ)),下次構(gòu)建時(shí)直接復(fù)用,節(jié)約npm下載安裝時(shí)間。對于構(gòu)建階段,正如前述,可以配置 Rush 的遠(yuǎn)程構(gòu)建緩存,將產(chǎn)物緩存到云存儲(chǔ)。許多 CI 服務(wù)(如 GitHub Actions)提供了緩存機(jī)制,也可以直接緩存 Rush 的本地構(gòu)建產(chǎn)物目錄。不過,由于 Rush 自帶更完善的遠(yuǎn)程緩存策略,推薦使用 Rush 自身的機(jī)制,以便在本地開發(fā)和 CI 之間共享緩存 (Enabling the build cache | Rush)。
CI 用法建議:在 CI 腳本中,通常先執(zhí)行
rush install(而非 update)以確保完全按照鎖定文件安裝依賴,這可以避免 CI 意外將依賴升級。隨后執(zhí)行rush build或者根據(jù)需要執(zhí)行特定命令。如果啟用了增量構(gòu)建/緩存,Rush 會(huì)自動(dòng)跳過無需構(gòu)建的部分,使CI過程更高效。如果需要確保每次CI都是干凈構(gòu)建,可以在執(zhí)行前調(diào)用rush purge清理本地狀態(tài),然后再安裝構(gòu)建。根據(jù)經(jīng)驗(yàn),在大多數(shù)情況下讓緩存發(fā)揮作用能顯著縮短流水線時(shí)間,因此除非問題排查,一般不建議每次都強(qiáng)制全量重裝。發(fā)布流程:將 Rush 發(fā)布集成到 CI/CD 可以大幅減少人工干預(yù)。比如,當(dāng)主干有變更合并且需要發(fā)布新版本時(shí),CI 可以檢測到變更文件并運(yùn)行
rush publish執(zhí)行自動(dòng)發(fā)布。這需要配置好 CI 的身份(如 npm 的發(fā)布令牌)以及確保版本策略正確。Rush 的發(fā)布過程支持干跑(dry-run)模式,可以先在 CI 中模擬發(fā)布,生成變更日志供審核,再正式執(zhí)行。
通過以上方式,Rush 可以無縫融入持續(xù)集成和部署的流程,發(fā)揮其在穩(wěn)定性和效率方面的優(yōu)勢。對比而言,其他 Monorepo 工具(如 Nx)也能在 CI 中運(yùn)作,但 Rush 天生為大型團(tuán)隊(duì)設(shè)計(jì),其確定性和可預(yù)測性在CI環(huán)境下尤其受用。
結(jié)語
Rush 作為一款面向大型前端工程的 Monorepo 管理工具,以其全面的功能集和嚴(yán)謹(jǐn)?shù)墓芾聿呗?/strong>脫穎而出。在需要管理眾多模塊、多人協(xié)作、并追求高構(gòu)建性能的場景下,Rush 提供了可靠的解決方案。從依賴安裝到構(gòu)建發(fā)布,Rush 打通了整個(gè)鏈路,極大簡化了開發(fā)者的工作。然而,正如工具的選擇沒有銀彈,Rush 也并非適用于所有團(tuán)隊(duì)。對于規(guī)模適中、追求上手簡單的項(xiàng)目,Lerna 或 Nx 可能更為適合;但當(dāng)你的代碼倉庫日益龐大、復(fù)雜,協(xié)作人數(shù)不斷增加時(shí),Rush 的優(yōu)勢將愈發(fā)明顯 (Lerna vs Turborepo vs Rush: Which is better in 2023?)。