如何使用lerna管理你的仓库

multirepo VS monorepo

在介绍我们今天的主角 lerna 之前,首先了解下什么是 multirepo ?什么是 monorepo

multirepo 指的是将模块分为多个仓库,monorepo 指的是将多个模块放在一个仓库中。

multirepo 可以让每个团队都拥有自己的仓库,他们可以使用自己的构建流程、代码规范等,但是同时也会存在很多问题,比如模块之间如果存在相互依赖,就必须到目标仓库里进行bug修复、构建、发版本等,相互依赖关系越复杂,处理起来就越困难。

monorepo 可以让多个模块共享同一个仓库,因此他们可以共享同一套构建流程、代码规范也可以做到统一,特别是如果存在模块间的相互依赖的情况,查看代码、修改bug、调试等会更加方便,因此也越来越受到大家的关注,像 BabelReactVue 等主流的开源仓库都采用的 monorepo

lerna

lerna 是一个管理工具,用于管理包含多个软件包(package)的 JavaScript 项目,最早是 Babel 自己用来维护自己的 monorepo 并开源出的一个项目,针对使用 gitnpm 管理多软件包代码仓库的工作流程进行优化,解决多个包互相依赖,且发布需要手动维护多个包的问题。

总结一下,使用 lerna 可以帮我们解决如下几个痛点:

多个仓库之间可以管理管理公共的依赖包,或者单独管理各自的依赖包

方便模块之间的相互引用,模块之间的调试不必发版本,lerna内部会自动进行link

lerna提供了两种模式,支持选择单独针对某个包发版本或者统一发版本

多个仓库之间可以共享统一的代码规范,版本管理更加规范

以下我会分两个部分介绍下 lerna,首先是介绍 lerna 的常规用法,然后介绍下 lerna 的最佳实践。

基本用法

安装

$ npm install --global lerna

创建一个git仓库

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

初始化一个 lerna 仓库

$ lerna init

注意,lerna init 可以通过参数 --independent 进入 Independent 模式,该模式可以单独发版本。

初始化之后的工程目录结构如下:

lerna-repo├── lerna.json├── package.json└── packages

lerna.json:

{"packages": ["packages/*"],"version": "0.0.0"
}

package.json:

{"name": "root","private": true,"devDependencies": {"lerna": "^4.0.0"}
}

新增package包

使用 lerna create 创建两个包 pkg1 和 pkg2

$ lerna create pkg1
$ lerna create pkg2

创建完成后的目录结构如下:

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

给两个package增加公共依赖

pkg1pkg2 这两个包都安装 fs-extra 这个包,pkg1pkg2package.jsondependency 会同时包含 fs-extra 这个包。

$ lerna add fs-extra

安装 fs-extra 之后的目录结构:

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

给某个包单独安装指定依赖

比如给 pkg1 安装一个 glob 包,给 pkg2 安装一个 ora 包:

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

其中 --scope 参数用来指定具体给哪个 package 安装包,注意 --scope 后面的参数是对应模块的 package.json 中的 name 字段名。

添加内部模块之间的依赖

pkg1 作为 pkg2 的依赖进行安装:

$ lerna add pkg1 --scope pkg2

需要注意的是,通过这种方式安装的依赖,并不会将 pkg1 安装到 pkg2node_modules 里,而是通过 symlink 的形式进行关联。

发布

以上包确认没有问题之后,就可以通过执行 lerna publish 进行发布了。

在进行 publish 之前需要首先提交你的代码,否则 lerna 会报错:

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

提交代码并关联到 git 仓库:

$ git add .
$ git commit -m 'init'
$ git remote add origin git@github.com:astonishqft/lerna-demo.git // 关联到远程git仓库
$ git push -u origin main

删除某个包

pkg1 里面的 glob 包删除:

$ lerna exec --scope=pkg1 npm uninstall glob

抽离公共的包

