Nx Console 是一個 Nx 官方推出的 VSCode Extension,可以幫助開發者減少輸入 Nx Command 的時間,透過圖形化介面,快速整合並產生不同 Stack 的程式碼,大幅提升開發效率,是一套非常強大且好用的工具。
補充:Extension 可以從這裡進行安裝。
下方是 Nx Console 提供的控制面板,可以看到共有三大區塊:
build
。透過面板的「Generate (UI)」來選擇程式碼產生器,上一篇選擇了 NestJS Stack,所需的套件和程式碼產生器已經安裝完成,所以會在跳出來的程式碼產生器選單中看到非常多與 NestJS 相關的選項:
此處選擇 @nx/nest - application
來建立 Application,此時會跳出一個表單來填寫該 Application 相關的設置參數,其中 name
為必填項目,目的是要為該 Application 命名。下方是輸入名稱「ground」、directory
為「apps/ground」後的狀態,可以看到終端機內出現了建立的檔案名稱與路徑,但這只是預覽模式,此時還不會真的建立檔案:
確認表單內容填妥後,點選右上角的「Generate」按鈕來建立檔案:
如果有專案要棄用需要移除,一樣可以透過 Nx Console 來執行。點選「Generate (UI)」選擇 @nx/workspace - remove
:
選擇後會跳出表單讓使用者選擇要刪除的專案。下方是選擇「ground-e2e」後的狀態,在終端機會顯示刪除的檔案名稱與路徑,但與建立 Application 的行為相同,僅作為預覽使用並沒有真的刪除檔案:
點選右上角的「Generate」按鈕來刪除檔案:
建立 Library 的方式與建立 Application 的方式大同小異,同樣是透過「Generate (UI)」這個選項來進行,不過選擇的程式碼產生器是 @nx/nest - library
:
選擇後會跳出表單讓使用者填寫建置的相關參數,其中 name
為必填項目。這邊可以看到 buildable
與 publishable
的選項,buildable
的意思是這個 Library 是不是一個可以獨立建置的 Library,而 publishable
的意思則是能不能發佈成獨立套件的 Library,當然也可以都不勾選,那就把該 Library 當作 Application 會使用的模組即可。下方是輸入名稱「todo」、directory
為「libs/todo」、importPath
為「@playground/todo
」後的狀態,可以看到終端機內出現了建立的檔案名稱與路徑,但同樣是預覽模式並沒有真的建立檔案:
點選右上角的「Generate」按鈕就會建立:
補充:
importPath
可以幫助我們決定該 Library 程式碼被匯入時使用的路徑,以上方範例來說,可以從@playground/todo
匯入該 Library 的程式碼。
Nx 除了上述產生程式碼的功能外,還有許多強大的功能可以幫助我們管理整個 Monorepo,甚至可以加速整體執行 Task 的時間,就讓我帶大家一探究竟吧!
前一篇有介紹到 Project Graph 與 Task Graph 兩個重要概念,但在 Monorepo 變得龐大時很難靠想像將這些 Dependency 關聯起來,對於開發者來說就比較不好掌握它們之間的關係,那麼有沒有方法把這些 Graph 用圖形的方式呈現出來呢?答案是可以的,Nx CLI 有提供相關指令可以架設 Nx Graph UI 讓開發者探索 Project Graph 與 Task Graph。打開終端機將路徑移動到 Nx Workspace 下,並輸入下方指令:
$ npx nx graph
Nx 會將 Graph UI 架設在 4211
Port 並自動開啟 UI:
在 UI 的左上角有一個選擇器,預設情況下會是「Projects」,意思就是現在探索的 Graph 即為 Project Graph。畫面左半邊的面板可以搜尋要探索的專案,在 Monorepo 規模較大的時候,查看特定專案的 Dependency 非常有用,原因是一次呈現所有專案的 Dependency 會相當雜亂,造成過多雜訊,不過如果規模不大的話,可以直接點選「Show all projects」 來看所有專案形成的 Project Graph:
從上圖可以看到 todolist
依賴於 todo
,這是因為我在 todolist
專案的 AppModule
使用了 todo
專案提供的 TodoModule
,根據上一篇的描述,Nx 會自動推斷專案之間的 Dependency:
import { Module } from '@nestjs/common';
import { TodoModule } from '@playground/todo';
// ...
@Module({
imports: [TodoModule],
// ...
})
export class AppModule {}
從圖中還可以觀察到 todolist
是 todolist-e2e
的 Implicit Dependency,原因是在 todolist-e2e
的 project.json
有使用 implicitDependencies
屬性,並指定 todolist
:
{
"name": "todolist-e2e",
"$schema": "../../node_modules/nx/schemas/project-schema.json",
"projectType": "application",
"implicitDependencies": [
"todolist"
],
"targets": {
// ...
}
}
將左上角的選擇器改為「Tasks」來探索 Task Graph:
將「Target Name」選擇 serve
來觀察所有專案中 serve
Task 的 Task Graph,並點選「Show all tasks」:
在 Nx 的架構下,我們會將程式碼以 Library 的形式拆分成各個模組,當規模越來越大的時候,Library 的數量可能會成長到上百個甚至上千個,如果這些 Library 可以自由互相引用的話,最終很可能會陷入難以控管的 循環依賴(Circular Dependency),因此 Nx 提供了劃分 Boundary 的機制,讓開發者可以根據 Library 的性質賦予不同的 標籤(Tag),再根據 Tag 的性質做進一步的依賴限制,達到降低 Circular Dependency 的效果。
以我們先前建立的 Nx Workspace 當作例子,假如希望 todo
只有 Todo 相關 Domain 的 Application 或 Library 可以使用,那可以替 todo
添加 Tag 來標示它的 Boundary。下方是 project.json
的範例,在 tags
屬性加上 scope:todo
的 Tag:
{
"name": "todo",
"$schema": "../../node_modules/nx/schemas/project-schema.json",
"sourceRoot": "libs/todo/src",
"projectType": "library",
"tags": ["scope:todo"],
"targets": {}
}
補充:範例中使用
scope:todo
作為 Tag 名稱,但真實情況應該根據團隊共識進行命名,這邊蠻推薦使用scope:
前綴來標示它的 domain Boundary,另外還可以根據 Library 的性質添加 Tag,比如:type:util
。
由於 todolist
也屬於 Todo 相關 Domain,所以在 project.json
中加上 scope:todo
Tag:
{
"name": "todolist",
"$schema": "../../node_modules/nx/schemas/project-schema.json",
"sourceRoot": "apps/todolist/src",
"projectType": "application",
"tags": ["scope:todo"],
"targets": {
// ...
}
}
最後,需要編輯 Nx Workspace 根目錄中的 .eslintrc.json
,在 rules
內有一個名為 @nx/enforce-module-boundaries
的 Rule,這就是定義 Boundary 限制的地方。在這條 Rule 中,有一個 depConstraints
的屬性,在初始設置中會有 sourceTag
為 *
、onlyDependOnLibsWithTags
為 ["*"]
的設定,意思是不論 Tag 是什麼都可以引用所有 Library:
{
// ...
"overrides": [
{
// ...
"rules": {
"@nx/enforce-module-boundaries": [
"error",
{
"enforceBuildableLibDependency": true,
"allow": [],
"depConstraints": [
{
"sourceTag": "*",
"onlyDependOnLibsWithTags": ["*"]
}
]
}
]
}
},
// ...
]
}
我們需要將預設的設定進行調整,把 sourceTag
改為 scope:todo
、onlyDependOnLibsWithTags
改為 ["scope:todo"]
:
{
// ...
"overrides": [
{
// ...
"rules": {
"@nx/enforce-module-boundaries": [
"error",
{
"enforceBuildableLibDependency": true,
"allow": [],
"depConstraints": [
{
"sourceTag": "scope:todo",
"onlyDependOnLibsWithTags": ["scope:todo"]
}
]
}
]
}
},
// ...
]
}
此時如果在 ground
使用 todo
提供的程式碼會報錯:
在架構龐大的情況下,如果每次執行 Task 都要重新執行所有 Dependency Task,那勢必會花費非常大量的時間,於是 Nx 想到了方法來解決這個問題,將 Task 執行完畢的結果進行快取,每次只重新計算有異動的部分,如此一來,就可以大幅加速執行 Task 的時間。
不過這套機制要能發揮效果的前提是需要將 Application 與 Library 依照 Nx 官方的建議進行合理的拆分,如果都集中在某一個 Application 或 Library 來實現功能,那對 Nx 而言就需要重新計算 Task,這也是為什麼上一篇提到如果不把非共用程式碼抽成 Library 會讓 Nx 效益打折的原因之一。
來進行一段快取回放的實驗,透過 Nx Console 執行 todolist
的 build
Task:
執行完畢後,終端機會顯示成功的訊息:
建置完的程式碼會放在 dist
資料夾底下,此時我們將此資料夾刪除,來驗證再次執行時是否有正確執行快取回放:
再次執行 build
Task 可以看到終端機的訊息,是從快取回放的結果,在速度上也相當快:
在 PR 階段執行測試、建置等 Task 可以確保合併進去的程式碼不會造成 Workspace 內的專案發生問題,但隨著規模越來越大,要針對每個專案執行這些 Task 顯得不切實際,於是 Nx 想到了方法來解決這個問題,只要將執行 Task 的範圍縮小至 這個 PR 改動影響的範圍 即可,透過指定 Git 的 起始點(Base) 與 結束點(Head) 讓 Nx 判斷這過程中改動了哪些專案,進而推論出受影響的專案,這套機制就叫 Affected。
從上方概念圖可以發現,假如只異動了 todolist
,那在 Affected 機制下僅需要針對 todolist
執行相關 Task,但如果異動的是 utility
,那依賴於它的 todolist
與 user
都必須執行相關 Task。
用下方 Git Graph 來進行 Affected 實驗:
圖中最近的這三個 Commit 內容分別是:
e233e760
:新增 todo
Library。d407467d
:在 todolist
Application 使用 todo
Library 的程式碼。9dabfd31
:新增 ground
Application。如果我們以 Commit e233e760
作為 base
、以 Commit 9dabfd31
作為 head
去執行 build
Task,那麼會建置 ground
與 todolist
。下方是透過 Nx Console 執行的操作:
回顧一下今天介紹的內容,在一開始介紹了 Nx Console 這套 Nx 官方推出的 VSCode Extension,透過圖形化介面減少 Nx Command 的輸入,讓開發者更方便管理專案。接著,詳細介紹了如何使用 Nx Console 建立應用程式、刪除專案以及建立 Library。接著,深入介紹 Nx 快取回放機制和 Affected 機制,這些功能幫助加速專案的建置和測試過程。除此之外,還探討了 Nx 的 Graph UI 和 Boundary 機制,提供視覺化的 Project Graph、Task Graph 與循環依賴的控制方法,使 Monorepo 的管理更高效且易於維護。
微服務管理策略的部分告一段落,這兩天充分學習到 Nx 這套強大的 Monorepo 管理工具, 後續的篇章會基於 Nx 來進行範例程式碼的實現。下一篇我們將進入 微服務的設計模式篇,敬請期待!