npm 脚本(npm scripts)是
package.json中最强大的功能之一,它可以将常见的开发任务(启动、测试、构建、部署等)封装成简单的命令,并利用庞大的 npm 生态系统实现自动化。本指南将从入门到精通,覆盖所有细节和最佳实践。
目录
- [什么是 npm 脚本](#什么是 npm 脚本 "#1-%E4%BB%80%E4%B9%88%E6%98%AF-npm-%E8%84%9A%E6%9C%AC")
- 定义和运行脚本
- 内置脚本快捷方式
- [生命周期钩子(pre / post)](#生命周期钩子(pre / post) "#4-%E7%94%9F%E5%91%BD%E5%91%A8%E6%9C%9F%E9%92%A9%E5%AD%90-pre--post")
- 传递参数给脚本
- 脚本中的环境变量
- 跨平台兼容性处理
- 并行与串行执行脚本
- [使用 npm 包提供的 CLI 工具](#使用 npm 包提供的 CLI 工具 "#9-%E4%BD%BF%E7%94%A8-npm-%E5%8C%85%E6%8F%90%E4%BE%9B%E7%9A%84-cli-%E5%B7%A5%E5%85%B7")
- 常用的实用脚本示例
- 脚本调试与日志
- 钩子脚本的深度应用
- 工作区(Workspaces)中的脚本
- 脚本安全注意事项
- 最佳实践与模式
1. 什么是 npm 脚本
在 package.json 中,scripts 字段定义了一组可以运行的自定义命令。这些命令会在项目的上下文中执行,并且 node_modules/.bin 会自动加入 PATH,因此可以直接使用本地安装的包的 CLI 工具。
json
{
"scripts": {
"hello": "echo 'Hello, npm scripts!'"
}
}
运行:
bash
npm run hello
# 输出:Hello, npm scripts!
核心优势:
- 简化命令 :
npm run build替代一长串构建命令。 - 环境隔离 :无需全局安装工具,项目内
devDependencies即可。 - 跨平台抽象 :用
cross-env等工具抹平系统差异。 - 生命周期钩子:自动在脚本前后执行预定义任务。
2. 定义和运行脚本
2.1 定义脚本
在 package.json 的 scripts 对象中添加键值对,值是要执行的 shell 命令。
json
{
"scripts": {
"build": "webpack --mode production",
"serve": "npx http-server ./dist -p 8080",
"clean": "rm -rf dist/",
"analyze": "webpack-bundle-analyzer dist/stats.json"
}
}
2.2 运行脚本
bash
# 基本运行
npm run build
# 运行自定义脚本
npm run serve
# 运行带有参数的脚本(见第5节)
npm run build -- --watch
# 列出所有可用脚本
npm run
2.3 脚本中的 & 和 && 的使用
&&:串行执行,前一个成功(退出码 0)才执行后一个。&:并行执行(后台运行),但跨平台行为不一致,推荐使用npm-run-all或concurrently。
json
{
"scripts": {
"build:css": "sass src/style.scss dist/style.css",
"build:js": "webpack",
"build": "npm run build:css && npm run build:js" // 串行
}
}
2.4 在脚本中调用另一个 npm 脚本
json
{
"scripts": {
"prebuild": "npm run clean", // 直接调用其他脚本
"build": "webpack",
"clean": "rm -rf dist/"
}
}
注意:npm run <script> 会启动一个新的 shell,开销稍大。直接写命令更高效,但可读性可能降低。
2.5 使用 node 执行 JavaScript 文件
如果脚本逻辑复杂,可以单独写一个 .js 文件:
json
{
"scripts": {
"seed": "node scripts/seed-database.js"
}
}
scripts/seed-database.js 可以使用 Node.js API 和项目中的模块。
3. 内置脚本快捷方式
npm 为一些常见脚本名提供了简写,可以省略 run。
| 脚本名 | 简写命令 | 说明 |
|---|---|---|
start |
npm start |
启动应用 |
stop |
npm stop |
停止应用 |
test |
npm test |
运行测试 |
restart |
npm restart |
重启(依次执行 stop, start) |
version |
npm version 时会触发,但不是简写 |
版本生命周期钩子 |
prestart, poststart 等 |
自动触发 | 生命周期 |
示例:
json
{
"scripts": {
"start": "node server.js",
"test": "jest",
"stop": "pkill -f 'node server.js'"
}
}
运行:
bash
npm start # 等同 npm run start
npm test # 等同 npm run test
npm stop # 等同 npm run stop
npm restart # 会执行 stop -> restart -> start
注意:npm restart 如果没有定义 restart 脚本,则默认执行 npm stop && npm start。
4. 生命周期钩子(pre / post)
npm 脚本支持 pre 和 post 前缀的钩子,它们会在主脚本前后自动执行。
4.1 基础钩子
对于任意脚本 myscript,如果存在 premyscript 和 postmyscript,运行 npm run myscript 时会依次执行:
premyscriptmyscriptpostmyscript
json
{
"scripts": {
"prebuild": "echo 'Preparing build...'",
"build": "webpack",
"postbuild": "echo 'Build completed. Cleaning up...'"
}
}
运行 npm run build 输出:
erlang
Preparing build...
webpack ... (build output)
Build completed. Cleaning up...
4.2 安装和发布钩子
特殊的生命周期钩子在 npm install 或 npm publish 时触发:
| 钩子 | 触发时机 |
|---|---|
preinstall |
在安装依赖之前运行 |
install / postinstall |
安装依赖后运行(常用:编译原生模块,下载二进制文件) |
prepublish |
发布前运行(已被 prepublishOnly 取代) |
prepublishOnly |
npm publish 之前运行 |
pack |
npm pack 之前运行 |
postpack |
npm pack 之后运行 |
preversion |
版本升级前运行 |
version |
版本升级后、提交 git 之前运行 |
postversion |
版本提交后运行(可用来 push tag) |
示例 :postinstall 常用来自动安装 Git hooks(husky)
json
{
"scripts": {
"postinstall": "husky install"
}
}
示例 :prepublishOnly 确保发布前构建
json
{
"scripts": {
"prepublishOnly": "npm run test && npm run build"
}
}
4.3 嵌套钩子
钩子可以链式触发。例如运行 npm run build,如果 prebuild 中又调用了 npm run clean,而 preclean 存在,则也会执行。
4.4 避免无限循环
不要在 pre hook 中调用自身,例如 prebuild 里运行 npm run build 会导致无限循环。
5. 传递参数给脚本
5.1 使用 -- 分隔符
npm 把脚本名后的参数分成两类:
- 传递给
npm run本身的参数(如--silent) - 传递给脚本命令的参数(需要用
--来分隔)
bash
npm run myscript -- --flag value
-- 之后的所有内容都会原样追加到脚本命令的末尾。
示例:
json
{
"scripts": {
"test": "jest"
}
}
运行:
bash
npm run test -- --coverage --verbose
# 实际执行:jest --coverage --verbose
5.2 在脚本内部使用参数
如果参数需要插入到命令中间,不能仅依靠追加。可以使用 $npm_config_ 变量或通过 shell 处理。
方法1:使用 node 脚本处理参数
json
{
"scripts": {
"deploy": "node scripts/deploy.js"
}
}
在 deploy.js 中通过 process.argv.slice(2) 获得参数。
方法2:使用环境变量传递(见第6节)
5.3 传递参数给多个命令
复杂场景下,可以使用 npm-run-all 或 concurrently 的命令行选项传递参数。
bash
npx npm-run-all build:css build:js -- --watch
# 会将 --watch 传递给每一个脚本
6. 脚本中的环境变量
6.1 预定义环境变量
npm 自动注入大量环境变量,可以在脚本中直接使用。
| 变量 | 说明 | 示例值 |
|---|---|---|
npm_package_name |
项目名称 | "my-app" |
npm_package_version |
项目版本 | "1.0.0" |
npm_package_config_<key> |
自定义配置(在 package.json 的 config 字段) |
见下文 |
npm_config_<key> |
npm 配置项(如 npm_config_registry) |
https://registry.npmjs.org/ |
npm_node_execpath |
Node.js 可执行文件路径 | /usr/local/bin/node |
npm_execpath |
npm 可执行文件路径 | /usr/local/lib/node_modules/npm/bin/npm-cli.js |
npm_lifecycle_event |
当前运行的脚本名 | "build" |
npm_lifecycle_script |
当前运行的脚本内容 | "webpack ..." |
6.2 自定义配置变量
在 package.json 中定义 config 字段:
json
{
"name": "my-app",
"config": {
"port": "3000",
"api_url": "https://api.example.com"
},
"scripts": {
"start": "node server.js --port=$npm_package_config_port"
}
}
在 shell 脚本中访问:
bash
echo "API URL: $npm_package_config_api_url"
用户可以通过 npm config set my-app:port 8080 覆盖(基于项目名)。
6.3 设置自定义环境变量
可以在脚本中直接设置变量(注意跨平台问题):
json
{
"scripts": {
"start": "NODE_ENV=production node app.js" // Unix
}
}
Windows 不兼容,可使用 cross-env(见第7节)。
6.4 环境变量的覆盖优先级
- 在 shell 中显式
export或设置(如NODE_ENV=dev npm start) - npm 注入的变量
package.json中的config字段- 默认值
6.5 通过 --env 传递参数(使用 webpack 等工具)
webpack-cli 支持 --env 参数:
json
{
"scripts": {
"build": "webpack --env production"
}
}
7. 跨平台兼容性处理
不同操作系统(Windows / Linux / macOS)的 shell 命令差异很大,直接写 Unix 命令可能在 Windows 上失败。
7.1 常见的跨平台问题
| Unix 风格 | Windows 问题 | 解决方案 |
|---|---|---|
NODE_ENV=production |
set NODE_ENV=production 无效 |
cross-env |
rm -rf dist |
rm 不存在 |
rimraf 或 del-cli |
cp src/* dist |
copy 语法不同 |
ncp 或 copyfiles |
cat file |
type |
使用 node 脚本 |
& 后台运行 |
不支持 | concurrently 或 npm-run-all |
$VAR |
%VAR% |
使用 cross-var 或 node 脚本 |
7.2 使用 cross-env 设置环境变量
bash
npm install --save-dev cross-env
json
{
"scripts": {
"start": "cross-env NODE_ENV=production node server.js"
}
}
cross-env 会在 Unix 上使用 NODE_ENV=production,在 Windows 上使用 set NODE_ENV=production。
7.3 使用 rimraf 删除文件/文件夹
bash
npm install --save-dev rimraf
json
{
"scripts": {
"clean": "rimraf dist coverage"
}
}
rimraf 是跨平台的 rm -rf 实现。
7.4 使用 mkdirp 创建目录
bash
npm install --save-dev mkdirp
json
{
"scripts": {
"setup": "mkdirp dist/public"
}
}
7.5 使用 copyfiles 复制文件
bash
npm install --save-dev copyfiles
json
{
"scripts": {
"copy": "copyfiles -u 1 src/**/*.html dist"
}
}
7.6 跨平台并行/串行执行
避免使用 & 或 && 直接传递,推荐使用 npm-run-all:
bash
npm install --save-dev npm-run-all
json
{
"scripts": {
"build": "npm-run-all --parallel lint test build:*",
"build:css": "sass ...",
"build:js": "webpack"
}
}
7.7 使用 shx 模拟 Unix 命令
shx 在 Node 中实现常用的 shell 命令。
bash
npm install --save-dev shx
json
{
"scripts": {
"clean": "shx rm -rf dist",
"copy": "shx cp -r assets dist/"
}
}
7.8 终极方案:写 Node.js 脚本
当命令特别复杂或涉及大量分支时,直接写 JS 脚本最可靠。
json
{
"scripts": {
"deploy": "node scripts/deploy.js"
}
}
deploy.js 中使用 fs, child_process 等模块,可以检测 process.platform 做不同处理。
8. 并行与串行执行脚本
8.1 基础:&& 和 &
- 串行 (顺序执行):
cmd1 && cmd2 - 并行 (同时执行):
cmd1 & cmd2(但后台进程控制复杂,不推荐)
8.2 使用 npm-run-all 模块
npm-run-all 提供了高级流程控制。
安装:
bash
npm install --save-dev npm-run-all
串行执行(一个接着一个):
json
{
"scripts": {
"build": "npm-run-all --serial clean build:css build:js",
"clean": "rimraf dist",
"build:css": "sass src/style.scss dist/style.css",
"build:js": "webpack"
}
}
或者简写(默认串行):
json
"build": "npm-run-all clean build:css build:js"
并行执行(同时运行):
json
{
"scripts": {
"watch": "npm-run-all --parallel watch:css watch:js",
"watch:css": "sass --watch src:dist",
"watch:js": "webpack --watch"
}
}
混合执行:
json
{
"scripts": {
"all": "npm-run-all clean --parallel lint test --serial build"
}
}
传入通配符:
json
"build": "npm-run-all build:*"
会匹配所有以 build: 开头的脚本。
停止策略:
--continue-on-error:某个任务失败后继续其他任务。--fail-fast:默认,一个失败立即终止。
8.3 使用 concurrently 模块
专注于并行,输出更清晰。
bash
npm install --save-dev concurrently
json
{
"scripts": {
"watch": "concurrently --kill-others \"npm run watch:css\" \"npm run watch:js\"",
"watch:css": "sass --watch src:dist",
"watch:js": "webpack --watch"
}
}
特色功能:--kill-others(一个退出则杀死所有),--raw(原始输出),--success(指定成功条件)。
8.4 使用 wait-on 实现条件等待
当需要等待某个服务启动后再执行后续任务时:
bash
npm install --save-dev wait-on
json
{
"scripts": {
"serve": "node server.js",
"cypress": "wait-on http://localhost:3000 && cypress run",
"test:e2e": "npm-run-all --parallel --race serve cypress"
}
}
8.5 后台进程管理
长期运行的后台服务(如 API mock),建议使用 pm2 或 nodemon 配合 npm-run-all 的 --race 选项(第一个完成即终止其他)。
9. 使用 npm 包提供的 CLI 工具
9.1 本地 CLI 自动加入 PATH
当你安装一个包含可执行文件的包(如 webpack、jest),npm 会将其放入 node_modules/.bin。运行 npm run 时,这个目录会被临时添加到 PATH,因此脚本中可以直接写命令名,无需写完整路径。
json
{
"scripts": {
"build": "webpack --mode production" // 无需 ./node_modules/.bin/webpack
}
}
9.2 使用 npx 临时调用工具
即使未在 package.json 中安装,也可用 npx 运行远程或本地包的命令。
bash
npx create-react-app my-app # 下载并运行 create-react-app
npx eslint --init # 使用本地 eslint(如果存在),否则临时下载
npx http-server . -p 8080 # 临时启动一个静态服务器
在脚本中可以使用 npx 来保证使用最新版本,但通常推荐显式安装到 devDependencies 以锁定版本。
9.3 传递参数给 CLI 工具
很多 CLI 工具支持 -- 传递额外参数,npm 脚本中注意转义。
json
{
"scripts": {
"test": "jest --coverage"
}
}
运行时覆盖:
bash
npm run test -- --watch
# 最终命令:jest --coverage --watch
9.4 调用其他 Node.js 模块的 bin
如果某个模块没有导出 CLI 但你想使用,可以通过 node -e 执行代码。
json
{
"scripts": {
"list-modules": "node -e \"console.log(Object.keys(require('./package.json').dependencies))\""
}
}
10. 常用的实用脚本示例
10.1 开发环境
json
{
"scripts": {
"dev": "vite", // Vite 开发服务器
"start": "node server.js",
"watch": "nodemon --exec 'npm run build && node dist/index.js'",
"serve": "npm run build && npx serve dist"
}
}
10.2 构建与打包
json
{
"scripts": {
"clean": "rimraf dist coverage .nyc_output",
"prebuild": "npm run clean && npm run lint",
"build": "webpack --mode=production",
"build:dev": "webpack --mode=development --watch",
"postbuild": "echo 'Build completed at '$(date)"
}
}
10.3 测试
json
{
"scripts": {
"test": "jest",
"test:watch": "jest --watch",
"test:coverage": "jest --coverage",
"test:e2e": "cypress run",
"test:e2e:open": "cypress open",
"pretest": "npm run lint"
}
}
10.4 代码质量
json
{
"scripts": {
"lint": "eslint src --ext .js,.jsx,.ts,.tsx",
"lint:fix": "eslint --fix src",
"format": "prettier --write 'src/**/*'",
"type-check": "tsc --noEmit"
}
}
10.5 Git Hooks(结合 husky)
json
{
"scripts": {
"prepare": "husky install",
"precommit": "lint-staged"
},
"lint-staged": {
"*.js": ["eslint --fix", "git add"]
}
}
10.6 数据库操作
json
{
"scripts": {
"db:migrate": "node-pg-migrate up",
"db:seed": "node scripts/seed.js",
"db:reset": "npm run db:migrate reset && npm run db:seed"
}
}
10.7 部署与发布
json
{
"scripts": {
"prepublishOnly": "npm run test && npm run build",
"version": "npm run build && git add -A dist",
"postversion": "git push && git push --tags && npm publish"
}
}
10.8 监控与分析
json
{
"scripts": {
"stats": "webpack --profile --json > stats.json && webpack-bundle-analyzer stats.json",
"size": "npm run build && npx bundlesize"
}
}
11. 脚本调试与日志
11.1 查看脚本执行的命令
使用 npm run 默认会将脚本内容输出:
markdown
> my-app@1.0.0 build
> webpack --mode production
11.2 静默模式
隐藏 npm 前缀输出,只显示脚本本身的输出:
bash
npm run build --silent
# 或 npm run build -s
11.3 查看脚本执行细节
使用 --loglevel 或 -d 输出调试信息:
bash
npm run build --loglevel=verbose # 或 -d
npm run build --loglevel=silly # 最详细
11.4 在脚本中添加调试输出
在脚本命令中插入 echo 或 console.log 以便追踪。
json
{
"scripts": {
"debug": "echo 'Starting build...' && npm run build && echo 'Build success'"
}
}
11.5 错误处理
脚本默认遵循 shell 的规则:如果非零退出码,npm 会停止并报错。可以使用 || 来忽略错误或执行备用命令。
json
{
"scripts": {
"risky": "some-command-that-may-fail || echo 'Command failed but continuing'"
}
}
若要让整个脚本即使某一步失败也继续,可设置 set -e 的反义:
json
"myscript": "set +e; command1; command2; set -e"
11.6 使用 --if-present 避免不存在的脚本报错
bash
npm run mystyle --if-present
# 如果 mystyle 不存在,不会报错,正常退出
12. 钩子脚本的深度应用
12.1 自定义安装钩子
在项目安装后自动执行:
json
{
"scripts": {
"postinstall": "node scripts/postinstall.js"
}
}
postinstall.js 可以打印欢迎信息、提示配置等。
12.2 版本管理钩子
自动更新版本后构建并推送:
json
{
"scripts": {
"preversion": "npm test",
"version": "npm run build && git add -A dist",
"postversion": "git push && git push --tags"
}
}
运行 npm version patch 将:
- 运行
npm test - 更新
package.json版本 - 运行
npm run build并提交dist目录 - 创建 git tag
- 运行
git push推送 commit 和 tag
12.3 发布前检查
json
{
"scripts": {
"prepublishOnly": "npm run lint && npm test && npm run build"
}
}
确保发布到 npm 前所有检查通过。
12.4 利用 npm_lifecycle_event 在单个脚本中多行为
如果不想写多个钩子,可以写一个脚本判断当前事件名:
json
{
"scripts": {
"build": "node scripts/build-handler.js"
}
}
build-handler.js:
javascript
const event = process.env.npm_lifecycle_event;
if (event === 'prebuild') {
console.log('Running prebuild tasks');
} else if (event === 'build') {
console.log('Running build');
}
13. 工作区(Workspaces)中的脚本
npm 7+ 的工作区允许在 monorepo 中统一管理脚本。
13.1 在根目录运行工作区脚本
假设项目结构:
go
my-monorepo/
package.json (根)
packages/
web/
package.json
api/
package.json
根 package.json 定义工作区:
json
{
"workspaces": ["packages/*"]
}
在所有包中运行脚本:
bash
npm run test --workspaces
在特定包中运行:
bash
npm run build --workspace=packages/web
传递参数给工作区脚本:
bash
npm run test --workspaces -- --coverage
13.2 在根目录定义脚本同时影响所有包
根脚本可以通过 npm run 配合 --workspaces 来调用子包脚本。
json
{
"scripts": {
"test": "npm run test --workspaces",
"lint": "npm run lint --workspaces --if-present"
}
}
13.3 工作区的生命周期钩子
postinstall 在根目录运行后,也会在每个工作区中运行(如果它们有自己的 postinstall)。
14. 脚本安全注意事项
14.1 避免使用不受信任的脚本
在 preinstall 或 install 钩子中,npm 会执行任意代码。安装第三方包时,检查其 package.json 的 scripts。
14.2 不要将敏感信息写入脚本
环境变量如 NPM_TOKEN 应通过 CI 的 secrets 注入,不要硬编码在脚本中。
14.3 使用 --ignore-scripts 跳过安装钩子
如果你不信任某个包的安装脚本,可以临时忽略:
bash
npm install --ignore-scripts
14.4 对全局脚本的影响
全局安装的包如果包含 preinstall 等钩子,它们会在系统级别执行,具有更高权限。注意来源。
15. 最佳实践与模式
15.1 命名规范
- 使用 冒号 为子命令分组:
build:css,build:js,test:unit,test:e2e - 使用 短横线 连接单词:
pre-commit或precommit。 - 统一风格,团队保持一致。
15.2 引用本地二进制文件
即使 node_modules/.bin 已被加入 PATH,但有时 IDE 可能不识别,可显式使用 npx 或 $(npm bin)(旧方式)。推荐直接写命令名。
15.3 复杂脚本提取到独立文件
超过 3 个链式命令或涉及条件判断,建议写成独立的 .js 或 .sh 文件。
15.4 使用 npm run env 查看可用环境变量
bash
npm run env | grep npm_package
15.5 脚本中进行参数校验
在 Node 脚本中,使用 yargs 或 commander 来处理复杂参数。
15.6 为脚本添加文档
在 README.md 中列出常用脚本及其作用。
15.7 脚本的缓存与性能
避免在脚本中重复执行耗时操作,可以使用 npm ci 代替 npm install,使用缓存目录等。
15.8 使用 nodemon 热加载
开发时监控文件变化重新运行脚本:
json
{
"scripts": {
"dev": "nodemon --exec 'npm run build && node dist/index.js'"
}
}
附录:完整示例工程
json
{
"name": "awesome-project",
"version": "1.0.0",
"config": {
"port": 3000
},
"scripts": {
"preinstall": "node scripts/check-node-version.js",
"postinstall": "husky install",
"prebuild": "rimraf dist",
"build": "run-p build:*",
"build:css": "sass src:dist/css --style compressed",
"build:js": "webpack --mode=production",
"postbuild": "echo 'Build size:' && du -sh dist",
"dev": "cross-env NODE_ENV=development run-p start watch:*",
"start": "node server.js",
"watch:css": "sass --watch src:dist/css",
"watch:js": "webpack --watch --mode=development",
"lint": "eslint src",
"lint:fix": "npm run lint -- --fix",
"test": "jest",
"test:coverage": "jest --coverage",
"test:e2e": "start-server-and-test start http://localhost:$npm_package_config_port cypress-run",
"cypress-run": "cypress run",
"preversion": "npm run test",
"version": "npm run build && git add -A dist",
"postversion": "git push && git push --tags && npm publish",
"prepublishOnly": "npm run test && npm run build",
"clean": "rimraf dist coverage .nyc_output node_modules/.cache"
},
"devDependencies": {
"cross-env": "^7.0.3",
"cypress": "^13.0.0",
"eslint": "^8.0.0",
"husky": "^8.0.0",
"jest": "^29.0.0",
"npm-run-all": "^4.1.5",
"rimraf": "^5.0.0",
"sass": "^1.69.0",
"start-server-and-test": "^2.0.0",
"webpack": "^5.88.0",
"webpack-cli": "^5.1.0"
}
}
总结
npm 脚本是现代 JavaScript 项目自动化的基石。通过掌握脚本定义、钩子、参数传递、环境变量、跨平台兼容性和流程控制,你可以将复杂的开发流程封装成简洁的 npm run 命令,提高团队协作效率和项目可维护性。
核心要点:
- 使用
pre/post钩子自动化前置/后置任务。 - 用
cross-env、rimraf等实现跨平台。 - 用
npm-run-all或concurrently处理并行/串行。 - 善用
node_modules/.bin的本地 CLI。 - 将复杂逻辑抽离到独立脚本文件。
- 保持脚本的幂等性和可调试性。
不断实践,你会发现 npm 脚本足以满足绝大多数自动化需求。🚀