preinstall 在 npm install 开始前执行,仅限根目录全量安装时触发,用于环境校验;postinstall 在所有依赖写入 node_modules 后运行,常用于构建或生成资源,但存在供应链安全风险。

在 npm 包的生命周期中,preinstall 和 postinstall 是两个常用的钩子脚本(npm lifecycle scripts),它们会在 npm install 执行前后自动触发,适合用来做依赖安装前的校验、环境检查,或安装后的构建、资源生成等操作。
preinstall:安装前执行,常用于校验或清理
preinstall 在 npm install 开始解析和下载依赖之前运行。注意:它 不会 在 npm install <pkg>(安装单个包)时触发,只在项目根目录执行 npm install(即安装 package.json 中所有依赖)时生效。
- 适合做 Node.js 版本检查、权限验证、临时文件清理
- 若脚本退出码非 0(如
process.exit(1)),整个npm install会中止 - 不建议在此阶段执行耗时构建或网络请求,会影响安装体验
示例:在 package.json 中添加:
"preinstall": "node check-node-version.js"
}
postinstall:安装后执行,最常用也最需谨慎
postinstall 在所有依赖安装完成(包括 node_modules 写入完毕)后运行。它在以下场景都会触发:
– 本地执行 npm install
– CI/CD 构建时拉取依赖后
– 甚至别人 npm install your-pkg 时也会执行(⚠️关键风险点)
- 常见用途:自动生成类型声明(
tsc --emitDeclarationOnly)、编译 TS、复制 assets、生成配置文件 - 务必避免副作用操作(如修改全局配置、弹窗、发请求),尤其不要在发布到 npm 的包里用
postinstall下载二进制或调用外部服务 - 如果只是给项目自身用,可加条件判断:
"postinstall": "if [ -f tsconfig.json ]; then tsc; fi"(Shell)或用 cross-env + node 脚本更可靠
如何写一个安全可靠的钩子脚本
- 用独立的
.js文件(如scripts/postinstall.js),比内联命令更易维护、调试和跨平台 - 开头加
#!/usr/bin/env node并设可执行权限(Linux/macOS),或统一通过node xxx.js调用 - 捕获异常并显式退出:
process.exit(0)表示成功,非零值表示失败 - 区分开发 vs 生产环境:可通过
process.env.NODE_ENV或npm_lifecycle_event判断当前是install还是ci - 避免硬编码路径,用
path.join(__dirname, '..')或process.cwd()定位项目根目录
替代方案:什么时候不该用 pre/postinstall
这些钩子不是万能的。以下情况建议换方式:
- 只想在 CI 或特定机器上运行?→ 改用 CI 配置(如 GitHub Actions 的
steps) - 需要确保某命令总在启动前运行?→ 改用
start脚本组合:"start": "npm run build && node server.js" - 要为库用户屏蔽构建细节?→ 提前构建好再发布,或用
prepare(仅在npm publish前运行,不影响下游安装) - 需要跨平台兼容性极强?→ 避免 shell 内联命令,优先选纯 JS 脚本 +
execa等库调用子进程


















