























在处理某一条 PR 时,由于对方使用 pnpm,而我仍在项目中使用 npm,产生了一些冲突与误会。
我在开发初期的确考虑过使用 pnpm,不过处于一些奇怪的理由,换回了 npm:
npm 的扁平化依赖安装策略导致了幽灵依赖的特性,若模块 A 包含子依赖 B,则可以在不显式安装 B 的情况下直接在项目中 import 使用,这使得 package.json 中只包含最少数量的依赖,看上去比较整洁;npm 在多数场景下仍是使用人数最多的 Node.js 包管理器,私以为在开发中使用可以兼顾大多数缺乏开发经验的开发者;但在这条 PR 中,为了逐渐使项目趋于规范,我显式安装了一部分幽灵依赖。
既然已经违背了最主要的第 1 条理由,那差不多是时候迁移到 pnpm 了。
老生常谈的内容,还是简单概括一下 pnpm 的优势:
pnpm 采用内容寻址的存储方式2,所有模块都保存在磁盘的某一单一位置。
因此在项目中安装依赖时,会从该位置进行硬链接而不占用额外的磁盘空间,进而在安装速度远超 npm 的同时,相较 npm 简单粗暴复制文件的方式,能够节省大量存储空间。
不同场景下主流 Node.js 包管理器的性能表现可参考 Benchmarks。
此外,pnpm 亦有许多其独有的特性,详细可阅读 Feature Comparison。
最重要的是,pnpm 提供了 pnpm import 命令3,在我的项目中,即可以直接根据 package-lock.json 生成对应的 pnpm-lock.yaml。
接着删除原有的 node_modules 目录及 package-lock.json 文件,然后使用 pnpm install 以其特有的结构重新安装依赖。
然后更新脚本命令和 CI/CD 配置,将所有的 npm run ... 与 npx ... 替换为 pnpm ...。
对于 GitHub Actions,运行 actions/setup-node 前需使用 pnpm/action-setup 预安装对应的包管理器4:
...
- name: Setup pnpm
uses: pnpm/action-setup@v4
with:
version: 10
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '22'
cache: 'pnpm'
...最后为了增强规范性,可在 .gitignore 中添加对 package-lock.json 及 yarn.lock 的忽略。
使用 npm 时,为了实现全覆盖的版本更新,使用了 npm-check-updates 包作为一种曲线救国的方案。
pnpm 则提供了 --interactive | -i 选项,可以说是对 ncu 的全方位升级,至少在更新这件事上无需过多操心。
这部分才是我想记录的重点。
对于发布包而言,切换包管理或是只是一次 chore 更新,但对需要依靠完整仓库编译的主题项目而言,这无疑是个 BREAKING CHANGE。
其重要性我想并不是像之前那样简单一句注释「This is a breaking change.」就能搪塞过去的,必须清楚地在发布日志中体现出来。
于是必须对先前的发布流程进行改造。
googleapis/release-please-action 提供了其上游配置文件 release-please-config.json 的路径选项,但 googleapis/release-please 似乎并未完整公开其配置文档,只能参照着 Schema 和源码差不多改改。
release-please-action 提供了 release-type 配置,我之前将其配置为 node,即可自动更新 package.json 文件。只是文档中提到,这是一种简单策略,一经提供将无法配置高级选项。
起初没看到这段文字,跌跌撞撞触发了许多不必要的 PR,最后还是在代码中发现的这个事实…
function loadOrBuildManifest(github: GitHub, inputs: ActionInputs): Promise<Manifest> {
if (inputs.releaseType) {
core.debug('Building manifest from config');
return Manifest.fromConfig(
github,
github.repository.defaultBranch,
{
releaseType: inputs.releaseType,
includeComponentInTag: inputs.includeComponentInTag,
changelogHost: inputs.changelogHost,
versioning: inputs.versioningStrategy,
releaseAs: inputs.releaseAs,
},
{
fork: inputs.fork,
skipLabeling: inputs.skipLabeling,
},
inputs.path
);
}
// ...
core.debug('Loading manifest from config file');
return Manifest.fromManifest(
github,
github.repository.defaultBranch,
inputs.configFile,
inputs.manifestFile,
manifestOverrides
).then(manifest => {
// ...
});
}那么接下来,创建 release-please-config.json:
{
"$schema": "https://raw.githubusercontent.com/googleapis/release-please/main/schemas/config.json",
"packages": {
".": {
"versioning": "default",
"release-type": "node",
"include-component-in-tag": false,
"bump-minor-pre-major": true,
"changelog-sections": [
// ...
{ "type": "chore", "section": "Miscellaneous Chores", "hidden": false },
// ...
]
}
}
}release-type:和工作流中一样,指定项目类型;include-component-in-tag:在版本标签中指定组件名称,此处与之前的简单策略保持一致,设置为 false,否则可能无法衔接之前的版本进行迭代;bump-minor-pre-major:这是我所需要的最重要的项,描述为「如果版本 < 1.0.0,则重大更改仅会影响次要版本」,也就是版本规范中第 4 条的内容;changelog-sections:配置在发布日志中显示哪些类型的提交,这里启用了 chore 以记录重要变更,其中 section 的值来自工厂函数的默认值。然后创建版本追踪文件 .release-please-manifest.json,根据先前的失败 PR 来看,填写当前的版本号即可:
接下来进行了一些毫无必要的脑瘫操作…
我希望这是最后一次提交,且不想再污染 PR 历史了,于是安装了 act 试图在本地测试…
但 release-please-action 显然是一个强依赖于 GitHub API 的工具,无论是环境还是权限的配置都极为麻烦,且似乎不太可行,最终放弃了。
好在,在想到使用 release-please 命令行工具进行 dry-run 测试之前,版本已经按照预期成功发布了。
Footnotes
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。