鯨品堂|Lerna最佳實踐(內含大量代碼)

2022-10-17 465

在(zài)介紹本(běn)文的主角(jiǎo) lerna 之前,首先了解(jiě)下什麽是 multirepo ?什麽是 monorepo ?


multirepo 指的是將模塊分(fèn)為(wéi)多個倉庫,monorepo 指的是將多個模(mó)塊放在一個倉庫中。


multirepo 可以讓每個(gè)團隊都擁有自己的倉(cāng)庫,他們(men)可以使用自己的構建流程、代碼規範等,但(dàn)是同時也會存在很多(duō)問題,比如模塊之間(jiān)如果存在(zài)相互依賴,就必須到目(mù)標倉庫裏進(jìn)行bug修複、構建、發版本等,相互(hù)依賴關係越複雜,處理起來就越(yuè)困難。


monorepo 可以讓(ràng)多個模塊共享同一個倉庫,因此他們可以共享同一套構建流程、代碼規範也可以做到統一,特別是如果存在模塊間的相互(hù)依賴的情(qíng)況,查看(kàn)代碼、修(xiū)改bug、調試等會更加方便,因(yīn)此也越來越(yuè)受到大家的關注,像 Babel、React、Vue 等主流的開源倉庫都采用的 monorepo。



lerna









lerna 是一個管理工具,用於管理包含多個軟件包(package)的 JavaScript 項目,最(zuì)早是(shì) Babel 自己用來維護自己(jǐ)的 monorepo 並開源出的一個項目,針對使用 git 和 npm 管理多軟件包代碼倉庫的工作流程進行優化,解決多個包(bāo)互(hù)相依賴,且發布(bù)需(xū)要手動維護多個包的問(wèn)題。


總結一下(xià),使用 lerna 可以幫我們解(jiě)決(jué)如下幾個痛點:


多(duō)個倉庫之間可以管理管理公共的(de)依賴(lài)包,或者(zhě)單獨管(guǎn)理各自的依賴包


方便模塊之間的相互引用,模塊之間的調試不(bú)必發版本,lerna內部會自動進行link


lerna提供了兩種模式,支持選擇單獨(dú)針對某個(gè)包發(fā)版本或(huò)者(zhě)統一發版本


多個(gè)倉庫之間可以共享(xiǎng)統一的代碼規範,版本管理更加規範


以下會分兩個部分介紹,首先(xiān)是介紹 lerna 的常(cháng)規用法,然後介紹下(xià) lerna 的最佳實踐。



基本用法









安裝

$ npm install --global lerna


創建一個git倉庫

$ git init lerna-repo && cd lerna-repo


初始化一個 lerna 倉庫

$ lerna init


注意,lerna init 可以通(tōng)過(guò)參數 --independent 進入 Independent 模式,該模式可以單獨發版本。


初(chū)始化之後的工程目錄結構如下:

lerna-repo├── lerna.json├── package.json└── packageslerna.json:{"packages": ["packages/*"],"version": "0.0.0"}package.json:{"name": "root","private": true,"devDependencies": {"lerna": "^4.0.0"}}


新增package包

使(shǐ)用 lerna create 創建兩個包 pkg1 和 pkg2

$ lerna create pkg1$ lerna create pkg2


創建完成後的(de)目錄結構如下:

lerna-demo├── README.md├── lerna.json├── package.json└── packages├── pkg1│ ├── README.md│ ├── __tests__│ ├── lib│ └── package.json└── pkg2├── README.md├── __tests__├── lib└── package.json


給兩個package增加公共依賴

給 pkg1 和 pkg2 這兩個包(bāo)都安裝 fs-extra 這個包,pkg1 和 pkg2 的 package.json 的 dependency 會同時(shí)包含 fs-extra 這個包。

$ lerna add fs-extra


安裝 fs-extra 之後的目錄(lù)結構:

lerna-demo├── README.md├── lerna.json├── package.json└── packages├── pkg1│ ├── README.md│ ├── __tests__│ ├── lib│ ├── node_modules│ ├── package-lock.json│ └── package.json└── pkg2├── README.md├── __tests__├── lib├── node_modules├── package-lock.json└── package.json


給(gěi)某個包單(dān)獨安裝指定依賴

