pnpm-lerna-typescript项目实践

本系列文章分为两篇:理论篇和实践篇

  • 理论篇:介绍 pnpm(pnpm 的特点、解决的问题等)、lerna(lerna 的常用命令)、typescript
  • 实践篇:业务线中如何配置使用 pnpm、lerna 以及需要注意的坑有哪些

Part1 pnpm

pnpm 是一款当代受欢迎 新兴(问题较多) 的包管理工具。

为什么会出现 pnpm?因为 yarn 的出现并没有满足作者的一些期待,反而有些失望。

After a few days, I realized that Yarn is just a small improvement over npm. Although it makes installations faster and it has some nice new features, it uses the same flat node_modules structure that npm does (since version 3). And flattened dependency trees come with a bunch of issues

几天后,我意识到 Yarn 只是对 npm 的一个小小的改进。尽管它使安装速度更快,并且具有一些不错的新功能,但它使用与 npm 相同的平面 node_modules 结构(自版本 3 起)。扁平化的依赖树带来了一系列问题(具体后面会讲)

为什么叫 pnpm?是因为 pnpm 作者对现有的包管理工具,尤其是 npm 和 yarn 的性能比较特别失望,所以起名叫做 perfomance npm,即 pnpm(高性能 npm)

如何突显 pnpm 的性能优势?在 pnpm 官网上,提供了一个 benchmarks 图表,它比对了项目在 npm、pnpm、yarn(正常版本和 PnP 版)中,install、update 场景下的耗时:

下面表格是上图中的具体数据:

action cache lockfile node_modules npm pnpm Yarn Yarn PnP
install 1m 12.2s 15.7s 22.1s 27.5s
install 1.6s 1.3s 2.6s n/a
install 9.5s 4s 8.6s 1.9s
install 14.2s 7.9s 14.2s 7.4s
install 25.4s 13s 15.3s 21.1s
install 2.1s 1.8s 8.3s n/a
install 1.6s 1.4s 9.4s n/a
install 2.1s 5.9s 15s n/a
update n/a n/a n/a 1.6s 12.1s 18.7s 32.4s

可以看到 pnpm(橘色)有很明显性能提升,在我们项目实践中(基于 gitlib)提升更明显(跟 store dir 搭配使用)

在讨论性能提升原因之前,我们先了解下现有包管理工具中 node_modules 存在的问题

node_modules 结构

Nested installation 嵌套安装

在 npm@3 之前,node_modules 结构是干净、可预测的,因为 node_modules 中的每个依赖项都有自己的 node_modules 文件夹,在 package.json 中指定了所有依赖项。例如下面所示,项目依赖了 foo,foo 又依赖了 bar,依赖关系如下图所示:

1
2
3
4
5
6
7
8
node_modules
└─ foo
├─ index.js
├─ package.json
└─ node_modules
└─ bar
├─ index.js
└─ package.json

上面结构有两个严重的问题:

  • package 中经常创建太深的依赖树,这会导致 Windows 上的目录路径过长问题
  • 当一个 package 在不同的依赖项中需要时,它会被多次复制粘贴并生成多份文件

Flat installation 扁平安装

为了解决上述问题,npm 重新考虑了 node_modules 结构并提出了扁平化结构。在 npm@3+ 和 yarn 中,node_modules 结构变成如下所示:

1
2
3
4
5
6
7
node_modules
├─ foo
| ├─ index.js
| └─ package.json
└─ bar
├─ index.js
└─ package.json

可以看到,hoist 机制下,bar 被提升到了顶层。如果同一个包的多个版本在项目中被依赖时,node_modules 结构又是怎么样的?

例如:一个项目 App 直接依赖了 A(version: 1.0)和 C(version: 1.0),A 和 C 都依赖了不同版本的 B,其中 A 依赖 B 1.0,C 依赖 B 2.0,可以通过下图清晰的看到 npm2 和 npm3+结构差异:

包 B 1.0 被提升到了顶层,这里需要注意的是,多个版本的包只能有一个被提升上来,其余版本的包会嵌套安装到各自的依赖当中(类似 npm2 的结构)。

至于哪个版本的包被提升,依赖于包的安装顺序!

