Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

lerna workflow #16

Open
soda-x opened this issue May 2, 2018 · 13 comments
Open

lerna workflow #16

soda-x opened this issue May 2, 2018 · 13 comments
Assignees
Labels

Comments

@soda-x
Copy link
Owner

soda-x commented May 2, 2018

示例仓库

在讲 lerna workflow 前我们先粗话来谈下当今主流的项目代码管理方式

杂谈项目管理方式

multiRepos

multiRepos 它是一种管理 organisation 代码的方式,在这种方式下,独立功能会拆分成独立的 repo

这是最常见的项目管理方式

优点:

  • 功能拆分颗粒度较细,职责界线清晰,功能性模块复用度较高

缺点:

  • 由于历史原因或者拆分问题,一个 repo 内的依赖可能来源于多个 organisation
    • issue 提哪是个问题
    • 项目管理,工作协同比较糟糕
  • 维护成本较高
    • 任何的基层 repo 版本变更,将会引发一系列上层封装版本变动
  • changelog 梳理异常折腾
    • 基本靠口口相传

monoRepo

Monorepo 它是一种管理 organisation 代码的方式,在这种方式下会摒弃原先一个独立功能一个 repo 的方式,取而代之的是把所有的 modules 都放在一个 repo 内来管理,而 lerna 是基于此种理念在工具端集合 git 和 npm 的实现。

优点:

  • 功能依旧可以拆分的细粒度
  • one repo multiple packages 让项目管理比较方便,issue 管理,代码变更都能比较好清晰的体现给协同开发者
  • child package 版本变更会自动化同步至相关的 package

缺点:

  • monoRepo 体积都比较大
  • 配套工具 lerna 有一定的使用成本,开发者比较容易用错,另外它有一些约定俗成,不能妥协的规范,以及限制
    • 对 packages 内的依赖版本管理只能 ^
    • 不支持多个 registry 的推送
    • 等等
  • 配套的 changelog 方案只适配于 github 详见我的另外一篇文章 - introduce lerna,如果是社区项目非常推荐走这一套方案
  • 版本的生成还是存在一定的缺陷,开发者并不知情 break 等信息

总结

项目开发中使用 multiRepos 和 monoRepo 都可以,问题在于项目合不合适。

个人角度上:

合适的项目需要有以下特征

  • 存在多个 package
  • package 与 package 之间相互依赖

符合以上条件我个人比较建议采用 monoRepo,以及与之带来的 lerna workflow。

当前使用 lerna确实还会有些小问题,这也是我们需要解决的点。

lerna workflow

先再次简单的介绍下 lerna

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

lerna 模式

在初始化一个项目之前我们必须要清楚,lerna 对管理 monoRepo 有两种模式

  • Fixed/Locked mode (default)
  • Independent mode

Fixed/Locked 模式: 官方默认推荐模式,当前 babel 的项目管理模式,在该模式下所有的 packages 都会遵循一个版本号,该版本号维护在 lerna.json 的 version 字段中,当需要版本发布时 lerna publish 时,如果一个模块和上一次 release 相比有过变更的话,会自动发布一个新版本。

这种模式的问题在于:当有一个 major 变更的时候,所有 packages 都会都会有一个新的 major 版本。

维护团队认为:版本是一种非常 cheap 的东西,所以不必纠结。

Independent 模式: 在该模式下所有 packages 新版本的生成将会由开发者决定,lerna.json 的 version 字段也会随之失效。这种模式的弊端非常明显,开发者必须要非常清晰该发什么版本,事实上在多人协作项目上很难做到这一点。

简单命令速记

init

$ lerna init

初始化一个 lerna 项目

add

$ lerna add <package>[@version] [--dev]

默认给当前所有的 packages 添加一个依赖

这边需要推荐一个比较有用的命令

$ lerna add module-1 --scope=module-2 # Install module-1 to module-2
$ lerna add babel-core # Install babel-core in all modules

这种方式是可以快速建立 packages 的依赖关系,而不用人为手动建立

bootstrap

$ lerna bootstrap

这个命令会安装好所有 packages 的依赖,以及建立好 packages 相互依赖的软连接

正式流程为:

  1. 安装所有 package 的外部依赖.
  2. 对存在相互依赖的 package 创建软连接.
  3. 在所有已经 bootstrapped 的 package 中执行 npm run prepublish.
  4. 在所有已经 bootstrapped 的 package 中执行 npm run prepare.

publish

$ lerna publish

发布一个版本。

正式流程为:

  1. 执行 lerna updated 来确定哪些包需要被发布.
  2. 如有必要会升级 lerna.json 的 version 字段。
  3. 对所有需要 update 的 package 进行版本的更新,并写入他们的 package.json.
  4. 队友有需要 update 的 package 进行依赖申明 specified with a caret (^).
  5. 创建一个 git commit 和 tag
  6. 把包发布至 npm

较为有用的附加参数

--npm-tag

$ lerna publish --npm-tag=beta

使用传入的 tag 把包发布至 npm 对应的 dist-tag

--conventional-commits

