--- name: pnpm-hooks description: 使用 pnpmfile 钩子自定义包解析与依赖行为 --- # pnpm 钩子 pnpm 通过 `.pnpmfile.cjs` 提供钩子,用于自定义包的解析方式及其元数据的处理过程。 ## 设置 在 workspace 根目录创建 `.pnpmfile.cjs`: ```js // .pnpmfile.cjs function readPackage(pkg, context) { // 修改包元数据 return pkg } function afterAllResolved(lockfile, context) { // 修改 lockfile return lockfile } module.exports = { hooks: { readPackage, afterAllResolved } } ``` ## readPackage 钩子 在解析前为每个包调用。用于修改依赖、添加缺失的对等依赖或修复有问题的包。 ### 添加缺失的对等依赖 ```js function readPackage(pkg, context) { if (pkg.name === 'some-broken-package') { pkg.peerDependencies = { ...pkg.peerDependencies, react: '*' } context.log(`已为 ${pkg.name} 添加 react 对等依赖`) } return pkg } ``` ### 覆盖依赖版本 ```js function readPackage(pkg, context) { // 修复所有 lodash 版本 if (pkg.dependencies?.lodash) { pkg.dependencies.lodash = '^4.17.21' } if (pkg.devDependencies?.lodash) { pkg.devDependencies.lodash = '^4.17.21' } return pkg } ``` ### 移除不需要的依赖 ```js function readPackage(pkg, context) { // 移除会导致问题的可选依赖 if (pkg.optionalDependencies?.fsevents) { delete pkg.optionalDependencies.fsevents } return pkg } ``` ### 替换包 ```js function readPackage(pkg, context) { // 替换已废弃的包 if (pkg.dependencies?.['old-package']) { pkg.dependencies['new-package'] = pkg.dependencies['old-package'] delete pkg.dependencies['old-package'] } return pkg } ``` ### 修复有问题的包 ```js function readPackage(pkg, context) { // 修复错误的 exports 字段 if (pkg.name === 'broken-esm-package') { pkg.exports = { '.': { import: './dist/index.mjs', require: './dist/index.cjs' } } } return pkg } ``` ## afterAllResolved 钩子 在 lockfile 生成后调用。用于解析后的修改。 ```js function afterAllResolved(lockfile, context) { // 记录所有已解析的包 context.log(`已解析 ${Object.keys(lockfile.packages || {}).length} 个包`) // 根据需要修改 lockfile return lockfile } ``` ## context 对象 `context` 对象提供实用工具: ```js function readPackage(pkg, context) { // 记录日志消息 context.log('正在处理包...') return pkg } ``` ## 与 TypeScript 一起使用 如需类型提示,可使用 JSDoc: ```js // .pnpmfile.cjs /** * @param {import('type-fest').PackageJson} pkg * @param {{ log: (msg: string) => void }} context * @returns {import('type-fest').PackageJson} */ function readPackage(pkg, context) { return pkg } module.exports = { hooks: { readPackage } } ``` ## 常见模式 ### 按包名条件处理 ```js function readPackage(pkg, context) { switch (pkg.name) { case 'package-a': pkg.dependencies.foo = '^2.0.0' break case 'package-b': delete pkg.optionalDependencies.bar break } return pkg } ``` ### 应用于所有包 ```js function readPackage(pkg, context) { // 移除所有可选的 fsevents if (pkg.optionalDependencies) { delete pkg.optionalDependencies.fsevents } return pkg } ``` ### 调试解析过程 ```js function readPackage(pkg, context) { if (process.env.DEBUG_PNPM) { context.log(`${pkg.name}@${pkg.version}`) context.log(` 依赖: ${Object.keys(pkg.dependencies || {}).join(', ')}`) } return pkg } ``` ## 钩子 vs overrides | 特性 | 钩子(.pnpmfile.cjs) | overrides | |---------|----------------------|-----------| | 复杂度 | 可使用 JavaScript 逻辑 | 仅声明式 | | 范围 | 任意包元数据 | 仅版本 | | 适用场景 | 复杂修复、条件逻辑 | 简单版本锁定 | **简单的版本修复优先使用 overrides**。**在以下场景使用钩子**: - 需要条件逻辑 - 需要非版本类的修改(exports、对等依赖) - 需要日志记录/调试 ## 故障排查 ### 钩子未运行 1. 确保文件名为 `.pnpmfile.cjs`(不是 `.js`) 2. 确认文件位于 workspace 根目录 3. 运行 `pnpm install` 以触发钩子 ### 调试钩子 ```bash # 查看钩子日志 pnpm install --reporter=append-only ```