[TOC]
# 什么是 monorepo
美 `[mɑ?no??ri?po?]`
[monorepo-starter](https://github.com/thinkmill/monorepo-starter)
monorepo 是一個單個 git 存儲庫,可容納多個應用程序和庫的源代碼以及它們的工具。
目前 Babel, Angular, React,Vue, Ember, Meteor, Jest等許多開源項目都使用該種模式來管理代碼。

# 常見
## monorepo 就一定需要專門的工具(庫)才能實現嗎?
答案當然是否定的,嚴格意義上說,只要將多個項目放在一個存儲庫里就算 monorepo。
現在主流的 monorepo 所承擔的責任并不只是存儲的問題,還可以承擔比如依賴管理,增量構建等一系列工程化的功能,已經成為工程化技術中非常有價值的一塊領域,所以有時你為了實現某個特殊的功能不得不借助社區的力量,或者站在大佬的肩膀上。
## monorepo 的好處
* **共享代碼和可見性** - 在整個組織中保持代碼 DRY。在代碼庫中重用驗證代碼、UI組件和類型。在后端、前端和程序庫之間重用代碼。
* **原子性的更改** - 更改一個API接口,并在同一提交中修改使用了該 API 的下游應用程序。您可以在同一個提交中更改共享庫中的按鈕組件和使用該組件的應用程序。monorepo 省去了在多個存儲庫之間協調提交的麻煩。
* **開發人員的移動性** - 以一致的方式構建和測試使用不同工具和技術編寫的應用程序。開發人員可以自信地為其他團隊的應用程序做出貢獻,并驗證他們的更改是安全的。
* **單一版本的依賴項** - 使用所有第三方依賴項的單個版本,從而減少了應用程序之間的不一致性。對于較少積極開發的應用程序仍然會保持使用到最新版本的框架,庫或構建工具。
Angular,
## 復用 packages:workspace
接下來,簡單羅列一下各個包管理工具的 workspaces 特性和注意點(主觀觀點:從上至下,由差到好)。
| **包管理工具** | **準備工作** | **依賴統一管理** | **添加本地子項目為依賴** | **注意點** |
| --- | --- | --- | --- | --- |
| [yarn2](https://yarnpkg.com/features/workspaces) | 配置`workspaces`字段 | ? 支持 | ?? 支持,但使用的是 yarn2 的 workspace 協議 | 功能很多,基本可以和大型的 monorepo 解決方案媲美。 但 yarn2 自身可用性和穩定性尚待考驗,請謹慎選擇。 |
| [yarn](https://classic.yarnpkg.com/en/docs/workspaces/) | 配置`workspaces`字段 | ?? 默認不支持(需要關閉`nohoist`) | ?? 支持,但第一次添加依賴需要指定版本號 | 不支持子項目為依賴 yarn 的 bug 太多了,官方不怎么維護。 如果沒有太多需求需要實現,可以一試。 |
| [npm](https://docs.npmjs.com/cli/v7/using-npm/workspaces#using-workspaces) | 配置`workspaces`字段 | ?? 支持(子項目需要通過專門的指令) | ? 支持 | 起步較晚,需要很新版本的 npm 但是功能穩定且實用,值得一試 |
| [pnpm](https://pnpm.io/zh/workspaces) | 添加`pnpm-workspace.yaml`文件 | ? 支持 | ?? 支持,但使用的是 pnpm 的 workspace 協議 | 好使 ?? |
使用 monorepo 策略后,收益最大的兩點是:
1. 避免重復安裝包,因此減少了磁盤空間的占用,并降低了構建時間;
2. 內部代碼可以彼此相互引用;
這兩項好處全部都可以由一個成熟的包管理工具來完成,對前端開發而言,即是 yarn(1.0 以上)或 npm(7.0 以上)通過名為 workspaces 的特性實現的;推薦使用 pnpm;
為了實現前面提到的兩點收益,您需要在代碼中做三件事:
1. 調整目錄結構,將相互關聯的項目放置在同一個目錄,推薦命名為 **packages**;
2. 在項目根目錄里的`package.json`文件中,設置`workspaces`屬性,屬性值為之前創建的目錄;
3. 同樣,在`package.json`文件中,設置`private`屬性為true(為了避免我們誤操作將倉庫發布);
經過修改,您的項目目錄看起來應該是這樣:
~~~
.
├── package.json // `devDependencies`and`scripts`for the monorepo
└── packages/
├── @mono/project_1 // 社區推薦使用 `@<項目名>/<子項目名>` 的方式命名
│ ├── index.js
│ └── package.json // `dependencies`, unique`devDependencies`and`scripts`for the package
└── @mono/project_2/
│ ├── index.js
│ └── package.json
~~~
至此,我們已經完成了 monorepo 策略的核心部分,實在是很容易不是嗎?但是老話說「行百里者半九十」,距離優雅的搭建一個 monorepo 項目,我們還有一些路要走。
# lerna
`mono-repo`最出名是使用 [Lerna](https://github.com/lerna/lerna/) 管理 workspaces。(參見:Node - 常用庫)
* 使用`lerna bootstrap --hoist`可以將子項目的`node_modules`提升到頂層,解決`node_modules`重復的問題。
* 但是`lerna bootstrap --hoist`在提升時如果遇到各個子項目引用的依賴版本不一致,會提升使用最多的版本,從而導致少數派那個找不到正確的依賴,發生錯誤。
* 為了解決提升時版本沖突的問題,引入了`yarn workspace`,他也會提升用的最多的版本,但是會為少數派保留自己的依賴在自己的`node_modules`下面。
* 發布的時候使用`lerna publish`,它會自動更新內部依賴,并更新各個子項目自己的版本號。
* 子項目的版本號規則可以在`lerna.json`里面配置,如果配置為固定版本號,則各個子項目保持一致的版本,如果配置為`independent`關鍵字,各個子項目可以有自己不同的版本號。
## pnpm
[pnpm](https://pnpm.io/zh/workspaces) 是一個包管理器,它將`node_modules`從一個單一的可尋址存儲連接(links)起來,使得安裝速度更快,`node_module`的目錄更小。它有像 yarn workspaces 這樣的工作空間功能,當涉及到 node 應用時,它是 monorepo 開發的一個很好的選擇。
# 統一配置:合并同類項
( - Eslint,Typescript 與 Babel 等)
您一定同意,編寫代碼要遵循 DRY 原則(Don't Repeat Yourself 的縮寫)。那么,理所當然地,我們應該盡量避免在多個子項目中放置重復的 eslintrc,tsconfig 等配置文件。幸運的是,Babel,Eslint 和 Typescript 都提供了相應的功能讓我們減少自我重復。
## TypeScript
我們可以在 packages 目錄中放置`tsconfig.settting.json`文件,并在文件中定義通用的 ts 配置,然后,在每個子項目中,我們可以通過`extends`屬性,引入通用配置,并設置`compilerOptions.composite`的值為`true`,理想情況下,子項目中的`tsconfig`文件應該僅包含下述內容:
```
{
"extends": "../tsconfig.setting.json", // 繼承 packages 目錄下通用配置
"compilerOptions": {
"composite": true, // 用于幫助 TypeScript 快速確定引用工程的輸出文件位置
"outDir": "dist",
"rootDir": "src"
},
"include": ["src"]
}
```
## Eslint
對于 Eslint 配置文件,我們也可以如法炮制,這樣定義子項目的`.eslintrc`文件內容:
~~~
{
"extends": "../../.eslintrc", // 注意這里的不同
"parserOptions": {
"project": "tsconfig.json"
}
}
~~~
注意到了嗎,對于通用的 eslint 配置,我們并沒有將其放置在 packages 目錄中,而是放在整個項目的根目錄下,這樣做是**因為一些編輯器插件只會在項目根目錄尋找**`.eslintrc`文件,因此為了我們的項目能夠保持良好的「開發環境一致性」,請務必將通用配置文件放置在項目的根目錄中。
## Babel
> [Config Files · Babel](https://babeljs.io/docs/en/config-files#monorepos)
Babel 配置文件合并的方式與 TypeScript 如出一轍,甚至更加簡單,我們只需在子項目中的`.babelrc`文件中這樣聲明即可:
~~~
{ "extends": "../.babelrc" }
~~~
當一切準備就緒后,我們的項目目錄應該大致呈如下所示的結構:
```
.
├── package.json
├── .eslintrc
└── packages/
│ ├── tsconfig.settings.json
│ ├── .babelrc
├── @mono/project_1/
│ ├── index.js
│ ├── .eslintrc
│ ├── .babelrc
│ ├── tsconfig.json
│ └── package.json
└───@mono/project_2/
├── index.js
├── .eslintrc
├── .babelrc
├── tsconfig.json
└── package.json
```
# 當然還有其他事項
## 統一命令腳本
scripty
node script
## npm 包本地發布
體驗一把使用 管理/發布 monorepo 項目的感覺。將示例代碼發布到真實世界的 npm 倉庫并非一個好主意,可以使用 Verdaccio 在本地創建一個 npm 倉庫作為代理。
## 格式化 commit 信息
husky
commitlint
## changelog
#### lerna 主要能力:
1. 生成新版本。
2. lerna-changelog只能生成一套changelog,所有包都一樣
3. 一鍵發布所有包
#### changesets主要能力:
1. 每個包分別生成新版本。
2. 分別生成changelog
3. 一鍵發布
changesets 相比于 lerna 最關鍵的地方在于更**靈活**和更**細粒度**的控制每個 package。
## lint、測試
eslint、prettier、 lint-staged
## 現有項目遷移至 monorepo 項目的方法
使用 tomono,其內容是一個 shell 腳本
# JavaScript Monorepo 工具
> [JavaScript Monorepo Tooling](https://dev.to/hipstersmoothie/javascript-monorepo-tooling-48b9)
這些工具的功能可以組織成3個功能:
`installer` - 幫助安裝 monorepo 依賴項的工具
`task-runner` - 幫助在整個 repo 中運行命令或腳本的工具,并可能在repo中創建新package
`publisher` - 幫助/強制向 monorepo 進行版本控制的工具
某些工具具有多種功能,可以包含多種功能。
## Turbo
[Turborepo](https://turborepo.org/)
Turborepo是一個高性能的 JavaScript 和 TypeScript 代碼庫構建系統。
## Syncpack
[Syncpack](https://github.com/JamieMason/syncpack) 是 monorepo 依賴管理工具。如果你曾經糾結于軟件包之間的細微依賴差異,或者維護統一的`package.json` 。那么你就可以想象 Syncpack 有多有用了
## preconstruct
[preconstruct/preconstruct](https://github.com/preconstruct/preconstruct)
?? Dev and build your code painlessly in monorepos
## Ultra Runner
[folke/ultra-runner: ??? Ultra fast monorepo script runner and build tool (github.com)](https://github.com/folke/ultra-runner)
???? 超快速 monorepo 腳本運行器和構建工具
## manypkg
[Thinkmill/manypkg: ?? An umbrella for your monorepo (github.com)](https://github.com/Thinkmill/manypkg)
## rushjs
[@microsoft/rush](https://rushjs.io)
Rush 是由微軟開發的 monorepo 解決方案,它可以結合 pnpm 來使用,所以解決了很多 yarn 和 npm 的問題,同時具備著更強大的鏈路設施。
## Bit
## nx
[Nx: Smart, Extensible Build Framework](https://nx.dev/)
https://github.com/nrwl/nx
隨著存儲源代碼的 monorepo 策略的日益流行,在 monorepo 中管理項目的工具也越來越多,比如[Bazel](https://bazel.build/)、[Lerna](https://lerna.js.org/)、[changesets](https://github.com/atlassian/changesets)、[Rush](https://rushjs.io/)或[Nx](https://nx.dev/)。
NX 由 [NRWL](https://nrwl.io/) 發布,該公司是一家咨詢公司,該公司的使命是幫助客戶更好地構建軟件并更快。NRWL 專為幫助軟件組織而制造的 NX 更加富有成效,以更安全,可預測和可擴展的方式組織其代碼。NRWL 正在積極構建 NX 周圍的社區,包括博客帖子,培訓和咨詢,以協助組織采用 NX。
## @nrwl/tao
[@nrwl/tao](https://github.com/nrwl/nx)
## Bolt
[Bolt](https://github.com/boltpkg/bolt) 旨在成為一個“超級功能JavaScript項目管理工具”。
Bolt 在 Yarn 的基礎上實現了 workspaces 的概念。Bolt CLI 在很大程度上是 Yarn CLI 的替代品,你可以在任何 Yarn 項目中使用它。
## multipack
[episclera/multipack: ?? All in one tool for monorepos. (github.com)](https://github.com/episclera/multipack)
# 參考
> [korfuri/awesome-monorepo: A curated list of awesome Monorepo tools, software and architectures.](https://github.com/korfuri/awesome-monorepo)
> [如何利用 monorepo 策略管理代碼?](https://mp.weixin.qq.com/s/ZP2IigNVSIZKGuYxNYOYgw)
> [Monorepo 的這些坑,我們幫你踩過了! (qq.com)](https://mp.weixin.qq.com/s/PIdmJ2cHmq9QBj6MBJ9ygQ)
- 講解 Markdown
- 示例
- SVN
- Git筆記
- github 相關
- DESIGNER'S GUIDE TO DPI
- JS 模塊化
- CommonJS、AMD、CMD、UMD、ES6
- AMD
- RequrieJS
- r.js
- 模塊化打包
- 學習Chrome DevTools
- chrome://inspect
- Chrome DevTools 之 Elements
- Chrome DevTools 之 Console
- Chrome DevTools 之 Sources
- Chrome DevTools 之 Network
- Chrome DevTools 之 Memory
- Chrome DevTools 之 Performance
- Chrome DevTools 之 Resources
- Chrome DevTools 之 Security
- Chrome DevTools 之 Audits
- 技巧
- Node.js
- 基礎知識
- package.json 詳解
- corepack
- npm
- yarn
- pnpm
- yalc
- 庫處理
- Babel
- 相關庫
- 轉譯基礎
- 插件
- AST
- Rollup
- 基礎
- 插件
- Webpack
- 詳解配置
- 實現 loader
- webpack 進階
- plugin 用法
- 輔助工具
- 解答疑惑
- 開發工具集合
- 花樣百出的打包工具
- 紛雜的構建系統
- monorepo
- 前端工作流
- 爬蟲
- 測試篇
- 綜合
- Jest
- playwright
- Puppeteer
- cypress
- webdriverIO
- TestCafe
- 其他
- 工程開發
- gulp篇
- Building With Gulp
- Sass篇
- PostCSS篇
- combo服務
- 編碼規范檢查
- 前端優化
- 優化策略
- 高性能HTML5
- 瀏覽器端性能
- 前后端分離篇
- 分離部署
- API 文檔框架
- 項目開發環境
- 基于 JWT 的 Token 認證
- 扯皮時間
- 持續集成及后續服務
- 靜態服務器搭建
- mock與調試
- browserslist
- Project Starter
- Docker
- 文檔網站生成
- ddd