实现:使用 lerna 管理 npm 包

最近 xxx-admin-uixxx-admin-service 都用到了lerna, 记录一下注意点

Need

lerna
npm


1. 初始化工程

1
mkdir temp && cd temp && lerna init

https://static.slab.com/prod/uploads/d9aeaycl/posts/images/AJBMI-LnZmMZTNvMmuShS7Kf.png


2. 初始化每个包

  • 假设我们有 2 个包, 如 :
1
2
3
packages
- admin-1
- admin-2

https://static.slab.com/prod/uploads/d9aeaycl/posts/images/hidLbR-PR_GthtQmROKRB6UX.png

  • 分别进入 admin-1admin-2 初始化:
1
2
3
cd packages/admin-1 && npm init -y

cd packages/admin-2 && npm init -y
  • 当然, 每个包都是完整的工程, 可以自由发挥
  • admin-1admin-2package.json中修改包名和版本
1
2
3
4
5
6
// admin-1 package.json
{
"name": "@laputaz/admin-1",// 包名
"version": "0.0.1", // 起始版本号, 我用 0.0.1 开始
...
}

@laputaz 为 scope, 所有相同 scope 的包, 安装后会被放在 scope 目录下

  • admin-2 同理

3. 每个包添加内容

  • 为两个包都增加一个 src/index.js
1
2
// admin-1/src/index.js
console.log('admin-1');

-

1
2
// admin-2/src/index.js
console.log('admin-2');
  • 如果我们不需要对代码打包压缩等操作, 直接指定入口以及需要发布的内容即可:
1
2
3
4
5
6
7
8
9
// in package.json
{
"main": "src/index.js", // 发布后被引用时的入口
"files": [
// 需要发布的文件
"src", // 直接发布src的内容以及 README.md
"README.md"
]
}
  • 也可以用.npmignore指定不需要发布的内容, 区别如下
1
2
.npmignore 该文件指定的内容将不会被发布
package.json => files 字段, 该字段指定的内容将会被发布, 优先级高于.npmignore

4. 修改 lerna 配置

lerna.json 中, 增加如下配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// in lerna.json
{
"packages": ["packages/admin-1", "packages/admin-2"],
"command": {
"bootstrap": {
"hoist": true
},
"publish": {
"message": "release: publish tag ", // lerna version打tag的时候的commit
"conventionalCommits": false //
}
},
"version": "independent"
}
  • 建议:
    • packages 中我们可以写 packages/* , 但经常会建立一些不用发布的 demo 等代码文件目录, 所以最好手动指定需要发布的包
    • command => bootstrap => hoist 设置为 true, 可以把所有的 node_modules 的内容提升到顶层, 让每个包里的 node_modules 看起来更干净
    • version 改为 independent, 让每个包版本号可以不一样
    • command => publish => conventionalCommits 设置为 false 把自动生成 changelog 的功能关了, 太鸡肋, 因为生成的 changelog 可读性很差.

如果没有依赖关系,或者代码打包压缩等处理, 可以走直接第 6 步发布了.


5. 处理依赖关系

包之间可能存在依赖关系, 这里演示有依赖关系的情况:

  • 更新两个包的 src/index.js, 假设 admin-2 依赖 admin-1
1
2
// admin-1/src/index.js
export const say1 = () => console.log('I am admin-1');

&

1
2
3
// admin-2/src/index.js
import { say1 } from '@laputaz/admin-1';
say1();
  • 更新 admin-2dependencies:
1
2
3
4
5
6
7
8
9
//  admin-2 package.json
{
"name": "@laputaz/admin-2",
...
...
"dependencies": {
"@laputaz/admin-1": "0.0.1"
}
}
  • 本地调试时, 需要为 admin-1建立一个软链接, 运行:
1
lerna bootstrap

该命令会在 admin-2node_modules下建立指向admin-1的快捷方式:

https://static.slab.com/prod/uploads/d9aeaycl/posts/images/XCacQ7S04j1Cxhioo19K8L-U.png

可以发现 admin-2/node_modules/@laputa/admin-1 的内容就是 admin-1 的内容

这样不需要先发布admin-1 , 也可以在本地正常调试了.

6. 发布

由于权限限制, 我们不能直接运行lerna publish 发布.

需要分两步操作.

首先保证代码均已 push 到仓库.

  • 运行
1
lerna version [ patch | minor | major | ... ] # 还有其他选项, 但主要使用了这三个

该命令会自动更新每个包的版本并打 tag 提交到 git 仓库

  • slack deploy channel:
1
@k fab knpm_lerna_publish:xxxx.git

xxxx.git为 git 仓库地址

然后在 npm 上就能看到发布的包了


7. 如果要发布打包后的代码

我们经常需要对代码打包压缩等处理, 然后发布处理后的代码

假设, 我们对 admin-1 打包压缩, 输出文件为 lib/index.common.js

  1. 首先在 package.json修改包的入口
1
2
3
{
"main": "lib/index.common.js"
}
  1. 然后需要在scripts增加一个钩子:
1
2
3
4
5
6
7
{
"scripts": {
...
...
"prepublishOnly": "do something here ......"
}
}

我们在 deploy channel执行发布命令的时候, 会在远端执行 lerna publish

lerna publish 中又会执行 npm publish

prepublishOnly 这个钩子, 会在npm publish 前执行

prepublishOnly 中就可以写一些譬如: 切换 node 版本/ 执行 npm install && npm run build等操作


8. 如果需要 changelog

conventionalCommits生成的changelog是基于每个commit的, 难看且鸡肋, 所以决定手动生成.

文件:

1
2
1. changelog.md   // 该文件为最终的所有的版本记录
2. current.md // 当前版本的log

每次只修改 current.md 记录当前版本的更改内容

pulish之前将 current.md 的内容放到 changelog.md中, 并清空 current.md

在工程根目录的 package.json 中增加

1
2
3
4
5
6
7
{
"scripts": {
...
...
"version": "cat current.md >>> changelog.md && git ad ."
}
}

因为执行version 的时候, package中的版本号已经被更新了(即可以拿到最新的版本号), 所以可以在version钩子中操作版本文档的更新, 然后git ad .

9. 后话

2024 不得不说,pnpm 自带的 workspace 更好用