使用 Lerna 管理模塊

原文

www.lishuaishuai.com/engineering…html

前言

對於維護過多個package的同窗來講,都會遇到一個選擇:這些package是放在一個倉庫裏維護仍是放在多個倉庫裏單獨維護,數量較少的時候,多個倉庫維護不會有太大問題,可是當package數量逐漸增多時,一些問題逐漸暴露出來:前端

  1. package之間相互依賴,開發人員須要在本地手動執行npm link,維護版本號的更替;
  2. issue難以統一追蹤,管理,由於其分散在獨立的repo裏;
  3. 每個package都包含獨立的node_modules,並且大部分都包含babel,webpack等開發時依賴,安裝耗時冗餘而且佔用過多空間。

Monorepo vs Multirepo

Monorepo 的全稱是 monolithic repository,即單體式倉庫,與之對應的是 Multirepo (multiple repository),這裏的「單」和「多」是指每一個倉庫中所管理的模塊數量。node

Multirepo 是比較傳統的作法,即每個模塊都單獨用一個倉庫來進行管理,典型案例有 webpack,優缺點總結以下:webpack

優勢:git

  • 各模塊管理自由度較高,可自行選擇構建工具,依賴管理,單元測試等配套設施
  • 各模塊倉庫體積通常不會太大

缺點:github

  • issue 管理混亂,在實際使用中會發現 core repo 中常常會出現對一些針對 module 提出的問題,須要作 issue 遷移或關聯
  • changlog 沒法關聯,沒法很好的自動關聯各個 module 與 core repo 之間的變更聯繫
  • 版本更新繁瑣,若是 core repo 的版本發生了變化,須要對全部的 module 進行依賴 core repo 的更新
  • 測試複雜,對多個相關聯 module 測試繁瑣

Monorep 是把全部相關的 module 都放在一個倉庫裏進行管理,每一個 module 獨立發佈,典型案例有 babel,優缺點總結以下:web

優勢:npm

  • 管理簡便,issue 和 PR 都放在一個倉庫中進行維護
  • changelog 維護簡便,全部changelog 都基於同一份 commit 列表
  • 版本更新簡便,core repo 以及各模塊版本發生變動後能夠很簡便的同步更新其他全部對其有依賴的 module

缺點:json

  • 倉庫體積增加迅速,隨着 module 的增多,倉庫的體積會變得十分龐大
  • 自由度較低,高度的統一致使各個模塊的自由度較低,且對統一的配套工具(構建,測試)等要求較高,要能適配各個 module 的要求

Lerna 是什麼

A tool for managing JavaScript projects with multiple packages.bootstrap

Lerna is a tool that optimizes the workflow around managing multi-package repositories with git and npm.

Lerna 是一個管理多個 npm 模塊的工具,是 Babel 本身用來維護本身的 Monorepo 並開源出的一個項目。優化維護多包的工做流,解決多個包互相依賴,且發佈須要手動維護多個包的問題。Lerna 如今已經被不少著名的項目組織使用,如:Babel, React, Vue, Angular, Ember, Meteor, Jest 。

一個基本的 Lerna 倉庫結構以下:

my-lerna-repo/
    ┣━ packages/
    ┃     ┣━ package-1/
    ┃     ┃      ┣━ ...
    ┃     ┃      ┗━ package.json
    ┃     ┗━ package-2/
    ┃            ┣━ ...
    ┃            ┗━ package.json
    ┣━ ...
    ┣━ lerna.json
    ┗━ package.json
複製代碼

使用

初始化

全局安裝 lerna,再執行相關命令

$ npm i -g lerna
$ mkdir lerna-repo && cd $_
$ lerna init
複製代碼

lerna init 命令會建立一個用來配置的 lerna.json,文件以及用於存放全部 modulepackages 文件夾,以下:

lerna-repo/
    ┣━ packages/
    ┣━ lerna.json
    ┗━ package.json
複製代碼

Lerna 提供兩種不一樣的方式來管理你的項目:FixedIndependent,默認採用 Fixed 模式,若是你想採用 Independent 模式,只需在執行 init 命令的時候加上 --independent-i 參數便可。

Fixed/Locked 模式(默認) 固定模式下 Lerna 項目在單一版本線上運行。版本號保存在項目根目錄下 lerna.json 文件中的 version 下。當你運行 lerna publish 時,若是一個模塊自上次發佈版本之後有更新,則它將更新到你將要發佈的新版本。這意味着你在須要發佈新版本時只需發佈一個統一的版本便可。

Independent 模式(--independent) 獨立模式下 Lerna 容許維護人員獨立地的迭代各個包版本。每次發佈時,你都會收到每一個發生更改的包的提示,同時來指定它是 patchminormajor 仍是自定義類型的迭代。

