npm Scripts 进阶:生命周期钩子、参数传递和跨平台写法
npm scripts 是 Node.js 项目里最朴素的自动化工具——在 package.json 里写一行命令,npm run xxx 就能执行。但它能做的远不止 npm run dev,生命周期钩子和参数传递这两个特性,很多人不知道。
基础用法
json{ "scripts": { "dev": "nodemon index.js", "build": "webpack --mode production", "start": "node dist/index.js", "test": "jest --coverage", "lint": "eslint src/ --ext .ts" } }
bashnpm run dev # 运行 dev 脚本 npm run build # 运行 build 脚本 npm test # test 是特殊脚本,不需要 run npm start # start 也是特殊脚本,不需要 run
test、start、restart、stop 是 npm 的特殊脚本名——可以直接 npm xxx 执行,不用加 run。其他自定义脚本都要 npm run xxx。
生命周期钩子:自动执行的脚本
npm 为每个脚本提供了 pre 和 post 钩子——脚本执行前后自动运行同名前缀的脚本:
json{ "scripts": { "prebuild": "rimraf dist", "build": "tsc", "postbuild": "echo Build completed" } }
执行 npm run build 时,实际执行顺序是:prebuild → build → postbuild。
实用的钩子组合
json{ "scripts": { "pretest": "npm run lint", "test": "jest", "prebuild": "npm test", "build": "webpack --mode production", "postbuild": "npm run size" } }
跑 npm run build 的完整流程:lint → test → build → size check。任何一步失败,后续步骤不会执行。
注意:npm v7+ 取消了 install 的 pre/post 钩子
npm v7 起,preinstall、postinstall 等钩子不再自动执行(安全原因)。如果你的脚本需要依赖安装后执行,用 prepare:
json{ "scripts": { "prepare": "husky install" } }
prepare 在以下时机自动执行:
npm install之后npm publish之前git clone后执行npm install时
内置生命周期脚本
npm 定义了几个特殊脚本,在特定时机自动触发:
| 脚本名 | 触发时机 | 典型用途 |
|---|---|---|
prepare | install 后 / publish 前 | 初始化 husky、编译 |
prepublishOnly | publish 前(仅 publish) | 编译、跑测试 |
prepack | npm pack 前 | 编译 |
postinstall | install 后 | 原生模块编译 |
version | npm version 改版本号后 | 自动 commit changelog |
区分 prepublishOnly 和 prepare
prepublishOnly:只在npm publish时执行,npm install不执行prepare:npm publish和npm install都会执行
库项目用 prepublishOnly 做发布前检查(跑测试、确保编译),用 prepare 做初始化工作。
传递参数
bash# 错误写法——参数传给了 npm,不是脚本 npm run test --coverage # 正确写法——用 -- 分隔 npm run test -- --coverage npm run test -- --watchAll=false
-- 后面的参数会原样追加到脚本命令后面。所以 npm run test -- --coverage 等于执行 jest --coverage。
脚本里也可以用 --:
json{ "scripts": { "test": "jest", "test:watch": "npm run test -- --watch", "test:ci": "npm run test -- --ci --coverage" } }
这样不用重复写基础命令,只追加不同参数。
跨平台兼容
在 scripts 里写 shell 命令要注意跨平台——Windows 没有 rm -rf,也没有 && 的可靠支持。
用跨平台工具替代
| Unix 命令 | 跨平台替代 | 安装 |
|---|---|---|
rm -rf | rimraf | npm i -D rimraf |
mkdir -p | mkdirp | npm i -D mkdirp |
cp -r | cpy-cli | npm i -D cpy-cli |
&& | npm-run-all | npm i -D npm-run-all |
json{ "scripts": { "clean": "rimraf dist coverage", "build": "rimraf dist && tsc", "build:safe": "npm-run-all clean build" } }
npm-run-all 比 && 更可靠——它在所有平台上都能工作,还支持并行执行:
json{ "scripts": { "lint:js": "eslint src/", "lint:css": "stylelint src/", "lint": "npm-run-all --parallel lint:*" } }
--parallel 让 lint:js 和 lint:css 同时跑,速度翻倍。
环境变量
npm scripts 里可以直接使用环境变量:
json{ "scripts": { "start": "NODE_ENV=production node dist/index.js", "dev": "NODE_ENV=development nodemon src/index.js" } }
但 NODE_ENV=xxx 在 Windows 上不工作。跨平台方案用 cross-env:
bashnpm install -D cross-env
json{ "scripts": { "start": "cross-env NODE_ENV=production node dist/index.js" } }
组合脚本的模式
实际项目里的 scripts 通常这样组织:
json{ "scripts": { "dev": "nodemon src/index.ts", "build": "tsc", "start": "node dist/index.js", "lint": "eslint src/ --ext .ts", "test": "jest", "test:watch": "npm test -- --watch", "test:ci": "npm test -- --ci --coverage", "clean": "rimraf dist coverage", "prebuild": "npm run clean", "prepublishOnly": "npm-run-all lint test build", "release": "npm version patch && npm publish" } }
release 脚本组合了版本号更新和发布——npm version patch 自动改版本号并创建 git tag,npm publish 推到 registry。
常见问题
脚本里的命令找不到
npm scripts 执行时会把 node_modules/.bin 加到 PATH 里——所以可以直接用 jest、eslint、webpack,不需要写 ./node_modules/.bin/jest。但如果你用 bash -c "jest" 或在某些 CI 环境里,可能找不到。解决:用 npx 前缀。
脚本太长不好维护
拆成独立文件:
json{ "scripts": { "build": "bash scripts/build.sh", "deploy": "bash scripts/deploy.sh" } }
scripts/ 目录下放脚本文件,package.json 里只做调度。