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 相关文章推荐
JavaScript 指导方针
Apr 05 Javascript
[原创]js获取数组任意个不重复的随机数组元素
Mar 15 Javascript
JavaScript调用Activex控件的事件的实现方法
Apr 11 Javascript
jquery parent和parents的区别分析
Oct 02 Javascript
JQuery动态添加和删除表格行的方法
Mar 09 Javascript
javascript实现鼠标移到Image上方时显示文字效果的方法
Aug 07 Javascript
JavaScript的代码编写格式规范指南
Dec 07 Javascript
深入分析javascript中的错误处理机制
Jul 17 Javascript
jQuery实现最简单的切换图效果【可兼容IE6、火狐、谷歌、opera等】
Sep 04 Javascript
vue如何集成raphael.js中国地图的方法示例
Aug 15 Javascript
JavaScript插入排序算法原理与实现方法示例
Aug 06 Javascript
微信小程序如何修改本地缓存key中单个数据的详解
Apr 26 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
咖啡店都有些什么常规豆子呢?有什么风味在里面
2021/03/04 咖啡文化
php array_map()数组函数使用说明
2011/07/12 PHP
php 函数中使用static的说明
2012/06/01 PHP
PHP截取发动短信内容的方法
2017/07/04 PHP
PHP中的浅复制与深复制的实例详解
2017/10/26 PHP
可选择和输入的下拉列表框示例
2013/11/05 Javascript
三种检测iPhone/iPad设备方向的方法
2014/04/23 Javascript
d3.js实现简单的网络拓扑图实例代码
2016/11/06 Javascript
解析利用javascript如何判断一个数为素数
2016/12/08 Javascript
Bootstrap datepicker日期选择器插件使用详解
2017/07/26 Javascript
jQuery实现简单三级联动效果
2020/09/05 jQuery
vue项目页面嵌入代码块vue-prism-editor的实现
2020/10/30 Javascript
详解Vue的mixin策略
2020/11/19 Vue.js
微信小程序实现点击导航条切换页面
2020/11/19 Javascript
[01:10]DOTA2次级职业联赛 - EP战队宣传片
2014/12/01 DOTA
Python中的列表知识点汇总
2015/04/14 Python
Python中unittest模块做UT(单元测试)使用实例
2015/06/12 Python
windows下python连接oracle数据库
2017/06/07 Python
Python Requests模拟登录实现图书馆座位自动预约
2018/04/27 Python
Python + selenium自动化环境搭建的完整步骤
2018/05/19 Python
Flask框架使用DBUtils模块连接数据库操作示例
2018/07/20 Python
python开发准备工作之配置虚拟环境(非常重要)
2019/02/11 Python
python 获取微信好友列表的方法(微信web)
2019/02/21 Python
Python快速转换numpy数组中Nan和Inf的方法实例说明
2019/02/21 Python
python里dict变成list实例方法
2019/06/26 Python
Python Handler处理器和自定义Opener原理详解
2020/03/05 Python
Python3爬虫里关于代理的设置总结
2020/07/30 Python
英国花园家具中心:Garden Furniture Centre
2017/08/24 全球购物
美国独家设计师眼镜在线光学商店:Glasses Gallery
2017/12/28 全球购物
哄娃神器4moms商店:美国婴童用品品牌
2019/03/07 全球购物
全球性的在线婚纱礼服工厂:27dress.com
2019/03/21 全球购物
销售主管岗位职责范本
2014/02/14 职场文书
公司试用期员工自我评价
2014/09/17 职场文书
2016年中秋节晚会领导致辞
2015/11/26 职场文书
vue中的可拖拽宽度div的实现示例
2022/04/08 Vue.js
Python实现视频自动打码的示例代码
2022/04/08 Python