$ lerna publish --conventional-commits

遵从 Conventional Commits Specification 进行版本生成和 changlog 生成。

--skip-git

$ lerna publish --skip-npm

跳过 git 打标

--skip-npm

$ lerna publish --skip-npm

跳过 npm 发布

--cd-version

$ lerna publish --cd-version (major | minor | patch | premajor | preminor | prepatch | prerelease)
# uses the next semantic version(s) value and this skips `Select a new version for...` prompt

指定发包的时的语义版本

clean

$ lerna clean

移除所有 package 下的 node_modules 目录.

import

$ lerna import <path-to-external-repository>

从现有仓库导入一个 package,这种方式下会保留原有的 commit 的信息

run

$ lerna run <script> -- [..args] # runs npm run my-script in all packages that have it
$ lerna run test
$ lerna run build

# watch all packages and transpile on change, streaming prefixed output
$ lerna run --parallel watch

执行 package 下 npm script

exec

$ lerna exec -- <command> [..args] # runs the command in all packages
$ lerna exec -- rm -rf ./node_modules

在任何 package 下执行任意的命令

getting started

step 1:

$ npm install --global lerna

step 2:

$ mkdir lerna-example
$ cd lerna-example

step 3:

$ lerna init

运行完后在 terminal 中执行 tree 后我们可以看到此时的目录结构为

➜  lerna-example git:(master) ✗ tree
.
├── lerna.json
├── package.json
└── packages

step 4:

$  packages git:(master) ✗ mkdir module-a && cd module-a && touch index.js && tnpm init
$  packages git:(master) ✗ mkdir module-b && cd module-b && touch index.js && tnpm init
$  packages git:(master) ✗ mkdir module-base && cd module-base && touch index.js && tnpm init

运行完后在 terminal 中执行 tree 后我们可以看到此时的目录结构为

➜  lerna-example git:(master) ✗ tree
.
├── lerna.json
├── package.json
└── packages
    ├── module-a
    │   ├── index.js
    │   └── package.json
    ├── module-b
    │   ├── index.js
    │   └── package.json
    └── module-base
        ├── index.js
        └── package.json

step 5:

如果已知 module-base 被 module-a 和 module-b 共同依赖,同时 module-a 又 依赖 module-b

➜  lerna-example git:(master) ✗ lerna add @alipay/module-base
➜  lerna-example git:(master) ✗ lerna add @alipay/module-b --scope=@alipay/module-a

项目中使用的问题

在协同开发时,假设如果开发人员在 module-base 上发布了一个并不兼容的提交,此时做为 pm 的同学很难在没有提前沟通的情况下获知此次变更,所以在选择版本发布时也很容易出现,因为 lerna 默认对依赖的描述是 ^,所以这在信息不对称的情况下很容易造成线上故障。

如何破局呢?

  • github 用户使用 introduce lerna 文中提及的 lerna-changelog 来依据 changelog 来管理,这个方案的缺点是,版本号生成时并不是完全自动化的,还是需要人工介入。
  • 非 github 用户或使用 commitizen 用户,可以借由 --conventional-commits,来自动化生成版本以及 changelog

关于 commitzen 相关的可以看我另外一篇文章 用工具思路来规范化 git commit message

第二种方案也是目前我们项目中应用最多的。

应用 commitizen 方案后, package.json 变更为

{
  "private": true,
  "scripts": {
    "ct": "git-cz",
    "changelog": "./tasks/changelog.js",
    "publish": "./tasks/publish.js"
  },
  "config": {
    "commitizen": {
      "path": "./node_modules/cz-lerna-changelog"
    }
  },
  "husky": {
    "hooks": {
      "commit-msg": "commitlint -e $GIT_PARAMS",
      "pre-commit": "lint-staged"
    }
  },
  "lint-staged": {
    "*.js": [
      "prettier --trailing-comma es5 --single-quote --write",
      "git add"
    ]
  },
  "devDependencies": {
    "@alipay/config-conventional-volans": "^0.1.0",
    "@commitlint/cli": "^6.1.3",
    "commitizen": "^2.9.6",
    "cz-lerna-changelog": "^1.2.1",
    "husky": "v1.0.0-rc.4",
    "lerna": "^2.10.2",
    "lint-staged": "^7.0.4",
    "prettier": "^1.11.1"
  },
  "dependencies": {
    "fs-extra": "^6.0.0",
    "inquirer": "^5.2.0",
    "shelljs": "^0.8.1"
  }
}

commitizen 应用后仓库结构说明

packages 目录下存放的是所有的子仓库
tasks 目录下存放一些全局的任务脚本,当前有用的是 publish.jschangelog.js

  • changelog.js,当有发布任务时,请事先执行 npm run changelog,此举意为生成本次版本发布的 changelog,执行脚本时会提醒,本次发布是正式版还是 beta,会予以生成不同版本信息供予发布
  • publish.js,当 changelog 生成并调整相关内容完毕后,执行 npm run publish,会对如上所有的子 packages 进行版本发布,执行脚本时会提醒,本次发布是正式版还是 beta,会予以不同 npm dist-tag 进行发布

