一。定位
Lerna is a tool that optimizes the workflow around managing multi-package repositories with git and npm.
多模組管理工具,用來幫助維護 monorepo
P.S.Lerna 是 Babel 自己日用並開源的工具,見 Why is Babel a monorepo?
二。monorepo
monorepo(monolithic repository),與 multirepo 相對,分別是單代碼倉庫與多代碼倉庫(one-repository-per-module)
multirepo 即傳統做法,按模組分為多個代碼庫,實踐中發現一些問題:
-
issue 管理混亂,經常有在 core repo 提 module 問題的,需要 Close this and track that
-
changelog 難以整合,需要人工梳理所有變動的倉庫,並做整合
-
core repo 版本更新麻煩,需要同步所有 module 更新其依賴的 core repo 版本
monorepo 把所有相關 module 都放到一個 repo 裡,每個 module 獨立發布,但使用與該 repo 統一的版本號(例如 Babel 和 React),issue 和 PR 都集中到該 repo,changelog 可以簡單地從一份 commit 列表梳理出來(甚至如果按照 commit 規範關聯 issue tag 的話,能夠自動生成規範的 changelog)
monorepo 也存在一些問題,但不如上面提到的痛點強烈:
-
repo 體積較大,可能帶來版本控制的問題(Git 不適合管理體積太大的 repo)
-
統一構建工具,對構建工具提出了更高要求,要能構建各種相關 module
從源碼管理的角度來看,multirepo 與 monorepo 是兩種不同的理念,前者允許多元化發展,各個 module 可以有自己的玩法(構建,依賴管理,單元測試等),後者希望集中管理,減少玩法差異帶來的溝通��本
monorepo 標誌性的特徵是目錄結構,例如 React:
react-16.2.0/
packages/
react/
react-art/
react-.../
每個 module 都有自己的依賴項(package.json),能夠作為獨立的 npm package 發布,只是源碼放在一起維護
典型案例:
P.S. 之前使用 rollup 遇到問題都先去主 repo 查相關 issue,再根據線索找到對應的 plugin repo,再查相關 issue。一直感覺異常麻煩,又說不出來哪裡不對,原來是源碼組織方式帶來的困擾
三。lerna 試玩
// 安裝
npm install lerna -g
git init hoho-lerna && cd hoho-lerna
// 初始化目錄結構
lerna init
得到如下結構:
hoho-lerna/
packages/
lerna.json
package.json
創建 module:
mkdir packages/hoho-lerna-core && cd packages/hoho-lerna-core
npm init
這樣最終會得到一堆 package:
packages/
hoho-lerna-core/
package.json
hoho-lerna-module-a/
package.json
hoho-lerna-module-b/
package.json
module.../
我們實際做的事情是按模組拆分成 package,並(通過 module 級的 package.json)聲明了各 package 之間的依賴關係
依賴處理
如果 moduleA 依賴 core,通過 lerna bootstrap 命令處理依賴過後,會在 moduleA 的 node_modules 下創建軟鏈接指向 core 目錄,有一隻 活生生的例子
注意:npm不會自動安裝 peerDependencies,lerna 也不提供這個服務
lerna bootstrap 按照之前聲明的依賴關係,通過建立軟鏈接來把各 package 實際關聯起來
發布 package
既然都放在 packages 裡了,容易統一管理,所以支持一鍵發布所有 package 到 npm
P.S. 先要有 npm 賬號(自行 註冊),並 npm adduser 添加到本地配置裡
準備好之後,迫不及待的開始一箭 n 星:
lerna publish
不出意外的話,會得到類似輸出:
lerna info version 2.7.0
lerna info current version 0.0.0
lerna info Checking for updated packages...
lerna info Comparing with initial commit.
lerna info Checking for prereleased packages...
? Select a new version (currently 0.0.0) Major (1.0.0)
Changes:
- hoho-lerna-core: 1.0.0 => 1.0.0
- hoho-lerna-module-a: 1.0.0 => 1.0.0
- hoho-lerna-module-b: 1.0.0 => 1.0.0
? Are you sure you want to publish the above changes? Yes
lerna info publish Publishing packages to npm...
lerna info published hoho-lerna-module-b
lerna info published hoho-lerna-core
lerna info published hoho-lerna-module-a
lerna info git Pushing tags...
Successfully published:
- hoho-lerna-core @1.0.0
- hoho-lerna-module-a @1.0.0
- hoho-lerna-module-b @1.0.0
lerna success publish finished
然後,npm registry 裡就多了 3 個垃圾 package……
publish 的大致過程是:
-
本地打個 tag(例如
git tag v1.0.0) -
自動更新依賴項版本號 示例
-
然後把各個 package 發布到 npm
-
最後把 tag 和相應的 commit 給 push 上去
注意:如果發布到 npm 這一步失敗了的話(比如沒配置 npm 賬號),下一次直接 lerna publish 無法直接發布,貌似因為本地 tag 已經是 v1.0.0 認為上次發布成功了。把這個 tag 手動滾掉也不行,.git 裡可能記了一些發布狀態,滾掉之後出現 commit hash 匹配錯誤,這裡不太友好
P.S. 更多命令請查看 Lerna
自動生成 changelog
先安裝 changelog 工具:
npm install lerna-changelog -g
然後在 lerna.json 添加對應配置項:
"changelog": {
"repo": "ayqy/hoho-lerna",
"labels": {
"enhancement": ":rocket: Enhancement",
"bug": ":bug: Bug Fix",
"doc": "Refine Doc",
"feat": "New Feature"
},
"cacheDir": ".changelog"
}
特別注意:repo 是必填的,說是能自動推斷,實際上不太靠譜,見 The 'repo' field automatically inferred failed, but no error occurred
P.S.labels 裡,key 是要在 Github 配置的標籤,用來給 Issue/PR 分類,value 裡的:bug:只是調皮的 emoji,會作為 changelog 裡該類 change 的標題
到這裡還不算完,還需要 Github repo 權限(為了能查 Issue、PR),把 token 以環境變量的形式暴露出來(常用的話,可以添到 ~/.bash_profile 裡):
export GITHUB_AUTH="..."
配置完畢。要達到「自動」,前提是日常開發維護遵守約定的規範,否則最後工具肯定猜不出來 changelog。規範是指:
-
(建議)commit message 關聯上對應的 issue
-
(必須)創建 PR 時要選擇我們預定義的 label
因為工具只整理 github 帶有指定 label 的 PR,並把 commit message 作為 changelog 項,建議 commit message 裡關聯上 issue,生成的 changelog 就能關聯到對應 issue:
Uses github PR/Issue names categorized by labels with configurable headings.
例如:
git cm -m "feat: changelog, Close #1"
然後提交 PR 並給貼上 label:feat,merge 之後,本地 pull 過來試試 lerna-changelog:
## Unreleased (2018-01-13)
#### New Feature
* [#2](https://github.com/ayqy/hoho-lerna/pull/2) feat: changelog, Closes [#1](https://github.com/ayqy/hoho-lerna/issues/1). ([ @ayqy](https://github.com/ayqy))
#### Committers: 1
- 黯羽輕揚 ([ayqy](https://github.com/ayqy))
相當漂亮:https://github.com/ayqy/hoho-lerna/releases/tag/v1.1.0
P.S. 應該在 .gitignore 忽略掉本地生成的 changelog 臨時文件,僅在發布新版本時本地 lerna-changelog,並把生成的 changelog 貼到 release note。不自動發布 release note 可能是 API 限制或出於慎重考慮,畢竟 release note 還是比較重要的
另外,以這種方式自動整理出 changelog,實際上靠的是開發中約束(PR 的 label 規範,commit message 作為 changelog 項的規範),與 lerna 沒有太大關係,只要是 monorepo(Issue/PR)都放在一起,就可以按照這個思路獲取 Issue/PR 信息,整理出 changelog
相當於把最後梳理 changelog 的巨大工作量分佈到日常開發維護了,change 都要走 PR,而且要有 issue 記錄,不習慣的話還是很麻煩的(有要求 commit message 自帶 label 而不走 PR 的 呼聲,以後應該會支持)
四。適用場景
哪些場景可以採用 monorepo(並用 lerna 管理?)?
-
不過分龐大的項目,整合到一起有 100G 源碼的話,還是再考慮一下吧
-
多模組/插件化項目,把官方維護的插件都作為 package 非常合適
另外,還需要:
-
基礎建設
-
團隊信任
基礎建設是指強大的構建工具,能滿足所有模組的 build 需求(純前端項目的話,build 壓力不大)
monorepo 環境下,可以並且鼓勵改別人的代碼,一方面需要持續集成機制(例如 React - CircleCI)確認修改帶來的影響,另一方面還需要不同團隊之間互相信任,否則會經常出現一個團隊的變更影響了另一個團隊的情況,需要回滾掉別人的修改,反而影響效率
P.S.Lerna 出來很久了(和 Babel 差不多年紀),很多項目都在用了
參考資料
-
Lerna:很簡練的官方文檔
-
monorepo 新浪潮 | introduce lerna:前輩的 helloworld 還不錯
-
Mono Repository Tool Comparison:monorepo 工具對比
-
New wave modularity with Lerna, monorepos, and npm organizations
暫無評論,快來發表你的看法吧