依赖变更会影响提升的版本号,比如变更后,有可能是 B 1.0 ,也有可能是 B 2.0 被提升上来(但只能有一个版本提升)

细心的小伙伴可能发现,这其实并没有解决之前的问题,反而又引入了新的问题

npm3+和 yarn 存在的问题

Phantom dependencies 幽灵依赖

Phantom dependencies 被称之为幽灵依赖或幻影依赖,解释起来很简单,即某个包没有在 package.json 被依赖,但是用户却能够引用到这个包。

引发这个现象的原因一般是因为 node_modules 结构所导致的。例如使用 npm 或 yarn 对项目安装依赖,依赖里面有个依赖叫做 foo,foo 这个依赖同时依赖了 bar,yarn 会对安装的 node_modules 做一个扁平化结构的处理,会把依赖在 node_modules 下打平,这样相当于 foo 和 bar 出现在同一层级下面。那么根据 nodejs 的寻径原理,用户能 require 到 foo,同样也能 require 到 bar。

nodejs 的寻址方式:

对于核心模块(core module) => 绝对路径 寻址
node 标准库 => 相对路径寻址

第三方库(通过 npm 安装)到 node_modules 下的库:

  1. 先在当前路径下,寻找 node_modules/xxx
  2. 递归从下往上到上级路径,寻找 ../node_modules/xxx
  3. 循环第二步
  4. 在全局环境路径下寻找 .node_modules/xxx

NPM doppelgangers NPM 分身

这个问题其实也可以说是 hoist 导致的,这个问题可能会导致有大量的依赖的被重复安装.

举个例子:项目中有 packageA、packageB、packageC、packageD。packageA 依赖 packageX 1.0 和 packageY 1.0,packageB 依赖 packageX 2.0 和 packageY 2.0,packageC 依赖 packageX 1.0 和 packageY 2.0,packageD 依赖 packageX 2.0 和 packageY 1.0。

在 npm2 时,结构如下

1
2
3
4
5
6
7
8
9
10
11
12
- package A
- packageX 1.0
- packageY 1.0
- package B
- packageX 2.0
- packageY 2.0
- package C
- packageX 1.0
- packageY 2.0
- package D
- packageX 2.0
- packageY 1.0

在 npm3+和 yarn 中,由于存在 hoist 机制,所以 X 和 Y 各有一个版本被提升了上来,目录结构如下

1
2
3
4
5
6
7
8
9
10
11
- package X => 1.0版本
- package Y => 1.0版本

- package A
- package B
- packageX 2.0
- packageY 2.0
- package C
- packageY 2.0
- package D
- packageX 2.0

如上图所示的 packageX 2.0 和 packageY 2.0 被重复安装多次,从而造成 npm 和 yarn 的性能一些性能损失。

这种场景在 monorepo 多包场景下尤其明显,这也是 yarn workspace 经常被吐槽的点,另外扁平化的算法实现也相当复杂,改动成本很高。 那么 pnpm 是如何解决这种问题的呢?

pnpm 的破解之道:网状 + 平铺的 node_modules 结构

一些背景知识:inode、hardl link 和 symbolic link 的基础概念

pnpm 的用户可能会发现它 node_modules 并不是扁平化结构,而是目录树的结构,类似 npm version 2.x 版本中的结构,如下图所示

同时还有个.pnpm 目录,如下图所示

.pnpm 以平铺的形式储存着所有的包,正常的包都可以在这种命名模式的文件夹中被找到(peerDep 例外):

1
2
3
.pnpm/<organization-name>+<package-name>@<version>/node_modules/<name>

// 组织名(若无会省略)+包名@版本号/node_modules/名称(项目名称)

我们称.pnmp 为虚拟存储目录,该目录通过 <package-name> @ <version> 来实现相同模块不同版本之间隔离和复用,由于它只会根据项目中的依赖生成,并不存在提升,所以它不会存在之前提到的 Phantom dependencies 问题!

那么它如何跟文件资源进行关联的呢?又如何被项目中使用呢?

答案是 Store + Links!

Store

pnpm 资源在磁盘上的存储位置。

