首先我們要先問:
什麼時候會使用到 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 的時候,則有兩個選擇:
- 在 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 下:
實作
接下來我做個簡單的範例來說明 Yarn Workspace 的功能:
請使用
package-a
提供產生整數亂數的函數,然後在package-b
使用package-a
的函數並提供產生一定數量整數亂數陣列的函數,最後在package-c
使用到package-b
的函數並印出。
現在我們在 @yarn-wks-ex/package-a
與 @yarn-wks-ex/package-b
兩個工作區中都有了 lodash 這個套件,那麼接下來要開始在兩個工作區加入一些功能:
- 在
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
請注意一件事情,如果在這裡沒有指定版本將會噴錯:
那麼接下來就可以在 package-b
下使用到 package-a
提供的函數了。
在 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-c
的 package.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
看看:
這次我們可以看到 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/**"
]
...
而這樣的結果則是:
結論
了解 Mono-Repo 的概念是真的很有幫助的,曾經在一個專案中塞了一堆垃圾,但現在更清楚該怎麼切分模組,並且在使用 Worksspace 後我會更加習慣的去降低一些內部模組的相依,整體來說帶給我相當愉快的開發體驗。