比如給 pkg1 安裝一(yī)個 glob 包,給 pkg2 安裝一個 ora 包:

$ lerna add glob --scope pkg1$ lerna add ora --scope pkg2


其中 --scope 參(cān)數用(yòng)來(lái)指定具體給哪個 package 安裝(zhuāng)包,注意 --scope 後(hòu)麵的參數是對應模塊的 package.json 中的 name 字段名。


添加(jiā)內部模塊之間的依賴

將 pkg1 作為 pkg2 的依賴進行安裝:

$ lerna add pkg1 --scope pkg2


需要注意的是,通(tōng)過這種方式安裝的依賴,並不會將 pkg1 安裝到 pkg2 的 node_modules 裏,而是通過(guò) symlink 的形式進行(háng)關聯。


發布

以上包確認沒有問(wèn)題之(zhī)後,就可以通過執行 lerna publish 進行發布了。


在進行 publish 之前需要首先提交你的(de)代碼(mǎ),否則 lerna 會報錯:

lerna ERR! ENOCOMMIT No commits in this repository. Please commit something before using version.


提交代碼並關聯(lián)到 git 倉(cāng)庫:

$ git add .$ git commit -m 'init'$ git remote add origin git@github.com:astonishqft/lerna-demo.git // 關聯到遠程(chéng)git倉(cāng)庫$ git push -u origin main


刪除某個包

將 pkg1 裏麵的 glob 包刪除(chú):

$ lerna exec --scope=pkg1 npm uninstall glob


抽離公共的包

上麵可以看到,pkg1 和 pkg2 都依賴了 fs-extra 這個包,而(ér)各自 package 下麵的 node_modules 都(dōu)進行了一次安裝,因此(cǐ)我們可以通過 --hoist 來(lái)抽取重複的依賴到最外層的 node_modules 目錄下,同時(shí)最(zuì)外層的 package.josn 的依賴(lài)信息也不會進行更新。

$ lerna bootstrap --hoist

但是這種方式會有一(yī)個問題,不同版本號隻會保留使用最多的版本,這種配置不太好,當項目中有些(xiē)功能需要依賴老版本時,就會出現問題,因(yīn)此這種方式不推薦使用。



最佳實踐









前麵我們(men)已經(jīng)介紹了(le) lerna 的相關概念和基本用法,目前最(zuì)常見的解決方案是基(jī)於(yú) lerna 和 yarn workspace 的 monorepo 工作流。


由於 yarn 和 lerna 在功(gōng)能上有較多的重疊(dié),我(wǒ)們采用 yarn 官方(fāng)推薦的(de)做法:用 yarn 來處理依賴問題,用 lerna 來處理發布問題(tí)。


yarn workspaces 與 lerna

yarn workspaces 是 yarn 提供的 monorepo 的依賴管理機製,用於在代碼(mǎ)倉庫(kù)的(de)根目錄下管理多個 package 依賴,與(yǔ) lerna 不同的(de)是,yarn workspaces 可以解決前麵說的當不(bú)同的 package 依賴不同的版本(běn)號問題,yarn workspaces 會檢查每個子項目裏(lǐ)麵(miàn)依賴及其版本,如果版本不一致都會安(ān)裝到(dào)各自 package 的 node_modules 中,隻有依賴版本號(hào)一致的時候(hòu)才會提升到頂層,而 lerna 會進到(dào)每個 package 中執行 yarn/npm install,因此會在每個 package 下生成一個 node_modules。


