网站开发项目名,wordpress 文章列表页,wordpress 网站根目录,国家水资源监控能力建设网站本文来自作者金虹桥程序员 投稿原文链接#xff1a;https://juejin.cn/post/7043998041786810398本系列文章分为两篇#xff1a;理论篇和实践篇 理论篇#xff1a;介绍pnpm#xff08;pnpm的特点、解决的问题等#xff09;、lerna#xff08;lerna的常用命令#xff09;… 本文来自作者金虹桥程序员 投稿原文链接https://juejin.cn/post/7043998041786810398本系列文章分为两篇理论篇和实践篇 理论篇介绍pnpmpnpm的特点、解决的问题等、lernalerna的常用命令、typescript 实践篇业务线中如何配置使用pnpm、lerna以及需要注意的坑有哪些感兴趣的小伙伴赶紧收藏学习吧 ^_^Part1pnpmpnpm是一款当代受欢迎 新兴(问题较多) 的包管理工具。为什么会出现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图表它比对了项目[1]在npm、pnpm、yarn正常版本和PnP版中install、update场景下的耗时image.png下面表格是上图中的具体数据actioncachelockfilenode_modulesnpmpnpmYarnYarn PnPinstall1m 12.2s15.7s22.1s27.5sinstall✔✔✔1.6s1.3s2.6sn/ainstall✔✔9.5s4s8.6s1.9sinstall✔14.2s7.9s14.2s7.4sinstall✔25.4s13s15.3s21.1sinstall✔✔2.1s1.8s8.3sn/ainstall✔✔1.6s1.4s9.4sn/ainstall✔2.1s5.9s15sn/aupdaten/an/an/a1.6s12.1s18.7s32.4s可以看到pnpm橘色有很明显性能提升在我们项目实践中基于gitlib提升更明显跟store dir搭配使用在讨论性能提升原因之前我们先了解下现有包管理工具中node_modules存在的问题1node_modules 结构Nested installation 嵌套安装在 npm3 之前node_modules结构是干净、可预测的因为node_modules 中的每个依赖项都有自己的node_modules文件夹在package.json中指定了所有依赖项。例如下面所示项目依赖了foofoo又依赖了bar依赖关系如下图所示node_modules
└─ foo├─ index.js├─ package.json└─ node_modules└─ bar├─ index.js└─ package.json上面结构有两个严重的问题package中经常创建太深的依赖树这会导致 Windows 上的目录路径过长问题当一个package在不同的依赖项中需要时它会被多次复制粘贴并生成多份文件Flat installation 扁平安装为了解决上述问题npm 重新考虑了node_modules结构并提出了扁平化结构。在npm3 和 yarn中node_modules 结构变成如下所示node_modules
├─ foo
| ├─ index.js
| └─ package.json
└─ bar├─ index.js└─ package.json可以看到hoist机制下bar被提升到了顶层。如果同一个包的多个版本在项目中被依赖时node_modules结构又是怎么样的例如一个项目App直接依赖了Aversion: 1.0和Cversion: 1.0A和C都依赖了不同版本的B其中A依赖B 1.0C依赖B 2.0,可以通过下图清晰的看到npm2和npm3结构差异image.png包B 1.0被提升到了顶层这里需要注意的是多个版本的包只能有一个被提升上来其余版本的包会嵌套安装到各自的依赖当中类似npm2的结构。image.png至于哪个版本的包被提升依赖于包的安装顺序依赖变更会影响提升的版本号比如变更后有可能是B 1.0 也有可能是 B 2.0被提升上来但只能有一个版本提升细心的小伙伴可能发现这其实并没有解决之前的问题反而又引入了新的问题image.pngnpm3和yarn存在的问题Phantom dependencies 幽灵依赖Phantom dependencies 被称之为幽灵依赖或幻影依赖解释起来很简单即某个包没有在package.json 被依赖但是用户却能够引用到这个包。引发这个现象的原因一般是因为 node_modules 结构所导致的。例如使用 npm或yarn 对项目安装依赖依赖里面有个依赖叫做 foofoo 这个依赖同时依赖了 baryarn 会对安装的 node_modules 做一个扁平化结构的处理会把依赖在 node_modules 下打平这样相当于 foo 和 bar 出现在同一层级下面。那么根据 nodejs 的寻径原理用户能 require 到 foo同样也能 require 到 bar。nodejs的寻址方式(查看更多[2])对于核心模块core module 绝对路径 寻址node标准库 相对路径寻址第三方库通过npm安装到node_modules下的库 3.1. 先在当前路径下寻找 node_modules/xxx 3.2 递归从下往上到上级路径寻找 ../node_modules/xxx 3.3 循环第二步 3.4 在全局环境路径下寻找 .node_modules/xxxNPM doppelgangers NPM分身这个问题其实也可以说是 hoist 导致的这个问题可能会导致有大量的依赖的被重复安装.举个例子项目中有packageA、packageB、packageC、packageD。packageA依赖packageX 1.0和packageY 1.0packageB依赖packageX 2.0和packageY 2.0packageC依赖packageX 1.0和packageY 2.0packageD依赖packageX 2.0和packageY 1.0。在npm2时结构如下- 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各有一个版本被提升了上来目录结构如下- 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是如何解决这种问题的呢image.png2pnpm的破解之道网状 平铺的node_modules结构一些背景知识inode、hardl link和symbolic link的基础概念[3]pnpm的用户可能会发现它node_modules并不是扁平化结构而是目录树的结构类似npm version 2.x版本中的结构如下图所示image.png同时还有个.pnpm目录如下图所示image.png.pnpm 以平铺的形式储存着所有的包正常的包都可以在这种命名模式的文件夹中被找到peerDep例外.pnpm/organization-namepackage-nameversion/node_modules/name// 组织名(若无会省略)包名版本号/node_modules/名称(项目名称)我们称.pnmp为虚拟存储目录该目录通过package-nameversion来实现相同模块不同版本之间隔离和复用由于它只会根据项目中的依赖生成并不存在提升所以它不会存在之前提到的Phantom dependencies问题那么它如何跟文件资源进行关联的呢又如何被项目中使用呢答案是Store LinksStorepnpm资源在磁盘上的存储位置。pnpm 使用名为 .pnpm-store的 store dir[4]Mac/linux中默认会设置到{home dir}/.pnpm-store/v3windows下会设置到当前盘的根目录下比如CC/.pnpm-store/v3、D盘D/.pnpm-store/v3。具体可以参考 pnpm/store-path 这个 pnpm 子包中的代码: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 directoryreturn path.join(homedir, relStore, STORE_VERSION)
}由于每个磁盘有自己的存储方式所以Store会根据磁盘来划分。 如果磁盘上存在主目录存储则会被创建在 home dir/.pnpm-store如果磁盘上没有主目录那么将在文件系统的根目录中创建该存储。 例如如果安装发生在挂载在 /mnt 的文件系统上那么存储将在 /mnt/.pnpm-store 处创建。 Windows系统上也是如此。可以在不同的磁盘上设置同一个存储但在这种情况下pnpm 将复制包而不是硬链接它们因为硬链接只能发生在同一文件系统同一分区上。windows store如下图所示image.pngpnpm install的安装过程中我们会看到如下的信息这个里面的Content-addressable store就是我们目前说的Storeimage.pngCAS 内容寻址存储是一种存储信息的方式根据内容而不是位置进行检索信息的存储方式。Virtual store 虚拟存储指向存储的链接的目录所有直接和间接依赖项都链接到此目录中项目当中的.pnpm目录image.png如果是 npm 或 yarn那么这个依赖在多个项目中使用在每次安装的时候都会被重新下载一次如图可以看到在使用 pnpm 对项目安装依赖的时候如果某个依赖在 sotre 目录中存在了话那么就会直接从 store 目录里面去 hard-link避免了二次安装带来的时间消耗如果依赖在 store 目录里面不存在的话就会去下载一次。当然这里你可能也会有问题如果安装了很多很多不同的依赖那么 store 目录会不会越来越大答案是当然会存在针对这个问题pnpm 提供了一个命令来解决这个问题: pnpm store | pnpm[5]。同时该命令提供了一个选项使用方法为 pnpm store prune 它提供了一种用于删除一些不被全局项目所引用到的 packages 的功能例如有个包 axios1.0.0 被一个项目所引用了但是某次修改使得项目里这个包被更新到了 1.0.1 那么 store 里面的 1.0.0 的 axios 就就成了个不被引用的包执行 pnpm store prune 就可以在 store 里面删掉它了。该命令推荐偶尔进行使用但不要频繁使用因为可能某天这个不被引用的包又突然被哪个项目引用了这样就可以不用再去重新下载这个包了。看到这里你应该对Store有了一些简单的了解接着我们来看下项目中的文件如何跟Store关联。Linkshard link symbolic link还记得文章刚开始放了两张beachmark的图表图表上可以看到很明显的性能提升如果你使用过感触会更明显pnpm 是怎么做到如此大的提升的呢一部分原因是使用了计算机当中的 Hard link[6] 它减少了文件下载的数量从而提升了下载和响应速度。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那么这个依赖在多个项目中使用在每次安装的时候都会被重新下载一次。image.png如图可以看到在使用 pnpm 对项目安装依赖的时候如果某个依赖在 sotre 目录中存在了话那么就会直接从 store 目录里面去 hard-link避免了二次安装带来的时间消耗如果依赖在 store 目录里面不存在的话就会去下载一次。通过Store hard link的方式不仅解决了项目中的NPM doppelgangers问题项目之间也不存在该问题从而完美解决了npm3和yarn中的包重复问题如果随着项目越来越大版本变更变多历史版本的资源会堆积导致Store目录越来越大那如何解决这个问题呢针对这个现象pnpm 提供了一个命令来解决这个问题: pnpm store | pnpm[7]。同时该命令提供了一个选项使用方法为 pnpm store prune 它提供了一种用于删除一些不被全局项目所引用到的 packages 的功能例如有个包 axios1.0.0 被一个项目所引用了但是某次修改使得项目里这个包被更新到了 1.0.1 那么 store 里面的 1.0.0 的 axios 就就成了个不被引用的包执行 pnpm store prune 就可以在 store 里面删掉它了。该命令推荐偶尔进行使用但不要频繁使用因为可能某天这个不被引用的包又突然被哪个项目引用了这样就可以不用再去重新下载这个包了。symbolic link由于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 依赖的包的结构更加复杂[8]一些但思路是一样的使用软链与平铺目录来构建一个嵌套结构。假设我们有个mono repo它有repo A、repo B、repo C和repo D4个repo。每个repo有各自的一些依赖项包括dependencies和peerDependencies假定结构如下图所示(需要注意有个peer dep)image.png下面是pnpm workspace中比较清晰不清晰的话留言我可以改改说明了Store和Links间的相互关系image.png官网也更新了类似的调用关 图大家也可以看看image.pngPeerDependenciespnpm 的最佳特征之一是在一个项目中package的一个特定版本将始终只有一组依赖项。 这个规则有一个例外 -那就是具有 peer dependencies [9]的package。通常如果一个package没有 peer 依赖项peer dependencies它会被硬链接到其依赖项的软连接symlinks旁的 node_modules就像这样image.png如果 foo 有 peer 依赖peer dependencies那么它可能就会有多组依赖项所以我们为不同的 peer 依赖项创建不同的解析image.pngpnpm创建foo1.0.0_bar1.0.0baz1.0.0 或foo1.0.0_bar1.0.0baz1.1.0内到foo的软链接。 因此Node.js 模块解析器将找到正确的 peers。peerDep的包命名规则如下(看起来就很麻烦).pnpm/organization-namepackage-nameversion_organization-namepackage-nameversion/node_modules/name// peerDep组织名(若无会省略)包名版本号_组织名(若无会省略)包名版本号/node_modules/名称(项目名称)如果一个package没有 peer 依赖peer dependencies不过它的依赖项有 peer 依赖这些依赖会在更高的依赖图中解析, 则这个传递package便可在项目中有几组不同的依赖项。 例如a1.0.0 具有单个依赖项 b1.0.0。 b1.0.0 有一个 peer 依赖为 c^1。 a1.0.0 永远不会解析b1.0.0的 peer, 所以它也会依赖于 b1.0.0 的 peer 。如果需要解决peerDep引入的多实例问题可以通过 .pnpmfile.cjs[10]文件更改依赖项的依赖关系。Part2lernaLerna 是一个管理工具用于管理包含多个软件包package的 JavaScript 项目。它自身功能非常强大特别是版本变更、项目发布等功能可以满足各种复杂场景的诉求除了workspace经常被人吐槽可以使用yarn workspace是业界当中使用规模最多的repo管理工具。因为项目中要使用import带入、version版本变更、publish项目发布功能所以着重介绍这三个命令想要了解更多的同学可以去官网查看3lerna import将一个包内容包括提交历史记录导入到monorepo中。命令如下lerna import path-to-external-repository --preserve-commitpath-to-external-repository是本地代码的存储目录执行后导入到packages/directory-name中包括原始提交作者、日期和消息。执行前需要确保分支的正确性一般是master分支之后导入就会自动执行。需要注意目前仅支持本地导入远程导入的话需要使用一些其他技巧。image.png--preserve-commit选项使用该配置项可以保留原始提交者和提交日期从而避免下面的问题。每次git提交都有一位作者和一位提交者(每人都有一个单独的日期)。通常他们是同一个人(和日期)但是因为lerna import从外部存储库重新创建每个提交提交者就变成了当前的git用户(和日期)。这在技术上是正确的但逻辑上不对例如在 Github 上如果作者和提交者是不同的人它就会同时显示他们这可能会导致导入提交时的历史/职责出现混乱。import命令对历史代码迁移到mono repo仓库特比有用。同时对每次历史变更为相对包目录进行修改。例如添加package.json的提交将改为添加packages/directory-name/package.json。4lerna version修改自上次发布以来的包版本号。为方便同学们学习先简单介绍下语义化版本。语义化版本前端中的包应该遵循语义化版本也称为“semver”来源于荷兰语规范。当你从registry安装package时它将会使用语义化的版本添加到项目的package.json中。这些版本被分解major.minor.patch为以下其中之一3.14.1, 0.42.0, 2.7.18。版本的每个部分随时间或者功能进行变更major主版本号当你做了不兼容的 API 修改。minor次版本号当你做了向下兼容的功能性新增。patch修订版本号当你做了向下兼容的问题修正。注意 有时还有 semver 格式的“标签”或“扩展”用于标记预发布或测试版例如2.0.0-beta.3当开发人员谈论两个 semver 版本彼此“兼容”时他们指的是向后兼容的更改minor和 patch工作模式Lerna有两种工作模式Fixed mode和Independent modeFixed/Locked mode (default) 固定/锁定模式固定模式下 Lerna 项目全局仅有一个版本号。该版本号在项目根目录下的lerna.json文件中version属性中维护。运行lerna publish时如果模块从上次发布以来有能触发发版行为的更新则version会修改为要发布的新版本。这意味着可以仅在需要时发布包的新版本。注意如果主版本为零则所有更新都被视为破坏性修改(Breaking change). 因此lerna publish以零为主要版本运行并选择任何非预发布版本号将导致为所有包发布新版本即使自上次发布以来并非所有包都已更改。这是Babel[11]目前使用的模式。如果您想自动将所有软件包版本绑定在一起请使用此选项。这种方法的存在两个问题任何包的重大更改都会导致所有包都具有新的主要版本。项目中包的版本变更会非常快这些都是一致性带来的衍生效应具体大家可以自行评估Independent mode 独立模式将lerna.json文件中的version设置为independent即可以独立模式运行。 项目初始化时可以通过一下命令设置独立模式lerna init --independent独立模式的 Lerna 项目允许各个包维护自己的版本。每次发布时都会收到有关已更改的包的提示以指定它是补丁、次要、主要还是自定义更改。独立模式允许您更具体地更新每个包的版本并使每次更新有各自的意义。将这种模式与semantic-release[12]之类的东西结合起来会减少痛苦。在atlassian/lerna-semantic-release[13]已经有这方面的工作。预发布如果你有一个预发布版本号的软件包(例如2.0.0-beta.3)并且你运行了lerna version和一个非预先发布的版本(major、minor或patch)它将会发布那些之前发布的软件包以及自上次发布以来已经改变的软件包。对于使用常规提交的项目使用以下标志进行预发行管理--conventional-prerelease[14]: 将当前更改作为预发行版本发布。--conventional-graduate[15]: 将预发布版本的包升级为稳定版本。如果不使用上面的参数运行lerna version --conventional-commits则只有在版本已经在prerelease中时才会将当前更改作为prerelease释放。生命周期// preversion: 在设置版本号之前运行.
// version: 在设置版本号之后提交之前运行.
// postversion: 在设置版本号之后提交之后运行.lerna 将在lerna version期间运行npm 生命周期脚本[16]侦测更改的包选择版本号进行覆盖。在根目录运行preversion。对于每个更改的包按照拓扑顺序(所有依赖项在依赖关系之前): i. 运行preversion生命周期。 ii. 更新 package.json 中的版本。 iii. 运行version生命周期。在根目录运行version生命周期。如果可用[17]将更改文件添加到索引。如果可用[18]创建提交和标记。对于每个改变包按照词法顺序(根据目录结构的字母顺序): i. 运行postversion生命周期。在根目录运行postversion。如果可用[19]推动提交和标记到远程服务器。该流程会触发git push操作如果可用[20]创建发布。5lerna publish在当前项目中发布所有包lerna publish # 发布自上一个版本以来发生了变化的包
lerna publish from-git # 发布当前提交中标记的包
lerna publish from-package # 发布注册表中没有最新版本的包在运行时该命令做了下面几件事中的一个发布自上一个版本以来更新的包(背后调用了lerna version[21])。这是 lerna 2.x 版本遗留下来的。发布在当前提交中标记的包(from-git)。发布在最新提交时注册表中没有版本的包(from-package)。发布在前一次提交中更新的包(及其依赖项)的“金丝雀(canary)”版。注意: Lerna 永远不会发布标记为private的包package.json中的”private“: true在所有的发布过程中都有生命周期[22]在根目录和每个包中运行(除非使用了--ignore-scripts)。发布方式from-git除了 lerna version[23] 支持的语义化版本关键字外lerna publish也支持from-git关键字。这将会识别lerna version标记的包并将它们发布到 npm。这在您希望手动增加版本的 CI 场景中非常有用但要通过自动化过程一直地发布包内容本身。from-package与from-git关键字类似只是要发布的包列表是通过检查每个package.json确定的并且要确定注册表中是否存在任意版本的包。注册表中没有的任何版本都将被发布。当前一个lerna publish未能将所有包发布到注册表时就是他发挥的时候了。生命周期// prepublish: 在打包和发布包之前运行。
// prepare: 在打包和发布包之前运行在 prepublish 之后prepublishOnly 之前。
// prepublishOnly: 在打包和发布包之前运行只在 npm publish 时运行。
// prepack: 只在源码压缩打包之前运行。
// postpack: 在源码压缩打包生成并移动到最终目的地后运行。
// publish: 在包发布后运行。
// postpublish: 在包发布后运行。Lerna 将在lerna publish期间运行npm生命周期脚本[24]顺序如下:如果采用没有指定版本则运行所有版本生命周期脚本[25]如果可用[26]在根目录运行prepublish生命周期。在根目录中运行prepare生命周期。在根目录中运行prepublishOnly生命周期。在根目录运行prepack生命周期。对于每个更改的包按照拓扑顺序(所有依赖项在依赖关系之前):i. 如果可用[27]运行prepublish生命周期。ii. 运行prepare生命周期。iii. 运行prepublishOnly生命周期。iv. 运行prepack生命周期。v. 通过JS API[28]在临时目录中创建源码压缩包。 vi. 运行postpack生命周期。在根目录运行postpack生命周期。对于每个更改的包按照拓扑顺序(所有依赖项在依赖关系之前):i. 通过JS API[29]发布包到配置的注册表[30]。ii. 运行publish生命周期。iii. 运行postpublish生命周期。在根目录中运行publish生命周期。为了避免递归调用不要使用这个根生命周期来运行lerna publish。在根目录中运行postpublish生命周期。如果可用[31]将临时的 dist-tag 更新到最新6指令总览 (Commands)指令参考地址汉化[32]指令解释链接(英文)lerna publish在当前项目中发布包前往[33]lerna version更改自上次发布以来的包版本号前往[34]lerna bootstrap将本地包链接在一起并安装剩余的包依赖项前往[35]lerna list列出本地包前往[36]lerna changed列出自上次标记发布以来发生变化的本地包前往[37]lerna diff自上次发布以来的所有包或单个包的区别前往[38]lerna exec在每个包中执行任意命令前往[39]lerna run在包含该脚本中的每个包中运行npm脚本前往[40]lerna init创建一个新的Lerna仓库或将现有的仓库升级到Lerna的当前版本前往[41]lerna add向匹配的包添加依赖关系前往[42]lerna clean从所有包中删除node_modules目录前往[43]lerna import将一个包导入到带有提交历史记录的monorepo中前往[44]lerna link将所有相互依赖的包符号链接在一起前往[45]lerna create创建一个新的由lerna管理的包前往[46]lerna info打印本地环境信息前往[47]Part3typescriptTypeScript是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[48] 添加了 pnpm 链接器。 因此 Yarn 可以创建一个类似于 pnpm 创建的 node_modules 目录结构。此外Yarn 团队计划实现内容可寻址存储以提高磁盘空间效率。Npm 团队决定也采用 pnpm 使用的符号链接的 node_modules 目录结构相关 RFC[49]。lerna强大的版本管理能力完全可以弥补pnpm在包管理上的弱势两者协同支持的的呼喊声也越来越强烈相信将来lerna pnpm一定会成为最佳monorepo管理方案之一。参考资料点击原文查看················· 若川简介 ·················你好我是若川毕业于江西高校。现在是一名前端开发“工程师”。写有《学习源码整体架构系列》20余篇在知乎、掘金收获超百万阅读。从2014年起每年都会写一篇年度总结已经写了7篇点击查看年度总结。同时最近组织了源码共读活动帮助3000前端人学会看源码。公众号愿景帮助5年内前端人走向前列。识别上方二维码加我微信、拉你进源码共读群今日话题略。分享、收藏、点赞、在看我的文章就是对我最大的支持