Files
2026-07-13 21:35:53 +08:00

4.3 KiB
Raw Permalink Blame History

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、对等依赖)
  • 需要日志记录/调试

故障排查

钩子未运行

  1. 确保文件名为 .pnpmfile.cjs(不是 .js
  2. 确认文件位于 workspace 根目录
  3. 运行 pnpm install 以触发钩子

调试钩子

# 查看钩子日志
pnpm install --reporter=append-only