一、 概述
1.1 什么是 npm script
创建
npm项目, 在初始化项目时, 会自动生成一个项目描述文件package.json, 该文件scripts字段中允许用户自定义一系列脚本, 用户可以将一些项目中常用的命令记录在package.json中, 并通过简短的命令进行调用。
- 如下
package.json在scripts中定义了脚本start当运行脚本时将在终端中输出hello world!
....
{
"scripts": {
"start": "echo \"hello world!\""
}
}
....
- 假设项目中, 需要经常调用以下命令来统计项目中的代码行数
find src -name "*.js" | xargs cat | wc -l
- 为了减少繁琐操作我们就可以将上面命令添加到
npm script中, 之后只要执行npm run lines命令来调用脚本即可
{
....
"scripts": {
"lines": "find src -name \"*.js\" | xargs cat | wc -l"
},
....
}
1.2 npm script 优点
- 减少繁琐操作, 工程化项目, 彻底解放双手
- 在项目描述文件中, 集中管理项目脚本
- 简单、灵活、自由度高
- 社区活跃
二、 初始化 npm 项目
2.1 初始化
要初始化一个 npm 项目, 只需要执行
npm init命令, 执行该命令后需要填写些基本信息(项目名称、版本号、作者信息、入口文件、仓库地址、许可协议等), 当然你也可以一路回车保持默认值, 最后将会列出完整的package.json信息, 输入yes即可。
$ mkdir script && cd script
$ npm init
# 下列是 npm init 命令的交互信息
This utility will walk you through creating a package.json file.
It only covers the most common items, and tries to guess sensible defaults.
See `npm help init` for definitive documentation on these fields
and exactly what they do.
Use `npm install <pkg>` afterwards to install a package and
save it as a dependency in the package.json file.
Press ^C at any time to quit.
package name: (script)
version: (1.0.0)
description:
entry point: (index.js)
test command:
git repository:
keywords:
author:
license: (ISC)
About to write to /Users/qianyin/coding/tmp/script/package.json:
{
"name": "script",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC"
}
Is this OK? (yes) yes
- 如果如果你觉得
npm init交互过于麻烦, 你只想快速初始化一个项目(项目信息使用默认值), 可以使用-f或-y参数, 从而跳过交互所有需要填写的信息保持默认值
$ npm init -y
# 或: npm init -f
# 下面是执行 npm init -y 后的日志输出
Wrote to /Users/qianyin/coding/tmp/script/package.json:
{
"name": "script",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC"
}
2.2 如何设置默认配置
上文提到
npm init -y命令会初始化一个项目, 该命令会跳过交互并且package.json中基本信息将使用默认信息, 那么这默认信息又该怎么配置呢?
- 设置默认配置
npm config set init.author.email "qianyin95@gmail.com" # 设置作者邮箱
npm config set init.author.name "qianyin" # 设置作者名称
npm config set init.author.url "https://github.com/qianyin925" # 设置作者地址
npm config set init.license "MIT" # 设置许可协议
npm config set init.version "0.0.1" # 设置版本号
- 再次使用
npm init -y初始项目, 作者信息、协议、版本号等信息默认值和上面设置的值保持一致
$ npm init -y
# 下面是 package.json 信息
{
"name": "script",
"version": "0.0.1",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "qianyin <qianyin95@gmail.com> (https://github.com/qianyin925)",
"license": "MIT"
}
- 在终端执行
npm config --help查看更多操作命令: 删除、查看、修改、编辑配置
$ npm config --help
# 下面是执行命令后输出的信息
npm config set <key> <value>
npm config get [<key>]
npm config delete <key>
npm config list [--json]
npm config edit
npm set <key> <value>
npm get [<key>]
三、 演示: 创建并运行 npm script
3.1 如何运行脚本
语法: npm run <脚本名称>
- 如下定义一个脚本
lines
"scripts": {
"lines": "find src -name \"*.js\" | xargs cat | wc -l"
},
- 执行脚本
lines
$ npm run lines
# 下列是命令输出内容
> script@0.0.1 lines /Users/qianyin/coding/tmp/script
> find src -name "*.js" | xargs cat | wc -l
4
3.2 如何查看当前项目所有 npm script 信息
直接执行
npm run即可查看当前项目所有npm script的信息
- 假设
package.json脚本信息如下:
"scripts": {
"lines": "find src -name \"*.js\" | xargs cat | wc -l",
"start": "echo hello world!"
},
- 执行
npm run有
$ npm run
# 下面是执行命令后输出信息
Lifecycle scripts included in script:
start
echo hello world!
available via `npm run-script`:
lines
find src -name "*.js" | xargs cat | wc -l
- 补充:
npm run命令实际上是npm run-script的简写
3.3 自定义 npm script
演示: 添加一个
eslint校验脚本对本地代码风格进行校验
- 准备待校验代码: 创建
src/index.js文件并编写测试代码
const data = {}
const fun = () => {
console.log('hello world!');
}
- 添加依赖
npm install eslint -D
- 初始化
eslint配置
$ npx eslint --init
# 下列是命令交互信息, 所有选项全部保存默认值
✔ How would you like to use ESLint? · problems
✔ What type of modules does your project use? · esm
✔ Which framework does your project use? · react
✔ Does your project use TypeScript? · No / Yes
✔ Where does your code run? · browser✔ What format do you want your config file to be in? · JavaScript
The config that you've selected requires the following dependencies:
eslint-plugin-react@latest
✔ Would you like to install them now with npm? · No / Yes
- 添加
npm script
"scripts": {
"eslint": "eslint src/**/*.js"
},
- 执行脚本
npm run eslint
$ npm run eslint
# 下列是命令输出信息
> script@0.0.1 eslint /Users/qianyin/coding/tmp/script
> eslint src/**/*.js
Warning: React version not specified in eslint-plugin-react settings. See https://github.com/yannickcr/eslint-plugin-react#configuration .
/Users/qianyin/coding/tmp/script/src/index.js
1:7 error 'data' is assigned a value but never used no-unused-vars
3:7 error 'fun' is assigned a value but never used no-unused-vars
✖ 2 problems (2 errors, 0 warnings)
3.4 脚本执行流程(运行原理)
执行一个
npm script大致可以分为下列几个流程
- 新建一个
Shell, 从package.json文件中读取scripts对象里面的全部配置 - 以传给
npm run的第一个参数作为键, 在scripts对象里面获取对应的值作为接下来要执行的命令, 如果没找到直接报错 - 将
node_modules/.bin添加到环境变量PATH中, 这意味着.bin内任何可执行文件都能在npm script中直接调用, 不需要加上可执行文件的完整路径, 如./node_modules/.bin/eslint **.js - 在系统默认的
shell中执行上述命令, 系统默认shell通常是bash,windows环境下可能略有不同 - 命令结束后恢复默认环境变量
PATH
- 补充说明: 由于
npm script是在shell中执行
- 所以理论上只要能在
shell中执行的命令都能作为npm script - 所以它可以使用
shell通配符,*表示任意文件名,**表示任意一层子目录 - 所以也遵守
shell脚本规则, 退出码只要不是0,npm就认为这个脚本执行失败
3.5 简写命令
为了方便下面几个命名都支持简写形式
npm run start可简写为npm startnpm run stop可简写为npm stopnpm run test可简写为npm testnpm run stop && npm run restart && npm run start可简写为npm restart
3.6 默认命令
一般来说,
npm脚本由用户提供。但是npm对两个脚本提供了默认值。也就是说, 这两个脚本不用定义, 就可以直接使用。
{
"start": "node server.js",
"install": "node-gyp rebuild"
}
- 当
scripts未定义脚本start, 并且项目下有server.js时, 若执行npm start实际上将执行命令node server.js
// package.json
"scripts": {
"eslint": "eslint src/**/*.js"
},
// server.js
#!/usr/bin/env node
console.log('Hello world!');
$ npm start
# 下面是命令输出日志
> script@0.0.1 start /Users/qianyin/coding/tmp/script
> node server.js
Hello world!
- 当
scripts未定义脚本install, 并且项目下有binding.gyp时, 若执行npm run install实际上将执行命令node-gyp rebuild, 这里需要注意npm install并不是npm run install的简写形式, 它们是两个不同的命令。
四、 如何运行多个 npm script
4.1 测试脚本编写
// node 脚本: script_1.js
#!/usr/bin/env node
setTimeout(() => { console.log('1 秒') }, 1000);
// node 脚本: script_2.js
#!/usr/bin/env node
setTimeout(() => { console.log('2 秒') }, 2000);
// node 脚本: script_3.js
#!/usr/bin/env node
setTimeout(() => { console.log('3 秒') }, 3000);
4.2 串行: &&
顺序执行多条命令, 当碰到执行出错的命令后将不执行后面的命令
// package.json
"scripts": {
"print": "node ./src/script_3.js && node ./src/script_2.js && node ./src/script_1.js"
}
$ npm run print
# 下面是命令输出日志
3 秒 # 执行命令 3 秒后输出
2 秒 # 执行命令 5 秒后输出
1 秒 # 执行命令 6 秒后输出
4.3 并行: &
并行执行多条命令, 在命名最后跟上
wait可阻塞当前进程, 直到所有并行命令执行完毕才会结束进程
// package.json
"scripts": {
"print": "node ./src/script_3.js & node ./src/script_2.js & node ./src/script_1.js & wait"
}
$ npm run print
# 下面是命令输出日志
1 秒 # 执行命令 1 秒后输出
2 秒 # 执行命令 2 秒后输出
3 秒 # 执行命令 3 秒后输出
4.4 或: ||
顺序执行多条命令, 当命令被正确执行那么后面的命令将不会被执行
// package.json
"scripts": {
"print": "node ./src/script_3.js || node ./src/script_2.js || node ./src/script_1.js"
}
$ npm run print
# 下面是命令输出日志
3 秒 # 执行命令 3 秒后输出, 命令正确执行则结束进程
- 将
src/script_3.js进行修改, 并重新执行脚本
// node 脚本: script_3.js
#!/usr/bin/env node
setTimeout(() => {
process.exit(1); // 强制退出
console.log('3 秒');
}, 3000);
$ npm run print
# 下面是命令输出日志
2 秒 # 执行命令 5 秒后输出
4.5 多脚本管理插件推荐
这里推荐一款多脚本管理工具 npm-run-all, 该命令主要特点:
- 大大简化脚本命令
- 跨平台
// package.json
"scripts": {
"n_1": "node ./scripts/1.js",
"n_2": "node ./scripts/2.js",
"n_3": "node ./scripts/3.js",
"test_1": "npm-run-all -p n_1 n_2 n_3", // 并行, 也可使用简短命令: run-p: n_1 n_2 n_3
"test_2": "npm-run-all -s n_1 n_2 n_3", // 串行, 也可使用简短命令: run-s: n_1 n_2 n_3
}
- 补充: 其他多任务管理工具:
script-runner、redrun
五、 日志输级别
执行 npm 的过程中会产生一些日志输出, 例如执行 npm init -y 会将生成的 package.json 文件中的内容输出到终端中, 在 npm 命令中我们可通过 --loglevel 来设置日志输出级别:
--loglevel配置规则如下
- 默认:
notice - 类型:
String - 值:
silent,error,warn,notice,http,timing,info,verbose,silly
- 简写模式
-s--silent等价于--loglevel silent-q--quiet等价于--loglevel warn-d等价于--loglevel info-dd--verbose等价于--loglevel verbose-ddd等价于--loglevel silly
更内容参考: docs.npmjs.com/cli/v6/usin…
5.1 默认日志输出级别: notice
默认日志输出级别: 直接执行
npm脚本, 不设置任何日志参数时输出的日志, 也是我们平常见到最多的
$ npm i react
# 下面是命令输出日志
up to date, audited 4 packages in 2s
found 0 vulnerabilities
5.2 简洁模式: silent
简洁模式: 该日志级别只会保留脚本本身输出的日志, 日志相对来说较为简洁
参数:--loglevel silent||--silent||-s
$ npm i react -s # 或 npm i react --loglevel silent
# 没输出任何日志
5.3 详细模式: verbose
详细模式: 该日志级别将会详细打印出执行脚本过程中每个步骤的详细信息
参数:--loglevel verbose||--verbose||-dd
$ npm i react -dd # 或 npm i react --loglevel verbose
# 输出好多好多日志
# ....
5.4 修改默认日志级别
已知
npm默认loglevel选项值为notice, 可通过npm config对该值进行修改:
- 修改默认值
npm config set loglevel silent
# 删除 loglevel 配置: npm config delete loglevel
- 重新执行
npm i react会发现执行完命令, 将不会输出任何日志
六、 脚本传参
本节演示脚本代码如下:
// package.json
"scripts": {
"print": "node ./src/script"
}
#!/usr/bin/env node
// src/script.js
// 在 node 脚本中可通过 process.argv 数组获取执行脚本时传入的参数
// process.argv 第一第二个元素分别是 node 路径、脚本路径, 从第三个参数开始为脚本输入的参数
console.log('脚本参数有:', process.argv.splice(2));
6.1 向脚本简单传递参数
格式: npm run <脚本> <参数列表>
- 多个参数使用空格隔开
- 参数带有空格可用
""进行包裹- 部分文章有提到参数和脚本间需要使用
--隔开, 但经过测试发现--可加可不加
# 多个参数使用空格隔开
$ npm run print 123 456
## 下面是脚本输出内容
> script@0.0.1 print
> node ./src/script "123" "456"
脚本参数有: [ '123', '456' ]
# 参数带有空格可用 "" 进行包裹
$ npm run print "hello world"
## 下面是脚本输出内容
> script@0.0.1 print
> node ./src/script "hello world"
脚本参数有: [ 'hello world' ]
6.2 使用 commander 解析参数
commander 模块主用功能还是用于解析
node脚本参数, 扩展我们的脚本, 这里不对该模块展开说明, 只简单列出该模块的几个功能
- 可自定义选项: 必填选项、可选项
- 可生成脚本版本号
- 自动化帮助信息
- 自定义事件监听 ...
七、 环境变量
7.1 预定义变量
预定义变量简单来说就是在执行脚本中预设的环境变量
- 查看预定义变量
$ npm run env
# 作者信息...
npm_package_author_email=wangshijun2010@gmail.com
npm_package_author_name=wangshijun
npm_package_author_url=http://github.com/wangshijun
# 依赖信息...
npm_package_devDependencies_markdownlint_cli=^0.5.0
npm_package_devDependencies_mocha=^4.0.1
npm_package_devDependencies_npm_run_all=^4.1.2
# 各种 npm script
npm_package_scripts_lint=npm-run-all --parallel lint:*
npm_package_scripts_lint_css=stylelint *.less
npm_package_scripts_lint_js=eslint *.js
npm_package_scripts_lint_js_fix=npm run lint:js -- --fix
npm_package_scripts_lint_json=jsonlint --quiet *.json
# 基本信息
npm_package_version=0.1.0
npm_package_gitHead=3796e548cfe406ec33ab837ac00bcbd6ee8a38a0
npm_package_license=MIT
npm_package_main=index.js
npm_package_name=hello-npm-script
npm_package_readmeFilename=README.md
# 依赖的配置
npm_package_nyc_exclude_0=**/*.spec.js
npm_package_nyc_exclude_1=.*.js
- 如需要在 bash 中使用环境变量, 只需要遵守 shell 语法即可
// package.json 执行下面脚本将输出当前项目的名称
"scripts": {
"print": "echo $npm_package_name"
},
- 几个比较常用的变量
- 项目名称:
npm_package_name等于package.json中name字段 - 项目版本号:
npm_package_version等于package.json中version字段 - 所在
npm项目路径:PWD - 当前运行脚本的名称:
npm_lifecycle_event
7.2 自定义变量
7.2.1 在 npm script 中设置环境变量
在不同环境下设置环境变量的方式有所不同, 所以如果需要还是推荐使用
cross-env工具来设置环境变量
- 安装依赖
$ npm i cross-env -D
- 创建
npm script
// package.json
"scripts": {
"print": "cross-env NODE_ENV=development node ./src/script.js"
},
- 创建
node脚本文件src/script.js
#!/usr/bin/env node
// 所有环境变量都存在 process.env 该对象中
console.log(`环境变量 NODE_ENV: ${process.env.NODE_ENV}`);
- 运行脚本
$ npm run print
# 下面是脚本日志输出
> script@0.0.1 print
> cross-env NODE_ENV=development node ./src/script.js
环境变量 NODE_ENV: development
7.2.2 在 node 脚本中进行环境变量的赋值
已知环境变量都是存在
process.env对象中, 那么如果需要直接在脚本中往process.env中添加值即可
- 创建
npm script
// package.json
"scripts": {
"print": "node ./src/script.js"
},
- 创建
node脚本文件src/script.js
#!/usr/bin/env node
process.env.NODE_ENV = 'development';
console.log(`环境变量 NODE_ENV: ${process.env.NODE_ENV}`);
- 运行脚本
$ npm run print
# 下面是脚本日志输出
> script@0.0.1 print
> node ./src/script.js
环境变量 NODE_ENV: development
八、 钩子
钩子的简单理解: 在执行某个操作过程中定义了若干个挂载点(钩子), 在需要的时候可以往钩子上自定义内容(函数、脚本等), 比如在执行脚本前如果存在钩子, 那么我们就可以在钩子上定义脚本, 每次执行脚本前都将自动执行钩子上定义的脚本, 同理也可以在执行脚本后定义钩子, 钩子的定义在我看来有点类似于插件或者回调函数的意思。
8.1 npm script 钩子
每个
npm script有pre和post两个钩子,pre钩子在脚本执行前将被触发,post则是在脚本执行后触发, 下面介绍如何为任意脚本(这里以pre钩子、post钩子
- 创建
npm script
// package.json
"scripts": {
"print": "echo 'hello world!'"
},
- 为
print脚本创建pre钩子, 其实就是创建preprint钩子
// package.json
"scripts": {
"print": "echo 'hello world!'",
+ "preprint": "echo '我是 print pre 钩子, 将在执行 print 前被执行'"
},
- 为
print脚本创建post钩子, 其实就是创建postprint钩子
// package.json
"scripts": {
"print": "echo 'hello world!'",
"preprint": "echo '我是 print pre 钩子, 将在执行 print 前被执行'",
+ "postprint": "echo '我是 print post 钩子, 将在执行 print 后被执行'"
},
- 执行脚本
$ npm run print
# 下面是脚本输出日志
> script@0.0.1 preprint
> echo '我是 print pre 钩子, 将在执行 print 前被执行'
我是 print pre 钩子, 将在执行 print 前被执行
> script@0.0.1 print
> echo 'hello world!'
hello world!
> script@0.0.1 postprint
> echo '我是 print post 钩子, 将在执行 print 后被执行'
我是 print post 钩子, 将在执行 print 后被执行
- 执行
npm run print需要经过的流程有:
- 注意: 双重的
pre和post无效, 比如在这儿prepreprint和postpostprint是无效的
8.2 默认脚本钩子
npm script允许我们为默认脚本命令(如:npm start)提供prepost钩子
test脚本prepost钩子:pretestposttestinstall脚本prepost钩子:preinstallpostinstalluninstall脚本prepost钩子:preuninstallpostuninstallstart脚本prepost钩子:prestartpoststartrestart脚本prepost钩子:prerestartpostrestartstop脚本prepost钩子:prestoppoststop
8.3 npm_lifecycle_event 变量
npm提供一个npm_lifecycle_event变量, 返回当前正在运行的脚本名称, 比如pretesttestposttest等等。所以, 可以利用这个变量, 在同一个脚本文件里面, 为不同的npm scripts命令编写代码
const TARGET = process.env.npm_lifecycle_event;
if (TARGET === 'test') {
console.log(`Running the test task!`);
}
if (TARGET === 'pretest') {
console.log(`Running the pretest task!`);
}
if (TARGET === 'posttest') {
console.log(`Running the posttest task!`);
}
九、 脚本拆分: scripty 工具使用
scripty 可帮助我们将
npm script提取到独立的文件中, 在此我们无需修改执行命令, 只需要将脚本进行拆分即可
9.1 scripty 安装和使用
- 安装依赖
npm i scripty -D
- 创建脚本
执行下列脚本将匹配到 scripts/print.* 或 scripts/print/index.* 所有可执行脚本, 并执行它们
"scripts": {
"print": "scripty"
},
- 创建脚本
scripts/print.jsscripts/print.dev.js
#!/usr/bin/env node
// scripts/print.js
console.log('helo world!');
#!/usr/bin/env node
// scripts/print.dev.js
console.log('dev: helo world!');
- 执行脚本
$ chmod -R +x ./script # 为脚本添加可执行权限
$ npm run print
# 下面是脚本输入日志
> script@0.0.1 print
> scripty
dev: helo world!
helo world!
- 补充说明:
scripty允许通用在脚本名中使用:来对脚本进行分组, 如: 执行"print:dev": "scripty"将匹配scripts/print/dev.*或scripts/print/dev/index.*所有可执行脚本, 并执行它们- 这边可执行脚本可以是
node脚本、也可以是shell脚本、 甚至可以是bat脚本
9.2 传参、参数调用
使用
scripty允许对脚本进行传参, 传参方式不变, 如:npm run print dev, 参数为dev
- 在 node 脚本中使用
process.argv[2]获取参数
#!/usr/bin/env node
console.log('helo world!', process.argv[2]);
- 在
shell脚本中使用$1获取参数
#!/bin/bash
echo $1
十、 插件工具推荐
rimraf或del-cli删除文件、目录(跨平台)cpr用于拷贝、复制文件目录(跨平台)make-dir-cli用于创建目录(跨平台)cross-env设置环境变量(跨平台)
十一、参考
大家好, 我是墨渊君, 如果您喜欢我的文章可以:
- 关注公众号: 「昆仑虚F2E」获取最新文章。
- GitHub: github.com/MoYuanJun
- 个人网站(昆仑虚, 虽然现在没啥东西): www.kunlunxu.cc