[筆記] Yarn Workspaces 基礎教學

Toki Lee
11 min readDec 27, 2020

--

Logo 取自官方

最近在 Npm v7 的版本也加入了 Workspaces 的功能,想當然的可以預想未來在開發上 Mono-Repo 將會是更加普遍的概念,正好近期也有接觸到 Yarn Workspaces 的部分,故在這裡做個紀錄。

首先我們要先問:

什麼時候會使用到 Workspaces 的功能?

是這樣的,如果正在開發小專案或開發週期相對短的專案,你可能不需要考慮到這些問題。但如果是有些複雜的專案,或有一些需要復用的模組、需要結合幾個有些不同生態的技術之類的問題,那麼也許你可以考慮使用 Yarn Workspaces 看看。

當有兩個專案需要依賴一些共通的模組時,你不想要在兩個專案內重做同樣的事情,又覺得使用 git submodule 這樣的方式後,反而礙手礙腳,那麼也許需換個方式。

說不定可以考慮把兩個專案都放在一起看看?

這感覺像是走回頭路?但這邊說的放在一起,並不是真的就把一切都混在一起,搞的亂七八糟那樣。而是在一個規劃好的架構下,讓彼此作為一個獨立的模組下又能減少重複需要安裝的依賴。而這些就是 Yarn Workspaces 可以為我們帶來的好處。

接下來就開始簡單的說明下 Yarn Workspaces

Yarn Workspaces

在 Yarn v1.0 之後的版本支援了 Workspaces 的功能,而預設上Workspaces 是開啟的,不過也可以透過 yarn config set workspaces-experimental true 這個指令去設定開啟或關閉。

那麼開一個新專案:

mkdir yarn-wks-ex
cd yarn-wks-ex
mkdir packages

首先先稱這個專案目錄為 workspace root,並且在安裝套件後,在這個目錄下出現的 node_modules 稱為 root node_modules。

加上 package.json

{
"name": "yarn-wks-ex",
"version": "1.0.0",
"private":true,
"license": "MIT",
"workspaces": {
"packages": [
"packages/*"
],
"nohoist": []
}
}

workspace root 下的 package.json 必須設為 private,因為 workspace root 並不該發布出去。

workspaces 欄位下的 packages 說明哪些子目錄會是工作區,則nohoist的部分可以用來說明哪些依賴不需要 hoist 到 root node_modules 下( hoist 的部分會在最後說明),如果不會使用到 nohoist 時,可以直接省略為:

"workspaces": [
"packages/*"
]

加入工作區

接下來來增加一些新的工作區:

mkdir packages/package-a \
packages/package-b \
packages/package-c

依序在三個 package 的目錄下執行 yarn init 並修改 package 的名稱為:

// package-a/package.json"name": "@yarn-wks-ex/package-a"...// package-b/package.json"name": "@yarn-wks-ex/package-b"...// package-c/package.json"name": "@yarn-wks-ex/package-c"

現在我們有了三個工作區,現在就開始安裝套件看看吧。我們知道 yarn 安裝套件是使用 yarn add 這個指令,而在使用 Yarn Workspaces 的時候,則有兩個選擇:

  1. 在 workspace root 下任何位置下執行指令:
yarn workspace @yarn-wks-ex/package-a add lodash

2. 在特定工作區下執指令:

cd packages/package-b
yarn add lodash

我想這可能有點廢話,不過我這裡想說明的是,大部分 yarn 的指令可以透過 yarn workspace 工作區 指令 來執行。

並且只要你的設定是正確的,那你能夠在執行 yarn install或是在任何工作區下加入套件時,yarn 都會幫你把套件 hoist 到 root node_modules 下:

可以在 root node_modules 下看到安裝的套件。

實作

接下來我做個簡單的範例來說明 Yarn Workspace 的功能:

請使用package-a提供產生整數亂數的函數,然後在package-b使用package-a的函數並提供產生一定數量整數亂數陣列的函數,最後在package-c使用到package-b的函數並印出。

現在我們在 @yarn-wks-ex/package-a@yarn-wks-ex/package-b 兩個工作區中都有了 lodash 這個套件,那麼接下來要開始在兩個工作區加入一些功能:

  1. package-a 下加入產生 1 到 100 的整數亂數的函數:
// packages/package-a/index.jsconst _ = require('lodash')const random = () => {
return _.random(1, 100, false)
}
module.exports = random

2. 在 package-b 加入 package-a 這個套件:

