4.3 KiB
4.3 KiB
name, description
| name | description |
|---|---|
| pnpm-hooks | 使用 pnpmfile 钩子自定义包解析与依赖行为 |
pnpm 钩子
pnpm 通过 .pnpmfile.cjs 提供钩子,用于自定义包的解析方式及其元数据的处理过程。
设置
在 workspace 根目录创建 .pnpmfile.cjs:
// .pnpmfile.cjs
function readPackage(pkg, context) {
// 修改包元数据
return pkg
}
function afterAllResolved(lockfile, context) {
// 修改 lockfile
return lockfile
}
module.exports = {
hooks: {
readPackage,
afterAllResolved
}
}
readPackage 钩子
在解析前为每个包调用。用于修改依赖、添加缺失的对等依赖或修复有问题的包。
添加缺失的对等依赖
function readPackage(pkg, context) {
if (pkg.name === 'some-broken-package') {
pkg.peerDependencies = {
...pkg.peerDependencies,
react: '*'
}
context.log(`已为 ${pkg.name} 添加 react 对等依赖`)
}
return pkg
}
覆盖依赖版本
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
}
移除不需要的依赖
function readPackage(pkg, context) {
// 移除会导致问题的可选依赖
if (pkg.optionalDependencies?.fsevents) {
delete pkg.optionalDependencies.fsevents
}
return pkg
}
替换包
function readPackage(pkg, context) {
// 替换已废弃的包
if (pkg.dependencies?.['old-package']) {
pkg.dependencies['new-package'] = pkg.dependencies['old-package']
delete pkg.dependencies['old-package']
}
return pkg
}
修复有问题的包
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 生成后调用。用于解析后的修改。
function afterAllResolved(lockfile, context) {
// 记录所有已解析的包
context.log(`已解析 ${Object.keys(lockfile.packages || {}).length} 个包`)
// 根据需要修改 lockfile
return lockfile
}
context 对象
context 对象提供实用工具:
function readPackage(pkg, context) {
// 记录日志消息
context.log('正在处理包...')
return pkg
}
与 TypeScript 一起使用
如需类型提示,可使用 JSDoc:
// .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
}
}
常见模式
按包名条件处理
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
}
应用于所有包
function readPackage(pkg, context) {
// 移除所有可选的 fsevents
if (pkg.optionalDependencies) {
delete pkg.optionalDependencies.fsevents
}
return pkg
}
调试解析过程
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、对等依赖)
- 需要日志记录/调试
故障排查
钩子未运行
- 确保文件名为
.pnpmfile.cjs(不是.js) - 确认文件位于 workspace 根目录
- 运行
pnpm install以触发钩子
调试钩子
# 查看钩子日志
pnpm install --reporter=append-only