pnpm 使用名为 .pnpm-store 的 store dir,Mac/linux 中默认会设置到{home dir}>/.pnpm-store/v3;windows 下会设置到当前盘的根目录下,比如 C(C/.pnpm-store/v3)、D 盘(D/.pnpm-store/v3)。

具体可以参考 @pnpm/store-path 这个 pnpm 子包中的代码:

1
2
3
4
5
6
7
const homedir = os.homedir();
if (await canLinkToSubdir(tempFile, homedir)) {
await fs.unlink(tempFile);
// If the project is on the drive on which the OS home directory
// then the store is placed in the home directory
return path.join(homedir, relStore, STORE_VERSION);
}

由于每个磁盘有自己的存储方式,所以 Store 会根据磁盘来划分。 如果磁盘上存在主目录,存储则会被创建在 /.pnpm-store;如果磁盘上没有主目录,那么将在文件系统的根目录中创建该存储。 例如,如果安装发生在挂载在 /mnt 的文件系统上,那么存储将在 /mnt/.pnpm-store 处创建。 Windows 系统上也是如此。

可以在不同的磁盘上设置同一个存储,但在这种情况下,pnpm 将复制包而不是硬链接它们,因为硬链接只能发生在同一文件系统同一分区上。

windows store 如下图所示

pnpm install 的安装过程中,我们会看到如下的信息,这个里面的 Content-addressable store 就是我们目前说的 Store

CAS 内容寻址存储,是一种存储信息的方式,根据内容而不是位置进行检索信息的存储方式。

Virtual store 虚拟存储,指向存储的链接的目录,所有直接和间接依赖项都链接到此目录中,项目当中的.pnpm 目录

如果是 npm 或 yarn,那么这个依赖在多个项目中使用,在每次安装的时候都会被重新下载一次

如图可以看到在使用 pnpm 对项目安装依赖的时候,如果某个依赖在 sotre 目录中存在了话,那么就会直接从 store 目录里面去 hard-link,避免了二次安装带来的时间消耗,如果依赖在 store 目录里面不存在的话,就会去下载一次。

当然这里你可能也会有问题:如果安装了很多很多不同的依赖,那么 store 目录会不会越来越大?

答案是当然会存在,针对这个问题,pnpm 提供了一个命令来解决这个问题: pnpm store | pnpm.

同时该命令提供了一个选项,使用方法为 pnpm store prune ,它提供了一种用于删除一些不被全局项目所引用到的 packages 的功能,例如有个包 axios@1.0.0 被一个项目所引用了,但是某次修改使得项目里这个包被更新到了 1.0.1 ,那么 store 里面的 1.0.0 的 axios 就就成了个不被引用的包,执行 pnpm store prune 就可以在 store 里面删掉它了。

该命令推荐偶尔进行使用,但不要频繁使用,因为可能某天这个不被引用的包又突然被哪个项目引用了,这样就可以不用再去重新下载这个包了。

看到这里,你应该对 Store 有了一些简单的了解,接着我们来看下项目中的文件如何跟 Store 关联。

还记得文章刚开始,放了两张 beachmark 的图表,图表上可以看到很明显的性能提升(如果你使用过,感触会更明显)!

pnpm 是怎么做到如此大的提升的呢?一部分原因是使用了计算机当中的 Hard link ,它减少了文件下载的数量,从而提升了下载和响应速度。

通过 hard link, 用户可以通过不同的路径引用方式去找到某个文件,需要注意的是一般用户权限下只能硬链接到文件,不能用于目录。

pnpm 会在 Store(上面的 Store) 目录里存储项目 node_modules 文件的 hard links ,通过访问这些 link 直接访问文件资源。

举个例子,例如项目里面有个 2MB 的依赖 react,在 pnpm 中,看上去这个 react 依赖同时占用了 2MB 的 node_modules 目录以及全局 store 目录 2MB 的空间(加起来是 4MB),但因为 hard link 的机制使得两个目录下相同的 2MB 空间能从两个不同位置进行 CAS 寻址直接引用到文件,因此实际上这个 react 依赖只用占用 2MB 的空间,而不是 4MB。