上面可以看到,pkg1pkg2 都依赖了 fs-extra 这个包,而各自 package 下面的 node_modules 都进行了一次安装,因此我们可以通过 --hoist 来抽取重复的依赖到最外层的 node_modules 目录下,同时最外层的 package.josn 的依赖信息也不会进行更新。

$ lerna bootstrap --hoist

但是这种方式会有一个问题,不同版本号只会保留使用最多的版本,这种配置不太好,当项目中有些功能需要依赖老版本时,就会出现问题,因此这种方式不推荐使用。

最佳实践

前面我们已经介绍了 lerna 的相关概念和基本用法,目前最常见的解决方案是基于 lernayarn workspacemonorepo 工作流。

由于 yarnlerna 在功能上有较多的重叠,我们采用 yarn 官方推荐的做法:

yarn 来处理依赖问题,用 lerna 来处理发布问题。

yarn workspaces 与 lerna

yarn workspacesyarn 提供的 monorepo 的依赖管理机制,用于在代码仓库的根目录下管理多个 package 依赖,与 lerna 不同的是,yarn workspaces 可以解决前面说的当不同的 package 依赖不同的版本号问题,yarn workspaces 会检查每个子项目里面依赖及其版本,如果版本不一致都会安装到各自 packagenode_modules 中,只有依赖版本号一致的时候才会提升到顶层,而 lerna 会进到每个 package 中执行 yarn/npm install,因此会在每个 package 下生成一个 node_modules

