npm scripts 使用指南详解


Posted in Javascript onOctober 08, 2018

Node 开发离不开 npm,而脚本功能是 npm 最强大、最常用的功能之一。

本文介绍如何使用 npm 脚本(npm scripts)。

一、什么是 npm 脚本?

npm 允许在package.json文件里面,使用scripts字段定义脚本命令。

{
 // ...
 "scripts": {
  "build": "node build.js"
 }
}

上面代码是package.json文件的一个片段,里面的scripts字段是一个对象。它的每一个属性,对应一段脚本。比如,build命令对应的脚本是node build.js。

命令行下使用npm run命令,就可以执行这段脚本。

$ npm run build
# 等同于执行
$ node build.js

这些定义在package.json里面的脚本,就称为 npm 脚本。它的优点很多。

  • 项目的相关脚本,可以集中在一个地方。
  • 不同项目的脚本命令,只要功能相同,就可以有同样的对外接口。用户不需要知道怎么测试你的项目,只要运行npm run test即可。
  • 可以利用 npm 提供的很多辅助功能。

查看当前项目的所有 npm 脚本命令,可以使用不带任何参数的npm run命令。

$ npm run

二、原理

npm 脚本的原理非常简单。每当执行npm run,就会自动新建一个 Shell,在这个 Shell 里面执行指定的脚本命令。因此,只要是 Shell(一般是 Bash)可以运行的命令,就可以写在 npm 脚本里面。

比较特别的是,npm run新建的这个 Shell,会将当前目录的node_modules/.bin子目录加入PATH变量,执行结束后,再将PATH变量恢复原样。

这意味着,当前目录的node_modules/.bin子目录里面的所有脚本,都可以直接用脚本名调用,而不必加上路径。比如,当前项目的依赖里面有 Mocha,只要直接写mocha test就可以了。

"test": "mocha test"

而不用写成下面这样。

"test": "./node_modules/.bin/mocha test"

由于 npm 脚本的唯一要求就是可以在 Shell 执行,因此它不一定是 Node 脚本,任何可执行文件都可以写在里面。

npm 脚本的退出码,也遵守 Shell 脚本规则。如果退出码不是0,npm 就认为这个脚本执行失败。

三、通配符

由于 npm 脚本就是 Shell 脚本,因为可以使用 Shell 通配符。

"lint": "jshint *.js"
"lint": "jshint **/*.js"

上面代码中,*表示任意文件名,**表示任意一层子目录。

如果要将通配符传入原始命令,防止被 Shell 转义,要将星号转义。

"test": "tap test/\*.js"

四、传参

向 npm 脚本传入参数,要使用--标明。

"lint": "jshint **.js"

向上面的npm run lint命令传入参数,必须写成下面这样。

$ npm run lint -- --reporter checkstyle > checkstyle.xml

也可以在package.json里面再封装一个命令。

"lint": "jshint **.js",
"lint:checkstyle": "npm run lint -- --reporter checkstyle > checkstyle.xml"

五、执行顺序

如果 npm 脚本里面需要执行多个任务,那么需要明确它们的执行顺序。

如果是并行执行(即同时的平行执行),可以使用&符号。

$ npm run script1.js & npm run script2.js

如果是继发执行(即只有前一个任务成功,才执行下一个任务),可以使用&&符号。

 

$ npm run script1.js && npm run script2.js

这两个符号是 Bash 的功能。此外,还可以使用 node 的任务管理模块:script-runner、npm-run-all、redrun。

六、默认值

一般来说,npm 脚本由用户提供。但是,npm 对两个脚本提供了默认值。也就是说,这两个脚本不用定义,就可以直接使用。

"start": "node server.js",
"install": "node-gyp rebuild"

上面代码中,npm run start的默认值是node server.js,前提是项目根目录下有server.js这个脚本;npm run install的默认值是node-gyp rebuild,前提是项目根目录下有binding.gyp文件。

七、钩子

npm 脚本有pre和post两个钩子。举例来说,build脚本命令的钩子就是prebuild和postbuild。

"prebuild": "echo I run before the build script",
"build": "cross-env NODE_ENV=production webpack",
"postbuild": "echo I run after the build script"

用户执行npm run build的时候,会自动按照下面的顺序执行。

npm run prebuild && npm run build && npm run postbuild