因为这样一个机制,导致每次安装依赖的时候,如果是个相同的依赖,有好多项目都用到这个依赖,那么这个依赖实际上最优情况(即版本相同)只用安装一次。

而在 npm 和 yarn 中,如何一个依赖被多个项目使用,会发生多次下载和安装!

如果是 npm 或 yarn,那么这个依赖在多个项目中使用,在每次安装的时候都会被重新下载一次。

如图可以看到在使用 pnpm 对项目安装依赖的时候,如果某个依赖在 sotre 目录中存在了话,那么就会直接从 store 目录里面去 hard-link,避免了二次安装带来的时间消耗,如果依赖在 store 目录里面不存在的话,就会去下载一次。

通过 Store + hard link 的方式,不仅解决了项目中的 NPM doppelgangers 问题,项目之间也不存在该问题,从而完美解决了 npm3+和 yarn 中的包重复问题!

如果随着项目越来越大,版本变更变多,历史版本的资源会堆积,导致 Store 目录越来越大,那如何解决这个问题呢?

针对这个现象,pnpm 提供了一个命令来解决这个问题: pnpm store | pnpm。

同时该命令提供了一个选项,使用方法为 pnpm store prune ,它提供了一种用于删除一些不被全局项目所引用到的 packages 的功能,例如有个包 axios@1.0.0 被一个项目所引用了,但是某次修改使得项目里这个包被更新到了 1.0.1 ,那么 store 里面的 1.0.0 的 axios 就就成了个不被引用的包,执行 pnpm store prune 就可以在 store 里面删掉它了。

该命令推荐偶尔进行使用,但不要频繁使用,因为可能某天这个不被引用的包又突然被哪个项目引用了,这样就可以不用再去重新下载这个包了。

由于 hark link 只能用于文件不能用于目录,但是 pnpm 的 node_modules 是树形目录结构,那么如何链接到文件? 通过 symbolic link(也可称之为软链或者符号链接)来实现!

通过前面的讲解,我们知道了 pnpm 在全局通过 Store 来存储所有的 node_modules 依赖,并且在.pnpm/node_modules 中存储项目的 hard links,通过 hard link 来链接真实的文件资源,项目中则通过 symbolic link 链接到.pnpm/node_modules 目录中,依赖放置在同一级别避免了循环的软链。

pnpm 的 node_modules 结构一开始看起来很奇怪:

它完全适配了 Node.js。
包与其依赖被完美地组织在一起。

有 peer 依赖的包的结构更加复杂一些,但思路是一样的:使用软链与平铺目录来构建一个嵌套结构。

假设我们有个 mono repo,它有 repo A、repo B、repo C 和 repo D4 个 repo。每个 repo 有各自的一些依赖项(包括 dependencies 和 peerDependencies),假定结构如下图所示:(需要注意有个 peer dep)

下面是 pnpm workspace 中,比较清晰(不清晰的话留言,我可以改改!)说明了 Store 和 Links 间的相互关系:

官网也更新了类似的调用关 图,大家也可以看看!

PeerDependencies

pnpm 的最佳特征之一是,在一个项目中,package 的一个特定版本将始终只有一组依赖项。 这个规则有一个例外 -那就是具有 peer dependencies 的 package。

通常,如果一个 package 没有 peer 依赖项(peer dependencies),它会被硬链接到其依赖项的软连接(symlinks)旁的 node_modules,就像这样:

如果 foo 有 peer 依赖(peer dependencies),那么它可能就会有多组依赖项,所以我们为不同的 peer 依赖项创建不同的解析:

pnpm 创建foo@1.0.0_bar@1.0.0+baz@1.0.0foo@1.0.0_bar@1.0.0+baz@1.1.0内到 foo 的软链接。 因此,Node.js 模块解析器将找到正确的 peers。

peerDep 的包命名规则如下(看起来就很麻烦)

1
2
3
.pnpm/<organization-name>+<package-name>@<version>_<organization-name>+<package-name>@<version>/node_modules/<name>

// peerDep组织名(若无会省略)+包名@版本号_组织名(若无会省略)+包名@版本号/node_modules/名称(项目名称)

