234 lines
4.3 KiB
Markdown
234 lines
4.3 KiB
Markdown
---
|
||
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
|
||
```
|
||
|
||
<!--
|
||
Source references:
|
||
- https://pnpm.io/pnpmfile
|
||
-->
|