因此,可以在这两个钩子里面,完成一些准备工作和清理工作。下面是一个例子。

"clean": "rimraf ./dist && mkdir dist",
"prebuild": "npm run clean",
"build": "cross-env NODE_ENV=production webpack"

npm 默认提供下面这些钩子。

  • prepublish,postpublish
  • preinstall,postinstall
  • preuninstall,postuninstall
  • preversion,postversion
  • pretest,posttest
  • prestop,poststop
  • prestart,poststart
  • prerestart,postrestart

自定义的脚本命令也可以加上pre和post钩子。比如,myscript这个脚本命令,也有premyscript和postmyscript钩子。不过,双重的pre和post无效,比如prepretest和postposttest是无效的。

npm 提供一个npm_lifecycle_event变量,返回当前正在运行的脚本名称,比如pretest、test、posttest等等。所以,可以利用这个变量,在同一个脚本文件里面,为不同的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!`);
}

注意,prepublish这个钩子不仅会在npm publish命令之前运行,还会在npm install(不带任何参数)命令之前运行。这种行为很容易让用户感到困惑,所以 npm 4 引入了一个新的钩子prepare,行为等同于prepublish,而从 npm 5 开始,prepublish将只在npm publish命令之前运行。

八、简写形式

四个常用的 npm 脚本有简写形式。

  • npm start是npm run start
  • npm stop是npm run stop的简写
  • npm test是npm run test的简写
  • npm restart是npm run stop && npm run restart && npm run start的简写

npm start、npm stop和npm restart都比较好理解,而npm restart是一个复合命令,实际上会执行三个脚本命令:stop、restart、start。具体的执行顺序如下。

  • prerestart
  • prestop
  • stop
  • poststop
  • restart
  • prestart
  • start
  • poststart
  • postrestart

九、变量

npm 脚本有一个非常强大的功能,就是可以使用 npm 的内部变量。

首先,通过npm_package_前缀,npm 脚本可以拿到package.json里面的字段。比如,下面是一个package.json。

{
 "name": "foo", 
 "version": "1.2.5",
 "scripts": {
  "view": "node view.js"
 }
}

那么,变量npm_package_name返回foo,变量npm_package_version返回1.2.5。

// view.js
console.log(process.env.npm_package_name); // foo
console.log(process.env.npm_package_version); // 1.2.5

上面代码中,我们通过环境变量process.env对象,拿到package.json的字段值。如果是 Bash 脚本,可以用$npm_package_name和$npm_package_version取到这两个值。

npm_package_前缀也支持嵌套的package.json字段。

"repository": {
  "type": "git",
  "url": "xxx"
 },
 scripts: {
  "view": "echo $npm_package_repository_type"
 }

上面代码中,repository字段的type属性,可以通过npm_package_repository_type取到。

下面是另外一个例子。

"scripts": {
 "install": "foo.js"
}

上面代码中,npm_package_scripts_install变量的值等于foo.js。

然后,npm 脚本还可以通过npm_config_前缀,拿到 npm 的配置变量,即npm config get xxx命令返回的值。比如,当前模块的发行标签,可以通过npm_config_tag取到。

"view": "echo $npm_config_tag",

注意,package.json里面的config对象,可以被环境变量覆盖。

{ 
 "name" : "foo",
 "config" : { "port" : "8080" },
 "scripts" : { "start" : "node server.js" }
}

上面代码中,npm_package_config_port变量返回的是8080。这个值可以用下面的方法覆盖。

$ npm config set foo:port 80

最后,env命令可以列出所有环境变量。

"env": "env"

十、常用脚本示例

// 删除目录
"clean": "rimraf dist/*",

// 本地搭建一个 HTTP 服务
"serve": "http-server -p 9090 dist/",

// 打开浏览器
"open:dev": "opener http://localhost:9090",

// 实时刷新
 "livereload": "live-reload --port 9091 dist/",

// 构建 HTML 文件
"build:html": "jade index.jade > dist/index.html",

// 只要 CSS 文件有变动,就重新执行构建
"watch:css": "watch 'npm run build:css' assets/styles/",

// 只要 HTML 文件有变动,就重新执行构建
"watch:html": "watch 'npm run build:html' assets/html",

// 部署到 Amazon S3
"deploy:prod": "s3-cli sync ./dist/ s3://example-com/prod-site/",

// 构建 favicon
"build:favicon": "node scripts/favicon.js",

十一、参考链接
How to Use npm as a Build Tool, by Keith Cirkel

Awesome npm scripts, by Ryan Zimmerman

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
file模式访问网页时iframe高度自适应解决方案
Jan 16 Javascript
分享一个我自己写的ToolTip提示插件(附源码)
Jan 20 Javascript
js将iframe中控件的值传到主页面控件中的实现方法
Mar 11 Javascript
javascript中日期转换成时间戳的小例子
Mar 21 Javascript
js数组方法扩展实现数组统计函数
Apr 09 Javascript
jquery向上向下取整适合分页查询
Sep 06 Javascript
js实现简单的验证码
Dec 25 Javascript
javascript实现dom元素可拖动
Mar 21 Javascript
layer弹出层 iframe层去掉滚动条的实例代码
Aug 17 Javascript
JQuery实现ajax请求的示例和注意事项
Dec 10 jQuery
ES6中Promise的使用方法实例总结
Feb 18 Javascript
element-ui中dialog弹窗关闭按钮失效的解决
Sep 22 Javascript
webpack4.0 入门实践教程
Oct 08 #Javascript
vue实现一个炫酷的日历组件
Oct 08 #Javascript
angularJs利用$scope处理升降序的方法
Oct 08 #Javascript
Nuxt升级2.0.0时出现的问题(小结)
Oct 08 #Javascript
vue页面切换过渡transition效果
Oct 08 #Javascript
angularJs自定义过滤器实现手机号信息隐藏的方法
Oct 08 #Javascript
angular中子控制器向父控制器传值的实例
Oct 08 #Javascript
You might like
对Session和Cookie的区分与解释
2007/03/16 PHP
php编写一个简单的路由类
2011/04/13 PHP
PHP得到某段时间区间的时间戳 php定时任务
2012/04/12 PHP
ThinkPHP采用实现三级循环代码实例
2014/07/18 PHP
PHP集成环境XAMPP的安装与配置
2018/11/13 PHP
JQuery中的ready函数冲突的解决方法
2010/05/17 Javascript
jQuery实现类似淘宝购物车全选状态示例
2013/06/26 Javascript
javascript框架设计读书笔记之种子模块
2014/12/02 Javascript
jQuery实现动态表单验证时文本框抖动效果完整实例
2015/08/21 Javascript
ionic环境配置及问题详解
2017/06/27 Javascript
vue超时计算的组件实例代码
2018/07/09 Javascript
说说如何利用 Node.js 代理解决跨域问题
2019/04/22 Javascript
使用nodejs实现JSON文件自动转Excel的工具(推荐)
2020/06/24 NodeJs
Vue实现todo应用的示例
2021/02/20 Vue.js
[59:30]完美世界DOTA2联赛PWL S3 access vs LBZS 第二场 12.20
2020/12/23 DOTA
使用python编写android截屏脚本双击运行即可
2014/07/21 Python
初步理解Python进程的信号通讯
2015/04/09 Python
python实现黑客字幕雨效果
2018/06/21 Python
Python数据持久化shelve模块用法分析
2018/06/29 Python
Python实现的读取/更改/写入xml文件操作示例
2018/08/30 Python
Django ModelForm组件使用方法详解
2019/07/23 Python
Django中create和save方法的不同
2019/08/13 Python
Django之编辑时根据条件跳转回原页面的方法
2019/08/21 Python
python3的url编码和解码,自定义gbk、utf-8的例子
2019/08/22 Python
django实现将修改好的新模型写入数据库
2020/03/31 Python
Python爬虫实例——爬取美团美食数据
2020/07/15 Python
Django模型验证器介绍与源码分析
2020/09/08 Python
Python实现哲学家就餐问题实例代码
2020/11/09 Python
python连接手机自动搜集蚂蚁森林能量的实现代码
2021/02/24 Python
机械专业毕业生自荐信
2013/11/02 职场文书
《会走路的树》教后反思
2014/04/19 职场文书
群众路线剖析材料范文
2014/10/09 职场文书
研究生给导师的自荐信
2015/03/06 职场文书
安全教育观后感
2015/06/17 职场文书
golang内置函数len的小技巧
2021/07/25 Golang
码云(gitee)通过git自动同步到阿里云服务器
2022/12/24 Servers