如果一个 package 没有 peer 依赖(peer dependencies),不过它的依赖项有 peer 依赖,这些依赖会在更高的依赖图中解析, 则这个传递 package 便可在项目中有几组不同的依赖项。 例如,a@1.0.0 具有单个依赖项 b@1.0.0b@1.0.0 有一个 peer 依赖为 c@^1。 a@1.0.0 永远不会解析b@1.0.0的 peer, 所以它也会依赖于 b@1.0.0 的 peer 。

如果需要解决 peerDep 引入的多实例问题,可以通过 .pnpmfile.cjs文件更改依赖项的依赖关系。

Part2 lerna

Lerna 是一个管理工具,用于管理包含多个软件包(package)的 JavaScript 项目。它自身功能非常强大,特别是版本变更、项目发布等功能,可以满足各种复杂场景的诉求,除了 workspace 经常被人吐槽(可以使用 yarn workspace),是业界当中使用规模最多的 repo 管理工具。

因为项目中要使用 import 带入、version 版本变更、publish 项目发布功能,所以着重介绍这三个命令,想要了解更多的同学可以去官网查看

lerna import

将一个包内容(包括提交历史记录)导入到 monorepo 中。

命令如下:

1
lerna import <path-to-external-repository> --preserve-commit

<path-to-external-repository> 是本地代码的存储目录,执行后导入到 packages/<directory-name> 中,包括原始提交作者、日期和消息。执行前需要确保分支的正确性(一般是 master 分支),之后导入就会自动执行。

需要注意目前仅支持本地导入,远程导入的话需要使用一些其他技巧。

–preserve-commit 选项,使用该配置项可以保留原始提交者和提交日期,从而避免下面的问题。

每次 git 提交都有一位作者和一位提交者(每人都有一个单独的日期)。通常他们是同一个人(和日期),但是因为 lerna import 从外部存储库重新创建每个提交,提交者就变成了当前的 git 用户(和日期)。这在技术上是正确的,但逻辑上不对,例如,在 Github 上,如果作者和提交者是不同的人,它就会同时显示他们,这可能会导致导入提交时的历史/职责出现混乱。

import 命令对历史代码迁移到 mono repo 仓库特比有用。同时对每次历史变更为相对包目录进行修改。例如,添加package.json的提交将改为添加 packages/<directory-name>/package.json

lerna version

修改自上次发布以来的包版本号。

为方便同学们学习,先简单介绍下语义化版本。

语义化版本

前端中的包应该遵循语义化版本(也称为“semver”,来源于荷兰语)规范。当你从 registry 安装 package 时,它将会使用语义化的版本添加到项目的 package.json 中。

这些版本被分解 major.minor.patch 为以下其中之一:3.14.1, 0.42.0, 2.7.18。版本的每个部分随时间或者功能进行变更:

  1. major 主版本号:当你做了不兼容的 API 修改。
  2. minor 次版本号:当你做了向下兼容的功能性新增。
  3. patch 修订版本号:当你做了向下兼容的问题修正。

注意: 有时还有 semver 格式的“标签”或“扩展”,用于标记预发布或测试版(例如 2.0.0-beta.3)
当开发人员谈论两个 semver 版本彼此“兼容”时,他们指的是向后兼容的更改(minor 和 patch)

工作模式

Lerna 有两种工作模式:Fixed mode 和 Independent mode

Fixed/Locked mode (default) 固定/锁定模式

固定模式下 Lerna 项目全局仅有一个版本号。该版本号在项目根目录下的 lerna.json 文件中 version 属性中维护。运行 lerna publish 时,如果模块从上次发布以来有能触发发版行为的更新,则 version 会修改为要发布的新版本。这意味着可以仅在需要时发布包的新版本。

注意:如果主版本为零,则所有更新都被视为破坏性修改(Breaking change). 因此,lerna publish 以零为主要版本运行并选择任何非预发布版本号将导致为所有包发布新版本,即使自上次发布以来并非所有包都已更改。

这是 Babel 目前使用的模式。如果您想自动将所有软件包版本绑定在一起,请使用此选项。

这种方法的存在两个问题:

  1. 任何包的重大更改都会导致所有包都具有新的主要版本。
  2. 项目中包的版本变更会非常快

