Hi~又是一段时间没有更新了。前几天 stateof js 2021
的调查结果发布了,今年在里面增加了关于 monorepo tools 的调查报告(参考链接: https://2021.stateofjs.com/en-US/libraries/monorepo-tools )。
其中 pnpm 一举登顶 2021 年最受欢迎的 monorepo 工具链。同时在用户使用广度以及其他方面也取得了不错的成绩。
刚好前段时间调试过一阵子 pnpm 以及贡献过一些代码,因此笔者对 pnpm 的结构也算有些了解,这篇文章将在笔者的理解范围之内给大家做一个代码结构解析,如果有问题可以直接指出来,一起探讨学习。
首先 pnpm 的代码主要集中在根目录下的 packages
目录中,pnpm 自身所采用的以 pnpm workspace 作为 monorepo 的管理工具,其里面的一些模块都是作为一个个独立的子包存在于 packages 目录下面。
因为 pnpm 本身的 monorepo 主要管理的都是一些工具库相关的子包,因此其采用的发包方案正是 changesets,具体可以参考我之前的文章: [https://mp.weixin.qq.com/s/DkXmsAGcT6_xlePYgqy4Rw。]
packages 下各个子包的具体目录结构可以参考以下的结构:
.
// 依赖安装的核心逻辑代码包
├── core
// 核心包的日志输出包
├── core-loggers
// 日志打印的包
├── default-reporter
// 解析依赖包路径的包(包括软链接的情况等)
├── dependency-path
// filter 逻辑相关的包
├── filter-workspace-packages
// monorepo 下 package、workspace 相关的包
├── find-packages
├── find-workspace-dir
├── find-workspace-packages
// git 相关的工具包
├── git-fetcher
├── git-resolver
// 处理依赖提升的工具包
├── hoist
// 生命周期相关的包
├── lifecycle
// lock 文件相关的一些工具包
├── lockfile-file
├── lockfile-to-pnp
├── lockfile-types
├── lockfile-utils
├── lockfile-walker
// 处理 npm registry 相关、以及解析对应 npm 包的包
├── normalize-registries
├── npm-registry-agent
├── npm-resolver
// 处理 cli 参数相关的包
├── parse-cli-args
// 解析依赖
├── parse-wanted-dependency
// monorepo 生成依赖图相关包
├── pkgs-graph
// plugin-commands 包都是涉及对应子命令逻辑相关
├── plugin-commands-audit
├── plugin-commands-env
├── plugin-commands-installation
├── plugin-commands-listing
├── plugin-commands-outdated
├── plugin-commands-publishing
├── plugin-commands-script-runners
├── plugin-commands-server
├── plugin-commands-setup
├── plugin-commands-store
// pnpm 整个项目主包
├── pnpm
// 读项目 .pnpmfile.cjs 的包
├── pnpmfile
// 读项目的 pkg.json 工具包
├── read-project-manifest
// 用于提升 pnpm 中的项目依赖的包(类似于 yarn 的方式)
├── real-hoist
// 可视化输出依赖安装过程中的 peerDep 问题包
├── render-peer-issues
// 依赖安装过程中解析依赖使用
├── resolve-dependencies
//
├── resolve-workspace-range
├── resolver-base
// 用于降级命令到 npm 相关逻辑的包
├── run-npm
// 根据 pkg-graph 对子包进行排序
├── sort-packages
// 硬链接 store 管理相关的包
├── store-connection-manager
├── store-controller-types
// 将依赖添软链接到 node_modules 的包
├── symlink-dependency
// npm 压缩包的抓取以及解析的包
├── tarball-fetcher
├── tarball-resolver
// 写 pkg.json 的包
├── write-project-manifest
└── ...
pnpm 本身内部有很多的包,上面树状架构中,我已经省略掉了一些不常用到或者说是接近废弃的包(即便如此,仍然还是存在很多很多的包...)。
这里我主要根据 pnpm 官网中的各命令行来对代码结构做个介绍,其实也有很多命令封装使用到了相同模块的代码。例如 install
、update
、add
等命令。
首先 pnpm 整个项目的主入口包文件为 packages/pnpm
这个包里面,这个包名称也直接叫做 pnpm
,其中 main.ts
文件是其入口文件,这个文件会处理掉用户传进来的一些参数,然后根据处理后的不同的参数对各命令做一个下发执行工作,下发后的命令参数再到各个包里面去,从而执行里面对应的逻辑。
处理参数用到的包为 @pnpm/parse-cli-args
,它会接收到用户传递进来的命令行参数,然后将其处理成一个 pnpm 内部的统一格式,例如用户输入如下命令:
pnpm add -D axios
这里传进来的一些参数都会被 parseCliArgs
这个方法处理:
例如 add
会被处理给 cmd
字段,一些裸的参数例如 axios
会被放进 cliParams
这个数组中,-D
这个参数在 cliOptions
里面去。处理后的这些变量以及参数用于主入口文件后续代码执行逻辑的判断。具体的判断逻辑可以在调试的时候遇到了,再去看对应的入口逻辑判断调试即可,这里不做具体的介绍。
入口包里还会用到的内部包有 @pnpm/find-workspace-packages
以及 @pnpm/filter-workspace-packages
。
findWorkspacePackages
在入口文件中用于找到 pnpm workspace(适用于 monorepo 项目)中所有包的一些信息(例如名称、路径等)。
filterPackages
相对而言来说是个比较关键的包,pnpm 官方有一篇文档专门介绍了 --filter
这一功能模块(参考: https://pnpm.io/filtering),它为几乎所有的 pnpm 命令提供了一个很简单且实用的筛选功能,根据用户传递进来的筛选参数对 monorepo 下的子包进行一个筛选,会根据筛选参数(例如 ...
)输出帅选出来的对应包以及相关信息。
在 main.ts
中会通过调用当前包下面的 cmd
目录下面的方法(pnpmCmds
),来完成各命令的分发。
如果 cmd 值为 add
、install
、update
等这些涉及和依赖安装相关的包,则会走 @pnpm/plugin-commands-installation
这个包里面对应的子命令逻辑(基本上 pnpm 所有的核心模块都围绕依赖安装这一块展开)。
如果 cmd 值为 pack
、publish
这一类涉及到打包发布的包,则会走 @pnpm/plugin-commands-publishing
这个包的逻辑。
如果 cmd 值为 run
、exec
、 dlx
等这些和命令执行相关的方法,则会走 @pnpm/plugin-commands-script-runners
这个包的逻辑。
这里更多相关的逻辑参考 pnpm/src/cmd
这一块的命令挂载详情。
下面我会根据官网的 CLI commands 来对这里面涉及到的逻辑进行一个讲解。
这部分可以说是整个 pnpm 最核心的一部分了,其中涉及到了 pnpm install
、pnpm add <deps>
等依赖管理相关的核心命令。
在上一节提到这一块的逻辑主要在 pnpm 下的 @pnpm/plugin-commands-installation
这个包中完成,这里只是简单介绍一下这一块的逻辑以及引用到的包,并不做具体的讨论,因为关于 pnpm 的依赖安装原理真的要结合代码去介绍原理的话,是可以再去写一整篇文章的。
这一块依赖管理的核心逻辑是在对应包目录下的 src/installDeps
这个目录下,几乎所有依赖相关的命令最后的逻辑都会在这里中转执行,可以看到包括 install
、add
、update
命令的核心逻辑都会在这一块执行。具体还是根据用户传递进来的参数进行逻辑转换:
const result = await mutateModules([
{
...mutatedProject,
dependencySelectors,
manifest: updatedImporter.manifest,
peer: false,
targetDependenciesField: 'devDependencies',
},
], installOpts)
这里简单截取一下对应的依赖安装执行逻辑调用的方法,这里的 mutateModules
方法来自于包 @pnpm/core
,该包为整个 pnpm 项目的核心包,一些关键性的核心逻辑(例如依赖安装等)都是在这里实现,具体看实现可以参考源码。
依赖管理这里还会涉及到一些其他的包:
用于处理 lifeCycle 方法的 @pnpm/lifecycle
输出日志(例如依赖安装过程中的日志打印)的 @pnpm/core-loggers
、@pnpm/logger
依赖安装过程中生成、更新 pnpm-lock.yaml 文件的 @pnpm/lockfile-file
依赖安装过程中解析依赖并拉取依赖包的 @pnpm/resolve-dependencies
之前笔者在调试 pnpm update
的一个 bug 的时候,就是从 plugin-command-installation
到 resolve-dependencies
一步步抽丝剥茧,最后找到问题出现在一个库函数的语句处理里面,具体可以参考 pr: https://github.com/pnpm/pnpm/pull/4243。
如果你想调试 pnpm 的话,其实在 pnpm 的源码仓库下面有个 CONTRIBUTING.md
文档,里面比较推荐的方式是使用 pnpm run compile
对项目子包进行一个整体的编译,然后通过 node <repo_dir>/packages/pnpm [command]
的方式进行调试。
但实际上这种方式效率比较低下,很多时候代码修改了,调试的时候并不符合预期,修改完成之后又需要再次修改代码进行重新编译。
之前有一段时间调试 pnpm 的经历,这里给大家分享一下我个人的一些调试经验。
在 packages/pnpm
的 bin 目录下有个 pnpm.cjs
文件,里面的 require
方法指定了 pnpm 在执行的时候走那一块的逻辑:
这里默认的逻辑走的是打包后的 dist
目录下的代码逻辑,pnpm 的 compile 每次编译产物的默认目录都是在 dist 目录,但这里如果只是调试的话,我们其实可以完全不走 dist 目录下的产物代码逻辑。
之前笔者给 pnpm 提过一个 pr,在下面加上了一段用于走本地产物代码,在上面截图中也可以看到,这里调试的时候只需要注释掉走 dist
代码的那段逻辑,然后去走 lib
目录下的代码即可:
同时目前基本上 pnpm 下大部分正在维护的子包使用 typescipt 在开发,笔者之前还给一些库补上了 tsc --watch
命令:
因此如果想通过一种即时编译的方式去调试 pnpm 源码的话,可以直接到对应的子包下面将对应子包的 start 命令给 run 起来。然后针对不同的子包去进行一个调试的工作。以下为笔者的一个调试流程,可以提供来参考。
例如调试 pnpm 下面的一个子包,以 @pnpm/plugin-commands-installation
为例子。
首先可以对整个包代码执行一次全量的编译,防止有些包代码同步之后本地产物没更新,直接在整个项目的根目录下执行一次:
pnpm run compile
这次时间可能会比较久一点,但能保证后面一些被引用到的包且我们不去调试包的产物是最新的,防止出现一些包出现 require
不到的问题。
然后直接 cd
到需要调试的包目录下面,同时主包也要 run 起来,注意这里要把上一节提到的入口代码修改好。这里笔者一般是起多个终端进程,然后将该包的 ts 编译 run 起来:
cd packages/pnpm && npm run start
cd packages/plugin-commands-installation && npm run start
接下来就可以找个真实的 pnpm 项目来进行调试了。
例如这里以 naive-ui (https://www.naiveui.com/)这个项目(使用 pnpm 作为依赖管理)作为例子,这里可以在 plugin-commands-installation
中需要调试的代码打上断点,然后通过 vscode 的 debug terminal
来进行调试:
# 在调试项目的目录下,例如笔者这里是 naive-ui
node ~/path/to/pnpm/packages/pnpm install
这样通过 node 直接到指定的 pnpm 源文件目录去进行调试,这时命令就会分发到对应代码逻辑里面去,前面设置的断点就会很快生效。参考如图:
这样就可以相对简洁且能直接针对源码进行调试了,如果有代码修改也可以在源码里面修改之后直接进行调试。
不过这样调试也有个缺点,例如调试依赖层级比较深的库的时候,会出现同时起很多进程的现象,例如下图为笔者调试 pnpm 依赖安装流程时,对各个库进行断点观察的图:
图中一共起了 6 个进程,但总的来说的话,还是要比去构建产物里面进行调试找问题要简洁明了得多。
目前 pnpm 已经在 2021 年取得了不俗的成绩,期待 2022 年这一年同样也能带来更多惊喜的 feature。同时也期待越来越多的 contributor 能参与到 pnpm 的源码建设中来,一起共同建设可能是未来最有前景的包管理工具。
也喜欢这篇文章能给大家带来收获,期待越来越好~
本文由哈喽比特于2年以前收录,如有侵权请联系我们。
文章来源:https://mp.weixin.qq.com/s/cbWFzWbKxz4AUQFY6rsrfA
京东创始人刘强东和其妻子章泽天最近成为了互联网舆论关注的焦点。有关他们“移民美国”和在美国购买豪宅的传言在互联网上广泛传播。然而,京东官方通过微博发言人发布的消息澄清了这些传言,称这些言论纯属虚假信息和蓄意捏造。
日前,据博主“@超能数码君老周”爆料,国内三大运营商中国移动、中国电信和中国联通预计将集体采购百万台规模的华为Mate60系列手机。
据报道,荷兰半导体设备公司ASML正看到美国对华遏制政策的负面影响。阿斯麦(ASML)CEO彼得·温宁克在一档电视节目中分享了他对中国大陆问题以及该公司面临的出口管制和保护主义的看法。彼得曾在多个场合表达了他对出口管制以及中荷经济关系的担忧。
今年早些时候,抖音悄然上线了一款名为“青桃”的 App,Slogan 为“看见你的热爱”,根据应用介绍可知,“青桃”是一个属于年轻人的兴趣知识视频平台,由抖音官方出品的中长视频关联版本,整体风格有些类似B站。
日前,威马汽车首席数据官梅松林转发了一份“世界各国地区拥车率排行榜”,同时,他发文表示:中国汽车普及率低于非洲国家尼日利亚,每百户家庭仅17户有车。意大利世界排名第一,每十户中九户有车。
近日,一项新的研究发现,维生素 C 和 E 等抗氧化剂会激活一种机制,刺激癌症肿瘤中新血管的生长,帮助它们生长和扩散。
据媒体援引消息人士报道,苹果公司正在测试使用3D打印技术来生产其智能手表的钢质底盘。消息传出后,3D系统一度大涨超10%,不过截至周三收盘,该股涨幅回落至2%以内。
9月2日,坐拥千万粉丝的网红主播“秀才”账号被封禁,在社交媒体平台上引发热议。平台相关负责人表示,“秀才”账号违反平台相关规定,已封禁。据知情人士透露,秀才近期被举报存在违法行为,这可能是他被封禁的部分原因。据悉,“秀才”年龄39岁,是安徽省亳州市蒙城县人,抖音网红,粉丝数量超1200万。他曾被称为“中老年...
9月3日消息,亚马逊的一些股东,包括持有该公司股票的一家养老基金,日前对亚马逊、其创始人贝索斯和其董事会提起诉讼,指控他们在为 Project Kuiper 卫星星座项目购买发射服务时“违反了信义义务”。
据消息,为推广自家应用,苹果现推出了一个名为“Apps by Apple”的网站,展示了苹果为旗下产品(如 iPhone、iPad、Apple Watch、Mac 和 Apple TV)开发的各种应用程序。
特斯拉本周在美国大幅下调Model S和X售价,引发了该公司一些最坚定支持者的不满。知名特斯拉多头、未来基金(Future Fund)管理合伙人加里·布莱克发帖称,降价是一种“短期麻醉剂”,会让潜在客户等待进一步降价。
据外媒9月2日报道,荷兰半导体设备制造商阿斯麦称,尽管荷兰政府颁布的半导体设备出口管制新规9月正式生效,但该公司已获得在2023年底以前向中国运送受限制芯片制造机器的许可。
近日,根据美国证券交易委员会的文件显示,苹果卫星服务提供商 Globalstar 近期向马斯克旗下的 SpaceX 支付 6400 万美元(约 4.65 亿元人民币)。用于在 2023-2025 年期间,发射卫星,进一步扩展苹果 iPhone 系列的 SOS 卫星服务。
据报道,马斯克旗下社交平台𝕏(推特)日前调整了隐私政策,允许 𝕏 使用用户发布的信息来训练其人工智能(AI)模型。新的隐私政策将于 9 月 29 日生效。新政策规定,𝕏可能会使用所收集到的平台信息和公开可用的信息,来帮助训练 𝕏 的机器学习或人工智能模型。
9月2日,荣耀CEO赵明在采访中谈及华为手机回归时表示,替老同事们高兴,觉得手机行业,由于华为的回归,让竞争充满了更多的可能性和更多的魅力,对行业来说也是件好事。
《自然》30日发表的一篇论文报道了一个名为Swift的人工智能(AI)系统,该系统驾驶无人机的能力可在真实世界中一对一冠军赛里战胜人类对手。
近日,非营利组织纽约真菌学会(NYMS)发出警告,表示亚马逊为代表的电商平台上,充斥着各种AI生成的蘑菇觅食科普书籍,其中存在诸多错误。
社交媒体平台𝕏(原推特)新隐私政策提到:“在您同意的情况下,我们可能出于安全、安保和身份识别目的收集和使用您的生物识别信息。”
2023年德国柏林消费电子展上,各大企业都带来了最新的理念和产品,而高端化、本土化的中国产品正在不断吸引欧洲等国际市场的目光。
罗永浩日前在直播中吐槽苹果即将推出的 iPhone 新品,具体内容为:“以我对我‘子公司’的了解,我认为 iPhone 15 跟 iPhone 14 不会有什么区别的,除了序(列)号变了,这个‘不要脸’的东西,这个‘臭厨子’。