// 下方的版本號即是上面在 package-a 的 package.json 中的版本號yarn workspace @yarn-wks-ex/package-b add @yarn-wks-ex/package-a@^1.0.0

請注意一件事情,如果在這裡沒有指定版本將會噴錯:

看來是跑到 yarn registry 去尋找套件了。

那麼接下來就可以在 package-b 下使用到 package-a 提供的函數了。

只要設置正確的話,可以在 vscode 上看到提示。

package-b 下加上 index.js

// packages/package-b/index.jsconst random = require('@yarn-wks-ex/package-a')const createRandomNumberArray = (length = 1) => {
const array = []
for(let i = 0; i < length; i++) {
array.push(random())
}
return array
}
module.exports = createRandomNumberArray

3. 在 package-c 加上 package-b

yarn workspace @yarn-wks-ex/package-c add @yarn-wks-ex/package-b@^1.0.0

package-c 下加入 index.js

const createRandomNumberArray = require('@yarn-wks-ex/package-b')console.log(createRandomNumberArray(5))

修改 package-cpackage.json 並加上 script

// packages/package-c/package.json{
"name": "@yarn-wks-ex/package-c",
"version": "1.0.0",
"main": "index.js",
"license": "MIT",
"scripts": {
"start": "node index.js"
},

"dependencies": {
"@yarn-wks-ex/package-b": "^1.0.0"
}
}

接下來執行 yarn workspace @yarn-wks-ex/package-c start ,我們可以得到預期的結果:

當然你可以在 workspace root 加上 script:

// package.json{
"name": "yarn-wks-ex",
"version": "1.0.0",
"private":true,
"license": "MIT",
"scripts": {
"start": "yarn workspace
@yarn-wks-ex/package-c start"
},

"workspaces": {
"packages": [
"packages/*"
],
"nohoist": []
}
}

這樣在 workspace root 工作區下執行 yarn start 就會獲得同樣的結果了!

nohoist

剛剛有提到 nohoist 的部分,現在我們回過來看下這個功能,不過我們會需要理解一些情況:

試想一個情況,若不同的 workspace 使用了不同版本的套件將會發生什麼事呢?

首先先記住原本專案內的樣子:

原先兩者都是相同版本的樣子

現在在兩個工作區下安裝不同版本的 lodash

yarn workspace @yarn-wks-ex/package-a add lodash@4.17.20yarn workspace @yarn-wks-ex/package-b add lodash@4.17.19

接下來你會看到在 package-a 下多了 node_modules/lodash

安裝兩個不同版本後

來執行 yarn list lodash 看看:

可以看到,原來 yarn 幫我們把 lodash@4.17.19 的版本放在了 root node_modules下,而lodash@4.17.20的版本則留在了 package-a 工作區的 node_modules 下。

那麼我們在做另一個行為看看:

yarn workspace @yarn-wks-ex/package-c add lodash@4.17.20

我們一樣再次執行 yarn list lodash 看看:

root node_modules hoist 的版本改為 lodash@4.17.20

這次我們可以看到 hoist 的版本變為 lodash@4.17.20 了,由此可知,Yarn 會盡量的幫我們去減少需要重複安裝的套件。

現在了解了 hoist 的部分後,就來說 nohoist 的部分吧,其實從字面上就能大概理解,總之就是不要 hoist 吧!

修改 package.json

{
"name": "yarn-wks-ex",
"version": "1.0.0",
"private":true,
"license": "MIT",
"scripts": {
"start": "yarn workspace @yarn-wks-ex/package-c start"
},
"workspaces": {
"packages": [
"packages/*"
],
"nohoist": [
"
@yarn-wks-ex/package-a/lodash"
]

}
}

然後我們重新安裝依賴後檢查看看:

rm yarn.lock
yarn install
yarn list lodash
重新安裝後的結果

可以看到 @yarn-wks-ex/package-a lodash 就被保留在工作區的 node_modules 下了。

也就是說,也許在某些情況,有些依賴在使用上必須非常精確,那麼我們會需要使用到 nohoist,甚至要去指名特定依賴和他的依賴都不需要 hoist,像是這樣:

..."nohoist": [
"**/lodash",
"**/lodash/**"
]
...

而這樣的結果則是:

lodash 並沒有 hoist 到 root node_modules 中

結論

了解 Mono-Repo 的概念是真的很有幫助的,曾經在一個專案中塞了一堆垃圾,但現在更清楚該怎麼切分模組,並且在使用 Worksspace 後我會更加習慣的去降低一些內部模組的相依,整體來說帶給我相當愉快的開發體驗。

--

--