在獨立模式下,lerna.json 文件中 version 屬性的值將被忽略。

安裝依賴

爲全部項目安裝依賴,相似於 npm i

$ lerna bootstrap
複製代碼

當執行完上面的命令後,會發生如下的行爲:

  1. 在各個模塊中執行 npm install 安裝全部依賴
  2. 將全部相互依賴的 Lerna 模塊 連接在一塊兒
  3. 在安裝好依賴的全部模塊中執行 npm run prepublish
  4. 在安裝好依賴的全部模塊中執行 npm run prepare

爲packages文件夾下的package安裝依賴

$ lerna add <package>[@version] [--dev] [--exact] # 命令簽名

當咱們執行此命令後,將會執行下面那2個動做:

- 在每個符合要求的模塊裏安裝指明的依賴包,相似於在指定模塊文件夾中執行 `npm install <package>`。
- 更新每一個安裝了該依賴包的模塊中的 `package.json` 中的依賴包信息

# 例如
$ lerna add module-1 --scope=module-2 # 將 module-1 安裝到 module-2
$ lerna add module-1 --scope=module-2 --dev # 將 module-1 安裝到 module-2 的 devDependencies 下
$ lerna add module-1 # 將 module-1 安裝到除 module-1 之外的全部模塊
$ lerna add babel-core # 將 babel-core 安裝到全部模塊
複製代碼

卸載依賴

$ lerna exec -- <command> [..args] # 在全部包中運行該命令

# 例如
$ lerna exec --scope=npm-list  yarn remove listr # 將 npm-list 包下的 listr 卸載
$ lerna exec -- yarn remove listr # 將全部包下的 listr 卸載
複製代碼

清理依賴包

能夠經過 clean 命令來快速刪除全部模塊中的 node_modules 文件夾。基本命令以下:

$ lerna clean
複製代碼

檢測模塊是否發生過變動

$ lerna updated
# 或
$ lerna diff
複製代碼

建立模塊

Lerna 提供了兩種建立或導入模塊的方式,分別是 createimport

create

建立一個 lerna 管理的模塊。基本命令格式以下:

$ lerna create <name> [loc]
複製代碼

name 是模塊的名稱(必填項,可包含做用域,如 @uedlinker/module-a),必須惟一且能夠發佈(npm 倉庫中無重名已發佈包)

loc 是自定義的包路徑(選填), 會根據你在 lerna.json 文件中的 packages 的值去匹配,默認採用該數組的第一個路徑,指定其餘路徑時只要寫明路徑中的惟一值便可,例如想選擇 /user/lerna-repo/modules 這個路徑,只須要執行以下命令便可

命令執行完後,lerna 會幫咱們在指定位置建立模塊的文件夾,同時會默認在該文件夾下執行 npm init 的命令,在終端上根據根據提示填寫全部信息後會幫咱們建立對應的 package.json 文件,大體的結構以下

lerna-repo/
    ┣━ packages/
    ┃     ┗━ package-a/
    ┃            ┣━ ...
    ┃            ┗━ package.json
    ┣━ lerna.json
    ┗━ package.json
複製代碼

import

導入一個已存在的模塊,同時保留以前的提交記錄,方便將其餘正在維護的項目合併到一塊兒。基本命令格式以下:

$ lerna import <dir>
複製代碼

dir 是本項目外的包含 npm 包的 git 倉庫路徑(相對於本項目根路徑的相對路徑)

執行後會將該模塊總體複製到指定的依賴包存放路徑下,同時會把該模塊以前全部提交記錄合併到當前項目提交記錄中

查看模塊列表

建立完畢以後,咱們能夠經過 list 命令來查看和確認如今管理的包是否符合咱們的預期,執行以下命令:

$ lerna list
複製代碼

運行 script 腳本

lerna run 運行 npm script,能夠指定具體的 package。

$ lerna run <script> -- [..args] # 在全部包下運行指定

# 例如
$ lerna run test # 運行全部包的 test 命令
$ lerna run build # 運行全部包的 build 命令
$ lerna run --parallel watch # 觀看全部包並在更改時發報,流式處理前綴輸出

$ lerna run --scope my-component test # 運行 my-component 模塊下的 test
複製代碼

版本迭代

lerna 經過 version 命令來爲各個模塊進行版本迭代。基本命令以下:

$ lerna version [major | minor | patch | premajor | preminor | prepatch | prerelease]
複製代碼

若是不選擇這次迭代類型,則會進入交互式的提示流程來肯定這次迭代類型

例如:

$ lerna version 1.0.1 # 按照指定版本進行迭代
$ lerna version patch # 根據 semver 迭代版本號最後一位
$ lerna version       # 進入交互流程選擇迭代類型 
複製代碼

注意: 若是你的 lerna 項目中各個模塊版本不是按照同一個版本號維護(即建立時選擇 independent 模式),那麼會分別對各個包進行版本迭代

當執行此命令時,會發生以下行爲:

  1. 標記每個從上次打過 tag 發佈後產生更新的包
  2. 提示選擇這次迭代的新版本號
  3. 修改 package.json 中的 version 值來反映這次更新
  4. 提交記錄這次更新並打 tag
  5. 推送到遠端倉庫

小技巧: 你能夠在執行此命令的時候加上 ——no-push 來阻止默認的推送行爲,在你檢查確認沒有錯誤後再執行 git push 推送

--conventional-changelog

$ lerna version --conventional-commits
複製代碼

version 支持根據符合規範的提交記錄在每一個模塊中自動建立和更新 CHANGELOG.md 文件,同時還會根據提交記錄來肯定這次迭代的類型。只須要在執行命令的時候帶上 --conventional-changelog 參數便可

--changelog-preset

$ lerna version --conventional-commits --changelog-preset angular-bitbucket
複製代碼

changelog 默認的預設是 angular,你能夠經過這個參數來選擇你想要的預設建立和更新 CHANGELOG.md

預設的名字在解析的時候會被增添 conventional-changelog- 前綴,若是你設置的是 angular,那麼實際加載預設的時候會去找 conventional-changelog-angular 這個包,若是是帶域的包,則須要按照 @scope/name 的規則去指明,最後會被解析成 @scope/conventional-changelog-name

小技巧: 上述 2 個參數也能夠直接寫在 lerna.json 文件中,這樣每次執行 lerna version 命令的時候就會默認採用上面的 2 個參數

"command": {
  "version": {
    "conventionalCommits": true,
    "changelogPreset": "angular"
  }
}
複製代碼

發佈

在一切準備就緒後,咱們能夠經過 publish 命令實現一鍵發佈多個模塊。基本命令以下:

$ lerna publish
複製代碼

當執行此命令時,會發生以下行爲:

  1. 發佈自上次發佈以來更新的包(在底層執行了 lerna version,2.x 版本遺留的行爲)
  2. 發佈當前提交中打了 tag 的包
  3. 發佈在以前的提交中更新的未經版本化的 「canary」 版本的軟件包(及其依賴項)

注意: Lerna 不會發布在 package.json 中將 private 屬性設置爲 true 的模塊,若是要發佈帶域的包,你還須要在 'package.json' 中設置以下內容:

"publishConfig": {
    "access": "public"
  }
複製代碼

若是以前已執行過 lerna version 命令,這裏若是直接執行 lerna publish 會提示沒有發現有更新的包須要更新,咱們能夠經過從遠端的 git 倉庫來發布:

lerna publish from-git
複製代碼

lerna.json 解析

{
  "version": "1.1.3",
  "npmClient": "npm",
  "command": {
    "publish": {
      "ignoreChanges": [
        "ignored-file",
        "*.md"
      ]
    },
    "bootstrap": {
      "ignore": "component-*",
      "npmClientArgs": ["--no-package-lock"]      
    }
  },
  "packages": ["packages/*"]
}
複製代碼

version:當前庫的版本 npmClient: 容許指定命令使用的client, 默認是 npm, 能夠設置成 yarn command.publish.ignoreChanges:能夠指定那些目錄或者文件的變動不會被publish command.bootstrap.ignore:指定不受 bootstrap 命令影響的包 command.bootstrap.npmClientArgs:指定默認傳給 lerna bootstrap 命令的參數 command.bootstrap.scope:指定那些包會受 lerna bootstrap 命令影響 packages:指定包所在的目錄

適用場景

最後咱們來講說 Monorepo 的適用場景

  • 不過度龐大的項目,整合到一塊兒有 100G 源碼的話,仍是再考慮一下吧
  • 多模塊 / 插件化項目,把官方維護的插件都做爲 package 很是合適

另外,還須要:

  • 基礎建設
  • 團隊信任

基礎建設是指強大的構建工具,能知足全部模塊的 build 需求(純前端項目的話,build 壓力不大)

Monorepo 環境下,而且鼓勵改別人的代碼,一方面須要持續集成機制(例如 React – CircleCI)確認修改帶來的影響,另外一方面還須要不一樣團隊之間互相信任。

參考

sosout.github.io/2018/07/21/… www.uedlinker.com/2018/08/17/… juejin.cn/post/684490…

相關文章
相關標籤/搜索