众所周知,node的出现使的前端人员可以在服务器端编写javascript代码,也使前端的范围不仅仅是局限在浏览器端。而node所遵循的CommonJs规范也让javascript能够像其他语言(比如java,python)以模块化的形式开发(当然,在浏览器端目前也可以通过三方工具实现模块化规范,比如require.js,sea.js)。
node组织了自身的核心模块,也使得第三方文件模块可以有序地编写和使用。但是在第三方模块中,模块和模块之间仍然是互相独立的,他们相互之间不能互相饮用。在模块之外,包和NPM则是将模块联系起来的一种机制。
当提到模块时,不得不再回到CommonJs规范,npm可以理解成一个联系模块与模块的纽带,而其中的运作方式,都是基于CommonJs规范。其核心思想可以总结为一句话--“文件即模块” 举个简单的例子
// 定义一个cat模块,文件名cat.js
var cats = ['小猫1','小猫2','小猫3'];
module.exports = cats;
复制代码
可以看到,在这个模块文件中,定义了一个数组,通过module.exports将这个数组暴露出去,这样就形成了一个模块,而其他模块想要使用这个模块时,只需通过require方法去引用就行了。
// 引用模块为main.js
// 这里引用的所写的是相对地址
var cat = require('./cat')
console.log(cat)
复制代码
通过node命令执行main.js
node main.js
//输出 ['小猫1','小猫2','小猫3']
复制代码
通过这个简单的例子可以看到,node遵循的CommonJs规范使其处理不同的文件时都把它们作为一个模块对待,而模块的引用则是通过require(这是node自身实现的一个方法)方法来实现模块之间的互相依赖。当我们需要实现某个功能时,可能需要开发不同的模块,这些模块通过相互引用,最终实现一个完整的功能,这些模块组合在一起,就形成了--包。原理如图
到这里,我们对包和NPM的概念有了一个初步的概念,包是模块的集合,NPM是包管理工具。
我们已经知道了包是由一组相互依赖的模块组成的,当我们要使用一个包时,我们如何知道这个包的信息呢?这就引入到下一个概念:CommonJs包规范。
CommonJs包规范包括两方面:包结构 和 包描述文件 包结构,即包的文件结构,完全遵循CommonJs包规范的包目录应该包含如下文件:
package.json //包描述文件
bin //用于存放可执行二进制文件的目录
lib //用于存放javascript代码的目录
doc //用于存放文档的目录
test //用于存放单元测试用例的代码
复制代码
包结构不做过多介绍,接下来着重介绍包描述文件package.json
包描述文件用于表达非代码相关的信息,位于包的根目录下,是包的重要组成部分,NPM的所有行为都与包描述文件的字段息息相关。 我们以大名鼎鼎的express的包描述文件为例,来一探package.json的秘密~(实际文件内容很多,此处仅截取一些典型的内容做示例)
"name": "express", //包名字
"description": "Fast, unopinionated, minimalist web framework", //包描述
"version": "4.15.3", // 包版本号
"keywords": [
"express",
"framework"
], // 包关键字,可以通过这些关键字在npm中搜索到
"maintainers": [
{
"name": "dougwilson",
"email": "doug@somethingdoug.com"
}
], // 维护人员名单
"contributors": [
{
"name": "Aaron Heckmann",
"email": "aaron.heckmann+github@gmail.com"
},
{
"name": "Ciaran Jessup",
"email": "ciaranj@gmail.com"
}
], // 贡献者
"bugs": {
"url": "https://github.com/expressjs/express/issues"
}, // 提交bug的地址
"license": "MIT", // 当前包所使用的许可证列表
"repository": {
"type": "git",
"url": "git+https://github.com/expressjs/express.git"
}, // github仓库地址
"dependencies": {
"accepts": "~1.3.3",
"array-flatten": "1.1.1"
}, // 使用当前包所需要依赖的包的列表,该属性十分重要,后面详细介绍
"homepage": "http://expressjs.com/", //包的官网
"engines": {
"node": ">= 0.10.0"
}, // 支持的javascript引擎列表
"devDependencies": {
"after": "0.8.2",
"body-parser": "1.17.1"
}, // 开发依赖包列表,有别与dependencies
"scripts": {
"test": "mocha --require test/support/env --reporter spec --bail --check-leaks test/ test/acceptance/",
"test-ci": "istanbul cover node_modules/mocha/bin/_mocha --report lcovonly -- --require test/support/env --reporter spec --check-leaks test/ test/acceptance/",
"test-cov": "istanbul cover node_modules/mocha/bin/_mocha -- --require test/support/env --reporter dot --check-leaks test/ test/acceptance/",
"test-tap": "mocha --require test/support/env --reporter tap --check-leaks test/ test/acceptance/"
} // 脚本说明对象,后面会介绍到
复制代码
到这里,已经大概对包规范的概念有了一些了解,包规范的定义可以帮助Node解决依赖包安装的问题,而NPM正是基于该规范的实现。我们在安装好node的时候,NPM就作为一个附带内置工具包含在内,可以直接使用。
接下来,开发一个简单的node天气查询命令行工具,来看看NPM的运用。
新建一个文件夹,我们的源文件就放在这里
mkdir weatherquery && cd weatherquery
复制代码
第一个npm命令,初始化
npm init
复制代码
输入这个指令后,会要求我们输入一些关于这个包的一些基本信息,也可以通过 npm init -y 指令跳过这些步骤,直接采用默认配置 输入基本包信息后,大致信息如下
确认后输入yes按回车,再回到我们的根目录下,可以看到,多出了package.json文件,里面的内容就是我们配置的信息
{
"name": "weatherzyang",
"version": "1.0.0",
"description": "阳哥天气查询demo",
"main": "index.js", //入口文件为index.js
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [
"weather"
],
"author": "zyang",
"license": "ISC"
}
复制代码
至此,我们已经准备好开发的前期准备工作了,回到我们的需求,我们需要做一个天气查询命令行工具,那首先要有一个规则,这里我把规则定位,输入命令,返回城市天气,命令带上的参数为城市名字,如果没有参数,则根据ip地址判断地理位置返回天气,还需要一个天气查询的接口,简单效果大概如下
// 命令输入,参数为北京
weatherzyang 北京
// 返回结果
天气:晴
温度: 24-30度
复制代码
到这里有个疑惑,我如何在node端输入参数呢?这里就要用到node的一个属性,process.argv,先新建一个index.js文件,看看这个属性是什么,编辑index.js,内容如下
console.log(process.argv)
复制代码
通过node命令执行index.js
node index.js
复制代码
结果如下
再次通过node命令执行index.js,这次我们加上一个参数
node index.js 北京
复制代码
结果如下
可以看出来,我们输出的参数被打印了出来了,第一个问题解决。
第二个问题,我需要调用借口,那我需要发出请求,获取数据,那如何获取呢?难道需要我自己开发出一个请求模块吗?当然没这个必要,NPM的强大就在这里体现出来了,我们可以通过NPM来引入可以发出请求的包,直接调用就行了。这里就引入第二个概念,通过NPM来引入包。这里我们引入axios包,具体用法可以参考axiso-npm,具体做法如下 通过npm install 命令下载包
npm install --save axios
复制代码
安装完成后,看看package.json,多出了dependencies字段,即我们要开发的工具包的依赖
并且项目中多出了node_modules文件夹,这个文件夹就用用来存放我们引入的包啦。
这时只需要通过require()来引入axios,即可使用其功能了。 查询接口使用网络资源,具体接口规范不多做介绍。接下来继续编辑index.js
// 引用axios模块
var axios = require('axios');
// 定义查询参数
var data = {};
// 判断用户是否输入了城市参数,如果输入了参数,就给查询参数赋值
if(process.argv[2]){
data.params = {
city: process.argv[2]
}
}
// 使用axios发出查询请求,具体用法可以参考npm
axios.get('此处为接口地址', data)
.then(function(response) {
var weather = response.data.results[0].weather_data[0];
console.log(response.data.results[0].currentCity);
console.log(weather.temperature);
console.log(weather.weather + ',' + weather.wind);
})
.catch(function(err){
console.log(err);
})
复制代码
分别不输入参数,输入参数执行index.js,效果如下
至此,一个简单的查询天气功能就做好啦,但是还有点问题,我们要做的是一个命令行工具啊,为什么还要繁琐的去输入node index.js 这样的命令啊,能再简单点吗?当然可以,还记得前面介绍的package.json配置参数吗,package.json的参数非常多,要实现命令行工具的简化,这里需要用到bin的配置。 编辑package.json,新增bin配置
{
"name": "weatherzyang",
"version": "1.0.0",
"description": "阳哥天气查询demo",
"main": "index.js",
"bin": {
"weatherzyang": "./index.js" // 直接通过weatherzyang 命令来执行查询天气的功能
},
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [
"weather"
],
"author": "zyang",
"license": "ISC",
"dependencies": {
"axios": "^0.16.2"
}
}
复制代码
配置好bin字段后,通过npm install -g package_name(此处-g表示全局安装) 的方式把我们发布的包全局安装,就能在命令行中直接执行weatherzyang使用
再修改一下index.js文件,在文件开头加一句声明
#!/usr/bin/env node
复制代码
这句话的意思就是声明脚本执行环境为node,因为我们开发的就是node命令行工具啊。。
一切都完成后,进入下一步,发布我们的工具到npm
npm login //输入账号密码登陆
复制代码
登陆后执行publish命令
npm publish
复制代码
至此,我们的包就发布到npm上去了,当别人需要用这个包的时候,只需要install下来即可,操作如下 全局安装发布的包,包名字为weatherzyang
npm install -g weatherzyang
复制代码
执行bin命令
weatherzyang
复制代码
效果如下,大功告成,有兴趣的同学也可以去下载这个包查看源码(可能是开了翻墙,导致默认地点竟然是徐州。。。)
另外还漏掉一个知识点,dependencies和devDependencies的区别,这两个参数都是描述包的依赖,前者为包实现功能所需的依赖,而后者即使开发包的时候所需的依赖,一般后者的依赖都是一些测试工具,仅用在开发阶段调试使用,项目上线后这些依赖对用户来说是不必要的,用户通过npm install来载入依赖的时候只会读取dependencies的依赖,而忽略devDependencies的依赖。