眾所周知,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的依賴。