这些都是一致性带来的衍生效应,具体大家可以自行评估

Independent mode 独立模式

将 lerna.json 文件中的 version 设置为 independent,即可以独立模式运行。 项目初始化时,可以通过一下命令设置独立模式:

lerna init –independent

独立模式的 Lerna 项目允许各个包维护自己的版本。每次发布时,都会收到有关已更改的包的提示,以指定它是补丁、次要、主要还是自定义更改。

独立模式允许您更具体地更新每个包的版本并使每次更新有各自的意义。将这种模式与 semantic-release 之类的东西结合起来会减少痛苦。(在atlassian/lerna-semantic-release 已经有这方面的工作)。

预发布

如果你有一个预发布版本号的软件包(例如 2.0.0-beta.3),并且你运行了 lerna version 和一个非预先发布的版本(major、minor 或 patch),它将会发布那些之前发布的软件包以及自上次发布以来已经改变的软件包。

对于使用常规提交的项目,使用以下标志进行预发行管理:

–conventional-prerelease: 将当前更改作为预发行版本发布。
–conventional-graduate: 将预发布版本的包升级为稳定版本。

如果不使用上面的参数运行 lerna version –conventional-commits,则只有在版本已经在 prerelease 中时,才会将当前更改作为 prerelease 释放。

生命周期

1
2
3
// preversion:  在设置版本号之前运行.
// version: 在设置版本号之后,提交之前运行.
// postversion: 在设置版本号之后,提交之后运行.

lerna 将在 lerna version 期间运行 npm 生命周期脚本:

  • 侦测更改的包,选择版本号进行覆盖。
  • 在根目录运行 preversion。
  • 对于每个更改的包,按照拓扑顺序(所有依赖项在依赖关系之前): i. 运行 preversion 生命周期。 ii. 更新 package.json 中的版本。 iii. 运行 version 生命周期。
  • 在根目录运行 version 生命周期。
  • 如果可用,将更改文件添加到索引。
  • 如果可用创建提交和标记。
  • 对于每个改变包,按照词法顺序(根据目录结构的字母顺序): i. 运行 postversion 生命周期。
  • 在根目录运行 postversion。
  • 如果可用推动提交和标记到远程服务器。(该流程会触发 git push 操作)
  • 如果可用创建发布。

lerna publish

在当前项目中发布所有包

1
2
3
lerna publish              # 发布自上一个版本以来发生了变化的包
lerna publish from-git # 发布当前提交中标记的包
lerna publish from-package # 发布注册表中没有最新版本的包

在运行时,该命令做了下面几件事中的一个:

  • 发布自上一个版本以来更新的包(背后调用了 lerna version)。
  • 这是 lerna 2.x 版本遗留下来的。
  • 发布在当前提交中标记的包(from-git)。
  • 发布在最新提交时注册表中没有版本的包(from-package)。
  • 发布在前一次提交中更新的包(及其依赖项)的“金丝雀(canary)”版。

注意: Lerna 永远不会发布标记为 private 的包(package.json 中的”private“: true)

在所有的发布过程中,都有生命周期在根目录和每个包中运行(除非使用了–ignore-scripts)。

发布方式

from-git

除了 lerna version 支持的语义化版本关键字外,lerna publish 也支持 from-git 关键字。这将会识别 lerna version 标记的包,并将它们发布到 npm。这在您希望手动增加版本的 CI 场景中非常有用,但要通过自动化过程一直地发布包内容本身。

from-package

与 from-git 关键字类似,只是要发布的包列表是通过检查每个 package.json 确定的,并且要确定注册表中是否存在任意版本的包。注册表中没有的任何版本都将被发布。当前一个 lerna publish 未能将所有包发布到注册表时,就是他发挥的时候了。

生命周期
1
2
3
4
5
6
7
// prepublish:      在打包和发布包之前运行。
// prepare: 在打包和发布包之前运行,在 prepublish 之后,prepublishOnly 之前。
// prepublishOnly: 在打包和发布包之前运行,只在 npm publish 时运行。
// prepack: 只在源码压缩打包之前运行。
// postpack: 在源码压缩打包生成并移动到最终目的地后运行。
// publish: 在包发布后运行。
// postpublish: 在包发布后运行。