yarn workspaces 首(shǒu)先(xiān)在工程的根目(mù)錄下的(de) package.json 中增(zēng)加 "private": true 和 "workspaces”: [ "packages/*"] 配置項。"private": true 可以確保根目錄不會被發布出去,"workspaces”: [ "packages/*"] 聲明了 workspaces 中所包含的項目路徑。


package.json 配置(zhì)文件增加如下(xià)配置。


開啟yarn workspaces

{"private": true,"workspaces": ["packages/*"],}lerna.json 配置文件增加如下(xià)配置(zhì)。{"useWorkspaces": true,"npmClient": "yarn",}


工程初始化

對於一(yī)個已經存在的 monorepo 倉庫,使用 yarn install 安裝依賴(lài)。yarn install 會自動安裝依賴並且解決同(tóng)一個倉(cāng)庫之間多個package 之間的 link 問題。


yarn install 等價於 lerna bootstrap --npm-client yarn --use-workspace。


清理環境

使用 lerna clean 可以清理每個 package 下的(de) node_modules,但是沒有辦法清理根目錄下的 node_modules 目錄,因此,我們可以在根目錄下(xià)的 package.json 目錄的 scripts 中增加一條 clean 命令,用於清(qīng)理環境(jìng)。

package.json:{"clear-all": "rimraf node_modules && lerna clean -y"}


安裝(zhuāng)依賴(lài)

安裝依賴(lài)一般分為三種情況:



安裝到workspace-root

對於一些打包工具或(huò)者代碼(mǎ)規範校驗工具,可以使用 yarn -W add [package] [--dev] 進行安裝(zhuāng),比(bǐ)如(rú) typescript、eslint、cross-env、babel、rollup等。這類包一般都是一些開發依賴,比如將 ts 代碼轉換成 es5 代碼或者一些代碼校驗工具等。通過(guò)這種方式安裝的依賴包是裝在根目錄下的 node_modules 中。



給所有的 package 都安裝依賴(lài)

比如如果想給每個 package 都(dōu)安裝一個 lodash 包,就(jiù)可以使用 yarn workspace add lodash 給每(měi)個 package 都安裝 lodash。



給指定的某個 package 安裝依賴

通過 yarn workspace pkgA add pkgB 可以將 pkgB 作為依賴安裝到 pkgA 中,需要注(zhù)意的是,如果是 packages 之間的相互安裝,安裝的時候可以指定到具(jù)體的版本號,否則安裝的時(shí)候會去npm上搜索,但是因為(wéi)某個包還沒有發包出去,導致安裝失敗。


刪(shān)除包的時候隻需要把上述 add 換成 remove 即可。


運行workspace的command

通過運行 yarn workspace <workspace_name> <command> 命令(lìng)運行某個執行 package 下的某個 script 命令。


比如執行 pkgA 下的(de) build 命令,可以運行 yarn workspace pkgA run build。如果想運行 package 下的 build 命令,可以運(yùn)行(háng) yarn workspaces run build。


代碼提交

代碼編寫完畢(bì)後接下來就涉及到代碼的提交,為(wéi)了規範代碼提(tí)交格式,方便自動生(shēng)成 changelog,這裏需要借助以下幾個工具。

commitizen && cz-conventional-changelog

commitizen 的作用主要是為了生成(chéng)標準化的 commit message,符(fú)合 Angular規範。


一個標準化的 commit message 應(yīng)該包含三個部分:Header、Body 和 Footer,其中的 Header 是必須的,Body 和 Footer 可以選填。

<type>(<scope>): <subject>// 空一行<body>// 空一行<footer>


Header 部分由三個字段組(zǔ)成:type(必需)、scope(可選)、subject(必需)


Type

type 必須是下麵的其中之(zhī)一:

  • feat: 增加新功(gōng)能

  • fix: 修複bug

  • docs: 隻改動了文檔相關的內容

  • style: 不影響代(dài)碼含義的改動,例如去掉空格、改變縮進、增刪分號

  • refactor: 代碼重構時使用,既(jì)不是新增功(gōng)能也不是代碼的(de)bud修複

  • perf: 提高性能的修改

  • test: 添加或(huò)修改測試代碼

  • build: 構建工(gōng)具或者外部依賴包的修改,比如更新依賴包的版本

  • ci: 持續集成的配置文件或者腳本的修改

  • chore: 雜項(xiàng),其他不需要修改源代碼或不需要修改測試代碼(mǎ)的修改

  • revert: 撤銷某次提交


scope

用於說明本次提交的影響範圍。scope 依據項目而定,例如在(zài)業務項目中可以依據菜單或者功能模塊(kuài)劃分,如果是組件庫開發,則可以依據組件劃分。


subject

主題包含對更改的簡(jiǎn)潔(jié)描述(shù):

注意三點:

  1. 使(shǐ)用祈使語氣,現在時,比如使用      "change" 而不(bú)是      "changed" 或者      ”changes“

  2. 第(dì)一個字母不要大(dà)寫

  3. 末尾不要以.結尾


Body

主要包含對主題的進一(yī)步描述(shù),同樣的,應該使用祈使語氣,包含本次修改的動機(jī)並將其與之前的行為進行(háng)對比。


Footer

包含此次(cì)提交有關重大更改的信息,引用此次提交關閉的issue地址,如果代碼的提交是不兼容變更或關閉缺陷,則Footer必需,否則可以省略。


使用方法:

安裝 commitizen,如果需要在項(xiàng)目中使用 commitizen 生成符(fú)合 AngularJS 規範的提交說明,還需要安裝(zhuāng) cz-conventional-changelog 適(shì)配器:

$ yarn -W add commitizen cz-conventional-changelog -D


package.json 中增加(jiā)一條 script: commit: "git-cz",並且在 config 字段中(zhōng)指定 cz-conventional-changelog 路徑:

{"name": "root","private": true,"workspaces": ["packages/*"],"scripts": {"commit": "git-cz"},"config": {"commitizen": {"path": "cz-conventional-changelog"}},"devDependencies": {"commitizen": "^4.2.4","cz-conventional-changelog": "^3.3.0","lerna": "^4.0.0"}}


接下來就可以使用 yarn commit 來代替 git commit 進行代碼(mǎ)提(tí)交了。


commitlint && husky

前麵我們提到(dào),通過 commitizen && z-conventional-changelog 可以規範我們的(de) commit message,但是同(tóng)時也存在一個問題(tí),如果用戶不通過 yarn commit 來提交代碼,而(ér)是直接通過 git commit 命令來提交代碼,就能繞開 commit message 檢查,這是我們不希望看(kàn)到的。


因此(cǐ)接下來我們使用 commitlint 結合 husky 來對我們的提交行為進行約束(shù)。在 git commit 提交之前使(shǐ)用 git 鉤子來驗(yàn)證(zhèng)信息。提交不符(fú)合規(guī)則的信息將(jiāng)會被(bèi)阻止提交。


安裝 commitlint 和 husky:

$ yarn -W add @commitlint/cli @commitlint/config-conventional husky -D


在工程根目錄下增加 commitlint.config.js 配置文件,指定(dìng) commitlint 的校驗配(pèi)置文件:commitlint.config.js:

module.exports = { extends: ['@commitlint/config-conventional'] }


需要注意的是,如果安(ān)裝的 husky 的(de)版(bǎn)本是 7.x 的,以往直接在 package.json 中 hooks 字段增加的配置項已經被(bèi)廢棄了。


已(yǐ)經廢棄的配置:

"husky": { "hooks": { "commit-msg": "commitlint -E HUSKY_GIT_PARAMS" }},


下麵在 husky 7.x 版本下進行 commit-msg 的(de)配置。


  1. 執行 npm husky install

或者如果想在安裝後自動啟動 husky,在 package.json 的scripts中增加一條(tiáo)script命令:

"scripts": {"prepare": "husky install"},


prepare 是 NPM 操作生命周期中的一環,在執行 install 的時(shí)候會按生命周期順序執行相應鉤子:preinstall -> install -> postinstall -> prepublish -> preprepare -> prepare -> postprepare.


執(zhí)行(háng)完畢後就會在根目錄下創建一個 .husky 目錄。


  1. 通過 husky add <file> [cmd] 指令來添加一(yī)條 hook

執行 npx husky add .husky/commit-msg 'npx --no-install commitlint --edit "$1"' 會在 .husky 下生成一個 commit-msg 的 shell 文件:

#!/bin/sh. "$(dirname "$0")/_/husky.sh"npx --no-install commitlint --edit "$1"


這樣,當我們在執行 git commit -m 'xxx' 的時候,如果提交(jiāo)的 commit message 不符合規範就會(huì)報如下的錯誤,隻有提交信息符合提交(jiāo)規範才會允許代碼提交(jiāo)。


eslint 配置

配置 eslint 對代碼進行統一的規範校驗,配合 lint-staged 可以對已經提交(jiāo)的代碼進行校驗。


安(ān)裝 eslint 和 lint-stage:

$ yarn -W add eslint lint-staged @typescript-eslint/parser @typescript-eslint/eslint-plugin -D


增(zēng)加(jiā) .eslintrc.js 配置文件:

module.exports = {'parser': '@typescript-eslint/parser','plugins': ['@typescript-eslint'],'rules': {'no-var': 'error',// 不能使用var聲明變量'no-extra-semi': 'error','@typescript-eslint/indent': ['error', 2],'import/extensions': 'off','linebreak-style': [0, 'error', 'windows'],'indent': ['error', 2, { SwitchCase: 1 }], // error類型,縮進2個空格'space-before-function-paren': 0, // 在函數左括號的前麵(miàn)是(shì)否有(yǒu)空格(gé)'eol-last': 0, // 不檢測新文件末尾(wěi)是否有空行'semi': ['error', 'always'], // 在語句後麵加分號'quotes': ['error', 'single'],// 字符串使用單雙引號,double,single'no-console': ['error', { allow: ['log', 'warn'] }],// 允許使用console.log()'arrow-parens': 0,'no-new': 0,//允許使用 new 關(guān)鍵字'comma-dangle': [2, 'never'], // 數組和對象鍵值對最後一個逗號


never參數:不能帶末尾的逗號, always參數:必須帶(dài)末尾的(de)逗號,always-multiline多行模式必須帶逗號(hào),單行模式不能帶逗號

'no-undef': 0},'parserOptions': {'ecmaVersion': 6,'sourceType': 'module','ecmaFeatures': {'modules': true}}};


lint-staged staged 是(shì) Git 裏(lǐ)的概念,表示暫存區,lint-staged 表示隻檢查暫存區中的文件。


package.json 中增加如下配置:

"lint-staged": {"*.ts": ["eslint --fix","git add"]}


husky 中增(zēng)加(jiā) pre-commit 校驗:

$ npx husky add .husky/pre-commit "npx --no-install lint-staged"


版本發布

通過上麵的方式,嚴格 commit message 的提交規範(fàn),就可以方便地通過 lerna 完成生成 changelog、打 git tag、 更新(xīn) package.json 的 version 版本號、發布到 npm 等操作。


lerna puplish

lerna publish 的時候會做以下操作:

  • 找(zhǎo)出從上一個版本發布以來有過變更的 package

  • 提示開發者確定要發布的版本號

  • 將所有更新過的的 package 中的 package.json 的 version 字段更新

  • 將依賴更新過的 package 的 包中的依賴版本號更新

  • 更新 lerna.json 中的 version 字段

  • 提交上述修(xiū)改,並(bìng)打一(yī)個 tag

  • 推(tuī)送到 git 倉(cāng)庫


changelog 的生(shēng)成(chéng)可以通過 lerna version --conventional-commits 自動生成,關(guān)於 --conventional-commits 參數,lerna 是這(zhè)麽描述的:

Use conventional-changelog to determine version bump and generate CHANGELOG.


在 lerna.json 增加如下配置:

{"command": {"version": {"conventionalCommits": true}},}


lerna version 會檢(jiǎn)測從(cóng)上一個版(bǎn)本發(fā)布以來的變動(dòng),但有一些文件的提交,我們不(bú)希望觸發版本的變動,譬如 .md 文件的修改,並沒(méi)有實際引起 package 邏輯的變(biàn)化,不應該觸發版本的變更。可以通(tōng)過 ignoreChanges 配置排除。

{"ignoreChanges": ["**/*.md"],}


配置(zhì)好之後,通過 lerna publish 完成changelog生成、版本號的修改和npm發包等操作了。



總結









以上就(jiù)是一個完整(zhěng)的基於 lerna + yarn workspace 的 monorepo 的實踐流(liú)程,裏麵包含了依賴包的管理、完善的工作流、統一的代碼風格、一鍵(jiàn)發布機製等,當然還有一(yī)些不夠完善的地方需要自己補充,比(bǐ)如單(dān)元測試等,有需要的可以基於上述例子進行補充完善(shàn)。



官方微信公眾號

国产亚洲熟妇在线视频雲計算科技股份有限公司 版權所有 2003-2023

蘇ICP備10224443號-6       蘇公網安備 32011402011374號

国产亚洲熟妇在线视频-亚洲熟妇AV乱码在线观看-亚州国产AV一区二区三区伊在-中文字幕无码人妻少妇免费视频-欧美 日韩 人妻 高清 中文-熟妇人妻中文字幕无码老熟妇-丰满熟女人妻一区二区三-亚洲精品字幕