日常开发流程

在常规开发中,我们的操作方式会变更为如下:

第一步:使用 commitizen 替代 git commit

即当我们需要 commit 时,请使用如下命令

$ npm run ct

如果你在全局安装过 commitizen 那么,直接在项目目录下执行

$ git ct

执行时,会有引导式的方式让你书写 commit 的 message 信息

如果你是 sourceTree 用户,其实也不用担心,你完全可以可视化操作完后,再在命令行里面执行 npm run ct 命令,这一部分确实破坏了整体的体验,当前并没有找到更好的方式来解决。

关于为什么需要 commitizen,可以参考 这篇文章

当前我们遵循的是 angular 的 commit 规范。

具体格式为:

<type>(<scope>): <subject>
<BLANK LINE>
<body>
<BLANK LINE>
<footer>

type: 本次 commit 的类型,诸如 bugfix docs style 等
scope: 本次 commit 波及的范围
subject: 简明扼要的阐述下本次 commit 的主旨,在原文中特意强调了几点 1. 使用祈使句,是不是很熟悉又陌生的一个词,来传送门在此 祈使句 2. 首字母不要大写 3. 结尾无需添加标点
body: 同样使用祈使句,在主体内容中我们需要把本次 commit 详细的描述一下,比如此次变更的动机,如需换行,则使用 |
footer: 描述下与之关联的 issue 或 break change,详见案例

第二步:格式化代码

这一步,并不需要人为干预,因为 precommit 中的 lint-staged 会自动化格式,以保证代码风格尽量一致

第三步:commit message 校验

这一步,同样也不需要人为介入,因为 commitmsg 中的 commitlint 会自动校验 msg 的规范

第四步:当有发布需求时,先生成 changelog

使用

$ npm run changelog

在这一步中我们借助了 commitizen 标准化的 commit-msg 以及 lernapublish--conventional-commits 来自动化生成了版本号以及 changelog,但过程中我们忽略了 git tag 以及 npm publish ( --skip-git --skip-npm),原因是我们需要一个时机去修改自动化生成的 changelog。

第五步:再发布

由于第四步中,我们并没有实质意义上做版本发布,而是借以 lerna 的 publish 功能,生成了 changelog,所以后续的 publish 操作被实现在了自定义脚本中,即 publish.js 中。

$ npm run publish

> 第六步:打 tag

给当前分支打好对应的 git tag 信息,推送到开发分支

@soda-x soda-x added the lerna label May 2, 2018
@soda-x soda-x self-assigned this May 2, 2018
@soda-x soda-x changed the title (WIP) lerna 使用进阶 lerna workflow May 11, 2018
@soda-x
Copy link
Owner Author

soda-x commented May 12, 2018

更新示例仓库 https://github.com/example-repo/lerna-example

@soda-x
Copy link
Owner Author

soda-x commented May 16, 2018

更新: git tag 操作由 publish 脚本来承担

@oMaten
Copy link

oMaten commented May 22, 2018

packages 下的 modules 相互依赖可以不使用 ^ 控制版本,在发布时使用 publish --exact 以做到精确控制版本的更迭

@soda-x
Copy link
Owner Author

soda-x commented May 23, 2018

@oMaten 感谢提醒,后续修正下.

@techbirds
Copy link

Lerna最大的优点: Splitting up large codebases into separate independently versioned packages is extremely useful for code sharing. 作者说的优点其实没有lerna也可以实现。

@soda-x
Copy link
Owner Author

soda-x commented Jun 7, 2018

@techbirds 感觉你是不是看错了什么,lerna 只是一种项目管理方式下的工具端实现。

@techbirds
Copy link

techbirds commented Jun 7, 2018

可能我没表达明白,只是觉得作者的这篇文章没有表达monoRepo的核心作用,你说的优点和缺点都没问题。不过也可能是我没get到你的点。@pigcan

@soda-x
Copy link
Owner Author

soda-x commented Jun 9, 2018

@techbirds 非常欢迎王同学把自己的观点亮出来。这样我写文章的意义也就大了。

@relign
Copy link

relign commented Aug 20, 2018

@pigcan 我给前辈发了一封邮件(gmail),在lerna工作流中遇到了一些问题,希望有时间可以帮忙看看,回复一下~~thanks~

@daolou
Copy link

daolou commented Apr 20, 2020

这里是不是手误啊
image

@soda-x
Copy link
Owner Author

soda-x commented Apr 20, 2020

@Mr-jiangzhiguo 不是. 发布后续交给另外的流程来处理

@daolou
Copy link

daolou commented Apr 20, 2020

不对应啊: --skip-git => --skip-npm
image

另外,我看官方api,你这里有参数没找到,有的参数都Deprecated了,

@soda-x
Copy link
Owner Author

soda-x commented Apr 20, 2020

@Mr-jiangzhiguo 那确实有可能 lerna 自身已经发生了很大的变化. 但这套方式我现在在内部工程里面还有在用. lerna 我是固定在某个版本的.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

5 participants