erna 将在 lerna publish 期间运行 npm 生命周期脚本,顺序如下:

  • 如果采用没有指定版本,则运行所有版本生命周期脚本
  • 如果可用,在根目录运行 prepublish 生命周期。
  • 在根目录中运行 prepare 生命周期。
  • 在根目录中运行 prepublishOnly 生命周期。
  • 在根目录运行 prepack 生命周期。
  • 对于每个更改的包,按照拓扑顺序(所有依赖项在依赖关系之前):
  • i. 如果可用,运行 prepublish 生命周期。
  • ii. 运行 prepare 生命周期。
  • iii. 运行 prepublishOnly 生命周期。
  • iv. 运行 prepack 生命周期。
  • v. 通过 JS API 在临时目录中创建源码压缩包。 vi. 运行 postpack 生命周期。
  • 在根目录运行 postpack 生命周期。
  • 对于每个更改的包,按照拓扑顺序(所有依赖项在依赖关系之前):
  • i. 通过 JS API 发布包到配置的注册表。
  • ii. 运行 publish 生命周期。
  • iii. 运行 postpublish 生命周期。
  • 在根目录中运行 publish 生命周期。
  • 为了避免递归调用,不要使用这个根生命周期来运行 lerna publish。
  • 在根目录中运行 postpublish 生命周期。
  • 如果可用,将临时的 dist-tag 更新到最新

6 指令总览 (Commands)

指令参考地址(汉化)

指令 解释
lerna publish 在当前项目中发布包
lerna version 更改自上次发布以来的包版本号
lerna bootstrap 将本地包链接在一起并安装剩余的包依赖项
lerna list 列出本地包
lerna changed 列出自上次标记发布以来发生变化的本地包
lerna diff 自上次发布以来的所有包或单个包的区别
lerna exec 在每个包中执行任意命令
lerna run 在包含该脚本中的每个包中运行 npm 脚本
lerna init 创建一个新的 Lerna 仓库或将现有的仓库升级到 Lerna 的当前版本
lerna add 向匹配的包添加依赖关系
lerna clean 从所有包中删除 node_modules 目录
lerna import 将一个包导入到带有提交历史记录的 monorepo 中
lerna link 将所有相互依赖的包符号链接在一起
lerna create 创建一个新的由 lerna 管理的包
lerna info 打印本地环境信息

Part3 typescript

TypeScript 是 JavaScript 类型的超集,他可以编译成纯 JavaScript。

TypeScript 可以在任何浏览器、任何计算机和任何操作系统上运行,并且是开源的。

TS 介绍的人已经相当多了,这里就不再介绍了!强力安利一波,用过的人都说香!

Part4 总结

pnpm 将来会成为主流的 registry 管理工具,这个毋庸置疑。现在 pnpm 的下载量已经击败了 Bower,并且 2021 年的下载量是 2020 年的 3 倍,目前已经拥有了 14.6k 的 Star。yarn 和 npm 已经开始参考 pnpm 的设计并进行改进,vue、vite 等框架也开始为 pnpm 背书,还没有用过 pnpm 的同学可以尝试下,相信你一定会喜欢它!

Yarn 在 v3.1 添加了 pnpm 链接器。 因此 Yarn 可以创建一个类似于 pnpm 创建的 node_modules 目录结构。此外,Yarn 团队计划实现内容可寻址存储,以提高磁盘空间效率。

Npm 团队决定也采用 pnpm 使用的符号链接的 node_modules 目录结构(相关 RFC)。

lerna 强大的版本管理能力,完全可以弥补 pnpm 在包管理上的弱势,两者协同支持的的呼喊声也越来越强烈,相信将来 lerna + pnpm 一定会成为最佳 monorepo 管理方案之一。

原文链接:https://juejin.cn/post/7043998041786810398

  • 版权声明: 本博客所有文章,未经许可,任何单位及个人不得做营利性使用!转载请标明出处!如有侵权请联系作者。
  • Copyrights © 2015-2024 翟天野

请我喝杯咖啡吧~