19年,團隊沉澱了組件庫、圖表庫、工具庫等基礎建設相關內容。上述的內容均爲獨立工程維護,起初我們採用 Git Subtree + npm install <folder>
來關聯各個項目,帶來了開發、調試的便利,同時也帶了一些複雜性。
11月份,整個底層穩定性顯著提高,宿主項目中調試等已不是主要問題;我們的新成員 fusion-utils 誕生,由於 fusion-charts 和 fusion-components 同時需要依賴 fusion-utils,使得依賴冗餘&衝突,以及多個獨立倉庫提交繁瑣等問題凸顯出來。因此,我們再出發,選擇了 monorepo 。
選型 monorepo 後,並不是無腦的按照其指導來全部切換,爲了更平穩過度,我們規劃了以下幾步:
- 第一步:採用
yarn workspace
來解決依賴問題(npm install <folder>
就此落幕) - 第二步:深度利用
peerDependencies
等,來處理依賴版本問題 - 第三步:結合
package.json
中 bin字段,利用yarn link
,創建 node 交互式命令行。使用 Commander 開發了大量 CLI,來簡化提交、構建等問題 - 第四步:多倉合一倉(已論證…)
monorepo
Monorepo is a unified source code repository used by an organisation to host as much of its code as possible.
Monorepo
它是一種管理 organisation 代碼的方式,在這種方式下會摒棄原先一個 module 一個 repo 的方式,取而代之的是把所有的 modules 都放在一個 repo 內來管理。-- 這是宗旨,目前團隊已嘗試,但是否全線切換,仍待考量(因爲 依賴問題 已解決,提交複雜性也已通過 CLI 統一)
目前諸如 Babel、React、Angular、Ember、Meteor、Jest 等等都採用了 Monorepo 方式來進行源碼的管理。
monorepo 最終目標:將所有相關 module
都放到一個 repo
裏,每個 module
獨立發佈,issue 和 PR 都集中到該 repo 中。不需要手動去維護每個包的依賴關係,當發佈時,會自動更新相關包的版本號,並自動發佈。
優點:
- 單一(統一)的校驗、構建、測試和發佈流程
- 模塊之間的修改、測試更便捷
- 維護統一的 Issues 地址
- 更容易設置開發環境
缺點:
- 代碼庫體量更大
- 不能直接從 Github 安裝模塊 https://github.com/npm/npm/issues/2974
- monorepo 會產生大量的 commit、branch、tag、git 追蹤的文件也會增多。從而導致:
- commit 增多:
git log
git blame
變慢 - refs 增多:
git clone
git branch
git push
變慢 - tracked 文件增多:
git status
git commit
變慢
- commit 增多:
google、Facebook 花費了大量時間在 monorepo 上。但,也有反對者,強力建議不要使用 monorepo,https://medium.com/@mattklein123/monorepos-please-dont-e9a279be011b
yarn workspace
只需運行一次 yarn install
就可以一次安裝所有軟件包,其具有 hoist 提升功能。
- 依賴關係可以鏈接在一起,這意味着可以相互依賴,同時始終使用最新的可用代碼。比
yarn link
更好的機制,因爲它隻影響工作區樹而不是整個系統(yarn link
會在全局/usr/local/bin
中增加相關記錄,[見下述](###yarn link) - 所有的項目依賴項將一起安裝,從而爲 Yarn 提供了更大的自由度來更好地對其進行優化 – hoist
{
"private": true,
"workspaces": [
"src/charts",
"src/components",
"src/fusion-utils"
]
}
注意:private:true
是必需的!以確保意外地暴露它們。
這裏,src/charts
、src/components
、src/fusion-utils
都是獨立的工程,通過 Git Subtree
來關聯這些項目,然後每個項目中都有獨立的 package.json 文件,在宿主項目中使用 yarn install
統一安裝即可。
{
"name": "fusion-charts",
"version": "1.2.0",
"private": true,
"description": "基於 Vue 和 ECharts 封裝的圖表組件",
"main": "./index.js",
"module": "./index.js",
"dependencies": {
"echarts": "^4.2.0-rc.2"
},
"devDependencies": {
"vue-template-compiler": "^2.6.10"
},
"peerDependencies": {
"fusion-utils": "^1.0.0",
"vue": "^2.6.10"
}
}
安裝完成,具有類似的文件層次結構:
/package.json
/yarn.lock
/node_modules
/node_modules/echarts
/node_modules/vue
/node_modules/vue-template-compiler
/node_modules/fusion-charts -> src/charts
/node_modules/fusion-components -> src/components
/node_modules/fusion-utils -> src/fusion-utils
/src/charts/package.json
/src/components/package.json
/src/fusion-utils/package.json
echarts、vue 等均安裝到了根目錄下。代碼中對於 fusion-charts 等引用要使用 /workspace-a/package.json#name
字段(上述,name 字段爲 fusion-charts),而不是文件夾名稱 charts。import {...} from 'fusion-charts'
hoist
獨立項目
爲了減少冗餘,大多數程序包管理器採用某種提升方案將所有相關模塊儘可能地提取並展平到一個集中位置。依賴關係樹可能像這樣:
能夠消除重複的 [email protected]
和 [email protected]
,同時保留版本差異([email protected]
)。通過從項目根目錄遍歷 “node_modules” 樹,大多數模塊 crawlers/loaders/bundlers 可以非常有效地定位模塊。
monorepo 項目
通過將子模塊提升到其父項目的node_modules:monorepo/node_modules
來在子項目/程序包之間共享模塊。解決了相互依賴時的冗餘度(如,fusion-charts
、fusion-components
都要引用 fusion-utils
)。
依賴丟失?
至此,可以從項目的根 node_modules
訪問所有模塊,但我們通常會在其本地項目中構建每個程序包,這些模塊在其自己的 node_modules
下可能不可見。
- 在項目根目錄 “monorepo” 中找不到模塊 “[email protected]”(無法遵循符號鏈接 – symlink)
- “package-1” 中找不到模塊
[email protected]
(不知道上面 “monorepo” 中的模塊樹)
爲了使這個 monorepo 項目能夠從任何地方可靠地找到任何模塊,它需要遍歷每個 “node_modules” 樹:monorepo/nodemodules
和 monorepo/packages/package-1/node_modules
。
nohoist
禁止將選定的模塊提升到項目根目錄
"workspaces": {
"packages": ["packages/*"],
"nohoist": ["**/react-native", "**/react-native/**"]
}
yarn link
創建一個 nodejs 命令行包
cli.js
#!/usr/bin/env node
告訴*nix系統,我們的 JavaScript 文件的解釋器應該是 /usr/bin/env
節點
現在我們可以在 Linux 或 Mac OS X 上以 ./cli.js
或在 Windows 中使用 node cli.js
來運行它
package.json
bin 是一個讓 Yarn 在包安裝時給包創建 cli 命令(二進制)的映射表。
"bin": {
"fusion-smartV-build-cli": "./bin/cli.js"
}
yarn/npm link
命令允許我們在本地 “symlink a package folder”,它將在本地安裝 package.json
的 bin 字段中列出的任何命令。根目錄下直接執行 yarn link
即可。
yarn link
一個包可以鏈接到另一個項目
- 在你想連接的包裏,運行
yarn link
- 使用
yarn link [package]
來鏈接另一個你想在當前項目裏使用的本地包
$ cd project1
$ yarn link
$ cd project2
$ yarn link project1
這會創建一個符號鏈接 project2/node_modules/project1
連接到你本地的project1
項目副本
peerDependencies
peerDependencies
的目的是提示宿主環境去安裝滿足插件peerDependencies所指定依賴的包,然後在插件import或者require所依賴的包的時候,永遠都是引用宿主環境統一安裝的npm包,最終解決插件與所依賴包不一致的問題。
"peerDependencies": {
"fusion-utils": "^1.0.0",
"vue": "^2.6.10"
}
它要求宿主環境安裝 fusion-utils^@1.0.0
和 [email protected]
的版本。組件中引入的 fusion-utils 和 vue 包其實都是宿主環境提供的依賴包。
參考地址
- https://juejin.im/entry/586f00bc128fe100580a6f78
- https://www.toptal.com/front-end/guide-to-monorepos
- https://yarn.bootcss.com/docs/workspaces/
- https://medium.com/netscape/a-guide-to-create-a-nodejs-command-line-package-c2166ad0452e