scripts 是什么
{
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "eslint .",
"test": "vitest",
"typecheck": "tsc --noEmit"
}
}
pnpm run dev # 跑 scripts.dev pnpm dev # 简写(非保留字的 script 名) pnpm test # 特殊:test/start/stop 可省 run
pre / post 钩子
{
"scripts": {
"prebuild": "rimraf dist",
"build": "tsc",
"postbuild": "echo 'build done'"
}
}
pnpm build 会依次跑 prebuild → build → postbuild。
pnpm 默认不跑用户自定义的 pre/post
出于性能,pnpm 从某个版本起不自动跑
出于性能,pnpm 从某个版本起不自动跑
prefoo/postfoo——只有内置的 preinstall/postinstall 等 npm 规范钩子还跑。要启用自定义钩子,在 .npmrc 加 enable-pre-post-scripts=true。
npm 生命周期钩子(pnpm 自动跑的)
| 钩子 | 触发时机 |
|---|---|
preinstall | install 前(常用来检查 node 版本) |
postinstall | 包装完后;编译原生模块、下载资源常用 |
prepare | 发包前 + 本地 install 后,常跑 husky init |
prepack | npm pack / publish 前 |
prepublishOnly | pnpm publish 前(典型:先 build 再发) |
onlyBuiltDependencies(安全)
# pnpm-workspace.yaml onlyBuiltDependencies: - esbuild - sharp - @prisma/client
v9 起,pnpm 默认不跑第三方包的 postinstall 脚本——供应链攻击门被堵死一道。只有白名单里的能跑。第一次 install 会提示「某些包想跑脚本,要不要允许」。
env 变量
pnpm run build # 自动设置的变量: # npm_package_name=my-app # npm_package_version=1.0.0 # npm_lifecycle_event=build # npm_execpath=/path/to/pnpm # PATH=./node_modules/.bin:$PATH
scripts 里可以直接写 "prebuild": "echo $npm_package_version"。
把参数传给 script
pnpm run test -- --watch # -- 之后的参数原样传给 vitest pnpm test --watch # v9 起也支持(不需要 --)
pnpm 发布到 npm
# 单个包 pnpm publish # 整个 monorepo(遍历所有非 private 的) pnpm publish -r # 干跑看效果,不真发 pnpm publish -r --dry-run # 指定 tag pnpm publish --tag beta # npm install mypkg@beta 安装这个 tag
Changesets 发版流程
Step 1:安装
pnpm add -Dw @changesets/cli pnpm changeset init # 生成 .changeset/ 目录和 config.json
// .changeset/config.json { "$schema": "https://unpkg.com/@changesets/config/schema.json", "changelog": "@changesets/cli/changelog", "commit": false, "access": "public", "baseBranch": "main", "updateInternalDependencies": "patch", "ignore": [] }
Step 2:记录改动
pnpm changeset # 交互式: # 1. 选择哪些包被影响 # 2. 选择版本类型(patch/minor/major) # 3. 写变更说明 # 生成 .changeset/abc-xxx-yyy.md
<!-- .changeset/abc-xxx-yyy.md -->
---
"@myorg/ui": minor
"@myorg/utils": patch
---
Add new Button variant. Fix utils.formatDate edge case.
Step 3:进入 PR 评审
.changeset/*.md 文件进 git,PR 里能看到影响哪些包、升什么版本——Code review 有依据。
Step 4:merge 后升版
pnpm changeset version # 1. 读所有 .changeset/*.md # 2. 更新对应包的 package.json version # 3. 更新 CHANGELOG.md # 4. 更新 pnpm-lock.yaml # 5. 删掉已消费的 .changeset/*.md
Step 5:发包
pnpm publish -r --access public # workspace 协议自动展开为版本号 # 只发 version 比 npm 上新的包
GitHub Actions 自动发版
# .github/workflows/release.yml name: Release on: push: branches: [main] jobs: release: runs-on: ubuntu-latest permissions: contents: write pull-requests: write id-token: write # npm provenance steps: - uses: actions/checkout@v4 - uses: pnpm/action-setup@v4 - uses: actions/setup-node@v4 with: node-version: '20' cache: 'pnpm' registry-url: 'https://registry.npmjs.org' - run: pnpm install --frozen-lockfile - run: pnpm -r build - uses: changesets/action@v1 with: publish: pnpm changeset publish version: pnpm changeset version title: 'chore: version packages' env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} NPM_TOKEN: ${{ secrets.NPM_TOKEN }} NPM_CONFIG_PROVENANCE: 'true'
changesets/action 的魔法
- 检测到
.changeset/*.md→ 自动开 "Version Packages" PR - PR merge → 自动跑
pnpm changeset publish发包 + 打 git tag - 全流程 GitOps,人只负责写 changeset 和 review。
npm provenance
# .npmrc provenance=true
npm 2023 起支持 provenance:发布时附带签名,证明「这个 tarball 是从这个 git commit 在 GitHub Actions 构建的」。npm 网页会打「已验证」标——供应链信任的基石。要求 id-token: write 权限。
生产落地 checklist
锁 pnpm 版本
根
package.json 的 packageManager: "pnpm@9.12.0";CI 启用 Corepack。lockfile 进 git
pnpm-lock.yaml 是可复现的唯一保证。.npmrc 进 git
registry、auto-install-peers、engine-strict 这些共享——token 走
${VAR} 引用,不入库。CI 用
--frozen-lockfile防止偷偷升版本。
onlyBuiltDependencies 白名单
新项目开始就列好必需的 native 包,新增要 PR 讨论。
Catalog 统一版本
核心依赖(React/TS/Vite)放 catalog,消除漂移。
Changesets + CI 自动发版
不要手工
pnpm publish——所有 release 走 GitHub Actions + provenance。patches 目录进 git
任何第三方改动都走
pnpm patch,别手改 node_modules。Turborepo 缓存
多包大仓必配,远程缓存让同事共享 CI 产物。
常用命令速查
| 命令 | 用途 |
|---|---|
pnpm i | install,按 lockfile 装 |
pnpm i --frozen-lockfile | CI 用,严格不升版本 |
pnpm add <pkg> | 加依赖 |
pnpm -F <pkg> add <dep> | 给指定包加依赖 |
pnpm add -Dw <pkg> | 加到根 workspace devDep |
pnpm -r build | 所有包跑 build,按拓扑 |
pnpm -F "...[origin/main]" test | 改动影响到的包测试 |
pnpm why <pkg> | 解释为什么装了 |
pnpm outdated -r | 看所有可升版本 |
pnpm dlx <pkg> | 临时运行一个包 |
pnpm patch <pkg@ver> | 给第三方包打补丁 |
pnpm deploy <path> | 实体化子包到独立目录 |
pnpm store prune | 清 store 中未引用的文件 |
pnpm changeset publish | 按 changesets 记录发版 |
后续学习路线
- Turborepo:任务编排 + 本地/远程构建缓存,pnpm monorepo 的黄金搭档(本站有专题)
- Nx:更重的 monorepo 框架,自带代码生成和影响图可视化
- Verdaccio:自建 npm registry,私包 + 缓存代理
- Sigstore:供应链签名,结合 npm provenance
- Deno 2 / Bun:JS 包管理的"对岸",值得关注但暂不急迁
本章小结
- scripts 里自定义 pre/post 钩子默认不跑,需
enable-pre-post-scripts=true - 第三方
postinstall默认禁用,用onlyBuiltDependencies显式白名单 - Changesets 是 pnpm monorepo 发版的标准工作流:changeset → version → publish
- GitHub Actions + changesets/action 全自动发版 + npm provenance 签名
- 生产清单:锁版本、lockfile 进 git、frozen-lockfile、白名单、Catalog、patches、Turborepo