yarn workspaces 首先在工程的根目录下的 package.json 中增加 "private": true"workspaces”: [ "packages/*"] 配置项。"private": true 可以确保根目录不会被发布出去,"workspaces”: [ "packages/*"] 声明了 workspaces 中所包含的项目路径。

package.json 配置文件增加如下配置。

开启yarn workspaces

{"private": true,"workspaces": ["packages/*"],
}

lerna.json 配置文件增加如下配置。

{"useWorkspaces": true,"npmClient": "yarn",
}

工程初始化

对于一个已经存在的 monorepo 仓库,使用 yarn install 安装依赖。yarn install 会自动安装依赖并且解决同一个仓库之间多个package 之间的 link 问题。

yarn install 等价于 lerna bootstrap --npm-client yarn --use-workspace

清理环境

使用 lerna clean 可以清理每个 package 下的 node_modules,但是没有办法清理根目录下的 node_modules 目录,因此,我们可以在根目录下的 package.json 目录的 scripts 中增加一条 clean 命令,用于清理环境。

package.json:

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

安装依赖

安装依赖一般分为三种情况:

  • 安装到workspace-root

    对于一些打包工具或者代码规范校验工具,可以使用 yarn -W add [package] [--dev] 进行安装,比如 typescript、eslint、cross-env、babel、rollup等。这类包一般都是一些开发依赖,比如将 ts 代码转换成 es5 代码或者一些代码校验工具等。通过这种方式安装的依赖包是装在根目录下的 node_modules 中。

  • 给所有的 package 都安装依赖

    比如如果想给每个 package 都安装一个 lodash 包,就可以使用 yarn workspace add lodash 给每个 package 都安装 lodash

  • 给指定的某个 package 安装依赖

    通过 yarn workspace pkgA add pkgB 可以将 pkgB 作为依赖安装到 pkgA 中,需要注意的是,如果是 packages 之间的相互安装,安装的时候可以指定到具体的版本号,否则安装的时候回去npm上搜索,但是因为某个包还没有发包出去,导致安装失败。

删除包的时候只需要把上述 add 换成 remove 即可。

运行workspace的command

通过运行 yarn workspace <workspace_name> <command> 命令运行某个执行 package 下的某个 script 命令。

比如执行 pkgA 下的 build 命令,可以运行 yarn workspace pkgA run build。如果想运行所以 package 下的 build 命令,可以运行 yarn workspaces run build

代码提交

代码编写完毕后接下来就涉及到代码的提交,为了规范代码提交格式,方便自动生成 changelog,这里需要借助一下几个工具。

commitizen && cz-conventional-changelog

commitizen 的作用主要是为了生成标准化的 commit message,符合 Angular规范。

一个标准化的 commit message 应该包含三个部分:HeaderBodyFooter,其中的 Header 是必须的,BodyFooter 可以选填。

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

Header 部分由三个字段组成:type(必需)、scope(可选)、subject(必需)

Type

type 必须是下面的其中之一:

  • feat: 增加新功能
  • fix: 修复 bug
  • docs: 只改动了文档相关的内容
  • style: 不影响代码含义的改动,例如去掉空格、改变缩进、增删分号
  • refactor: 代码重构时使用,既不是新增功能也不是代码的bud修复
  • perf: 提高性能的修改
  • test: 添加或修改测试代码
  • build: 构建工具或者外部依赖包的修改,比如更新依赖包的版本
  • ci: 持续集成的配置文件或者脚本的修改
  • chore: 杂项,其他不需要修改源代码或不需要修改测试代码的修改
  • revert: 撤销某次提交

scope

用于说明本次提交的影响范围。scope 依据项目而定,例如在业务项目中可以依据菜单或者功能模块划分,如果是组件库开发,则可以依据组件划分。

subject

主题包含对更改的简洁描述:

注意三点:

  1. 使用祈使语气,现在时,比如使用 “change” 而不是 “changed” 或者 ”changes“
  2. 第一个字母不要大写
  3. 末尾不要以.结尾

Body

主要包含对主题的进一步描述,同样的,应该使用祈使语气,包含本次修改的动机并将其与之前的行为进行对比。

Footer

包含此次提交有关重大更改的信息,引用此次提交关闭的issue地址,如果代码的提交是不兼容变更或关闭缺陷,则Footer必需,否则可以省略。

使用方法:

安装 commitizen,如果需要在项目中使用 commitizen 生成符合 AngularJS 规范的提交说明,还需要安装 cz-conventional-changelog 适配器:

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

package.json 中增加一条 script: commit: "git-cz",并且在 config 字段中指定 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 进行代码提交了。

commitlint && husky

前面我们提到,通过 commitizen && z-conventional-changelog 可以规范我们的 commit message,但是同时也存在一个问题,如果用户不通过 yarn commit 来提交代码,而是直接通过 git commit 命令来提交代码,就能绕开 commit message 检查,这是我们不希望看到的。

因此接下来我们使用 commitlint 结合 husky 来对我们的提交行为进行约束。在 git commit 提交之前使用 git 钩子来验证信息。提交不符合规则的信息将会被阻止提交。

安装 commitlinthusky

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

在工程根目录下增加 commitlint.config.js 配置文件,指定 commitlint 的校验配置文件:

commitlint.config.js:

已经废弃的配置:

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

需要注意的是,如果安装的 husky 的版本是 7.x 的,以往直接在 package.jsonhooks 字段增加的配置项已经被废弃了,具体可以参考这里

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

下面在 husky 7.x 版本下进行 commit-msg 的配置。

  1. 执行 npm husky install

或者如果想在安装后自动启动 husky,在 package.json 的scripts中增加一条script命令:

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

prepareNPM 操作生命周期中的一环,在执行 install 的时候会按生命周期顺序执行相应钩子:preinstall -> install -> postinstall -> prepublish -> preprepare -> prepare -> postprepare

执行完毕后就会在根目录下创建一个 .husky 目录。

  1. 通过 husky add <file> [cmd] 指令来添加一条 hook

执行 npx husky add .husky/commit-msg 'npx --no-install commitlint --edit "$1"' 会在 .husky 下生成一个 commit-msgshell 文件:

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

这样,当我们在执行 git commit -m 'xxx' 的时候,如果提交的 commit message 不符合规范就会报如下的错误,只有提交信息符合提交规范才会允许代码提交。

eslint 配置

配置 eslint 对代码进行统一的规范校验,配合 lint-staged 可以对已经提交的代码进行校验。

安装 eslintlint-stage:

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

增加 .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, // 在函数左括号的前面是否有空格'eol-last': 0, // 不检测新文件末尾是否有空行'semi': ['error', 'always'], // 在语句后面加分号'quotes': ['error', 'single'],// 字符串使用单双引号,double,single'no-console': ['error', { allow: ['log', 'warn'] }],// 允许使用console.log()'arrow-parens': 0,'no-new': 0,//允许使用 new 关键字'comma-dangle': [2, 'never'], // 数组和对象键值对最后一个逗号, never参数:不能带末尾的逗号, always参数:必须带末尾的逗号,always-multiline多行模式必须带逗号,单行模式不能带逗号'no-undef': 0},'parserOptions': {'ecmaVersion': 6,'sourceType': 'module','ecmaFeatures': {'modules': true}}
};

lint-staged stagedGit 里的概念,表示暂存区,lint-staged 表示只检查暂存区中的文件。

package.json 中增加如下配置:

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

husky 中增加 pre-commit 校验:

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

版本发布

通过上面的方式,严格 commit message 的提交规范,就可以方便的通过 lerna 完成生成 changelog、打 git tag、 更新 package.jsonversion 版本号、发布到 npm 等操作。

lerna puplish

lerna publish 的时候会做以下操作:

  • 找出从上一个版本发布以来有过变更的 package

  • 提示开发者确定要发布的版本号

  • 将所有更新过的的 package 中的 package.jsonversion 字段更新

  • 将依赖更新过的 package 的 包中的依赖版本号更新

  • 更新 lerna.json 中的 version 字段

  • 提交上述修改,并打一个 tag

  • 推送到 git 仓库

changelog 的生成可以通过 lerna version --conventional-commits 自动生成,关于 --conventional-commits 参数,lerna 是这么描述的:

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

lerna.json 增加如下配置:

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

lerna version 会检测从上一个版本发布以来的变动,但有一些文件的提交,我们不希望触发版本的变动,譬如 .md 文件的修改,并没有实际引起 package 逻辑的变化,不应该触发版本的变更。可以通过 ignoreChanges 配置排除。

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

配置好之后,通过 lerna publish 完成changelog生成、版本号的修改和npm发包等操作了。

总结

以上就是一个完整的基于 lerna + yarn workspacemonorepo 的实践流程,里面包含了依赖包的管理、完善的工作流、统一的代码风格、一键发布机制等,当然还有一些不够完善的地方需要自己补充,比如单元测试等,有需要的可以基于我的例子进行补充完善。

本示例github地址。

参考链接:

  • erna 使用指南
  • lerna+yarn workspace+monorepo项目的最佳实践
  • Lerna+Yarn workspace管理多npm
  • Yarn Workspace使用指南

更多精彩内容欢迎关注我的公众号!

如何使用lerna管理你的仓库相关推荐

  1. 基于 Lerna 管理 packages 的 Monorepo 项目最佳实践

    对于维护过多个package的同学来说,都会遇到一个选择题,这些package是放在一个仓库里维护还是放在多个仓库里单独维护,本文通过一个示例讲述了如何基于Lerna管理多个package,并和其它工 ...

  2. 在PhpStorm中管理GitHub代码仓库的使用方法

    2019独角兽企业重金招聘Python工程师标准>>> 1.简介: PhpStorm是一个轻量级且便捷的PHP IDE,其提供的智能代码补全,快速导航以及即时错误检查等功能大大提高了 ...

  3. Lerna管理npm包

    Lerna管理npm包 准备条件 稳定的网速 GITHUB帐号 NPM帐号 注意事项 使用npm源,用于发布包 npm config set registry http://registry.npmj ...

  4. 如何使用repo管理本地私有仓库

    目录 1. 引导脚本 2. 搭建仓库 3. repo without gerrit 1. 引导脚本 repo安装前需要首先下载引导脚本, mkdir ~/bin PATH=~/bin:$PATH cu ...

  5. 详解Linux系统中的软件管理及软件仓库

    目录 一.Linux中软件包的类型 二.软件包的名称结构 三.rpm命令管理软件包 四.本地软件仓库的搭建 五.dnf软件管理命令 总结 一.Linux中软件包的类型 DEB UEBlinux DEB ...

  6. lerna管理多项目

    介绍: 公司有多个项目,每个项目有相同的依赖,或者相同的配置.依赖重复安装,多个依赖可能在多个仓库中存在不同的版本,磁盘空间占用高,开发效率也较低.每个项目都会有一个git仓库独立管理,多个项目来回切 ...

  7. 使用 Gitee 进行代码管理(包括本地仓库如何同时关联Git和Gitee)

    目录 使用Gitee 使用GitHub 使用Gitee 使用GitHub时,国内的用户经常遇到的问题是访问速度太慢,有时候还会出现无法连接的情况(原因你懂的). 如果我们希望体验Git飞一般的速度,可 ...

  8. 用Artifactory管理内部Maven仓库

    http://forearrow.iteye.com/blog/171385 1. 介绍 Maven是Java开发者中流行的构建工具,Maven的好处之一是可以帮助减少构建应用程序时所依赖的软件构件的 ...

  9. git 怎么备份本地分支_同步管理本地git仓库和github仓库上的分支

    参考文章 分支管理策略 在实际开发中,我们应该按照几个基本原则进行分支管理: 首先,master分支应该是非常稳定的,也就是仅用来发布新版本,平时不能在上面干活 那在哪里干活呢?干活都在 dev分支上 ...

最新文章

  1. 一种通过scout ESI和CNN解码EEG运动想象四分类任务的新方法
  2. linux accept 队列,[译] TCP的SYN队列和Accept队列
  3. linux临时挂载别的文件目录_linux基础05:linux系统目录有哪些?命令行界面如何切换目录?...
  4. 解决StreamReader读取中文出现乱码的问题
  5. PyTorch | torch.linspace()创建均分数列张量 | torch.linspace()如何使用?| torch.linspace()使用方法 | torch.linspace例子
  6. 富文本处理NSMutableAttributedString
  7. oracle hwm的位置,Oracle 高水位(HWM)教程(2)
  8. linux 中断 c语言程序,linux驱动之中断处理过程C程序部分
  9. CentOS下GitLab的安装部署
  10. 编写谷歌浏览器插件入门
  11. EasyDarwin云平台:EasyCamera开源摄像机接入海康威视摄像机PS流转ES流
  12. Git 和 GitHub
  13. PHP实现文章评论系统
  14. 2020年android系统版本多少,2020年的Android系统会是什么样?
  15. NI PXI-6221(16路模拟输入)校准小记
  16. 计算机考研专业课838考什么,17年管理学838专业课初试110分经验贴
  17. 汇编-ARMv8架构指令集
  18. 学报格式和论文格式一样吗_学报参考文献标准格式
  19. 网吧加油站_“天下加油站”更名为“天下网吧加油站”
  20. Iterm2使用指南

热门文章

  1. 简单的理解unicode和utf-8的关系
  2. 什么是HTML语义化标签?为什么要用H5语义化标签?HTML5语义化标签有哪些
  3. 趣头条爬虫(以财经频道为例)
  4. 房屋托管网络管理系统_学习管理系统和共享托管
  5. IntelliJ IDEA重置配置设定
  6. PS如何查看所选图层的实际像素?
  7. 厉害了!支付宝逆天升级,可一键撤回被骗转账 网友:干得好!
  8. 无法掩饰的憔悴,无法抗拒的痛紧紧相随
  9. 前端面试 计算机网络篇
  10. [解疑][TI]TI毫米波雷达系列(一):Texas Instruments德州仪器 相关软件安装及使用时问题汇总,持续更新......