現代前端開發已經離不開Node了。你們都知道在安裝Node時會附贈一個命令行工具Node Package Manager,即npm。或許你已經照着教程輸入過好多遍」npm install xxx」,而且你發現npm的命令林林總總幾十條,package.json的配置項使人眼花繚亂,但不知你有沒有認真想過,咱們爲何須要npm?若是沒有它,世界會怎樣?php
個人理解,npm所作的一切都是爲了解決軟件工程界一個一直以來的追求:代碼複用。抓住這個核心,也就抓住了正確理解和使用npm的鑰匙。css
爲何要複用代碼呢?由於基於已有的成熟代碼快速開發新的應用,能夠極大地提升開發效率,正所謂「站在巨人肩膀上」「不要重複造輪子」。html
So,在Node環境下要複用JS代碼,咱們有哪些方案呢? 前端
1. 刀耕火種——copy&paste
複製粘貼代碼的思路很直接,但現在還在的這樣搞的同窗應該是從原始社會穿越來的吧。。這個方案最大的缺點倒還不是代碼冗餘,而是一旦所複製的原始代碼發生了變化,那就必須手動修改每一處複本,在稍有規模的項目里根本不可行。 vue
2. 耕牛犁地——CommonJSnode
Node實現了一個模塊系統CommonJS,實在是JSer的一大福音。藉助它,咱們沒必要再複製粘貼代碼了:假如一個做者開發了一個名爲lib1的庫,他只需代碼寫在一個名叫lib1.js的文件裏,用module.export語句導出;而使用者只需把lib1.js下載到本身工程目錄,require一下即可直接用啦!(此時lib1也被稱爲一個「依賴」)webpack
但這裏仍然存在兩個大問題:git
一,若是lib1.js自己也複用了別的代碼,好比lib2.js、lib3.js...那你在下載lib1.js的時候,必須手動把它所依賴的這些模塊文件也一併下載;可要是lib2.js還依賴lib5.js、lib6.js....呢?一棵龐大的、深不見底的依賴樹很難手工管理。程序員
2、lib1.js的做者修復了幾個bug,但沒有一個機制能讓他通知你升級舊的模塊文件。github
做爲一名職業素養良好的程序員,看到這些問題的第一反應是否是「寫個腳本」?哈哈,不用麻煩了,由於已經有人替咱們寫好了,這個腳本工具就是npm。
3. 機械化耕做——npm
有了上面「自力更生」的原始體驗,再看看npm提供的依賴安裝、卸載、升級、發佈等一條龍服務,是否是很爽?
Npm制定了一個包規範,所謂規範就是一些格式和約定,好比約定從package.json文件裏讀取這個包的全部信息,包括它的名字、版本號、它依賴於哪些別的包等;又好比約定node_modules目錄專門用來存放第三方依賴,Node爲此提供的支持是內置的require方法默認會到這個目錄下去檢索模塊,而無需手動指定路徑。有了這些規範,一個包的開發、依賴安裝、發佈等都步驟都標準化了,省心省力。
能夠說,JavaScript從一門「玩具」語言,到現在能夠勝任大型項目開發,模塊化和npm是其進化路上的重要一步。
後記
你們都知道前端有「三板斧」,但剛纔我一直在談JS,徹底沒提到另外兩板斧。這是由於npm只是「Node模塊管理器」,Node上又沒有HTML和CSS,npm天然管不到。那麼除了能夠直接支持Node端的開發之外,npm又如何爲瀏覽器端開發提供支持呢?
答案是:npm生態圈提供了不少強大的前端開發工具,好比Webpack、Babel、ESLint等。特別是Webpack、Browserify、rollup這類構建工具,能夠接手瀏覽器端的依賴管理重任,以及不少其餘的附加功能。
npm 之於 Node.js ,就像 pip 之於 Python, gem 之於 Ruby, pear 之於 PHP 。
npm 是 Node.js 官方提供的包管理工具,他已經成了 Node.js 包的標準發佈平臺,用於 Node.js 包的發佈、傳播、依賴控制。npm 提供了命令行工具,使你能夠方便地下載、安裝、升級、刪除包,也可讓你做爲開發者發佈並維護包。
npm 是隨同 Node.js 一塊兒安裝的包管理工具,能解決 Node.js 代碼部署上的不少問題,常見的場景有如下幾種:
容許用戶從 npm 服務器下載別人編寫的第三方包到本地使用。 容許用戶從 npm 服務器下載並安裝別人編寫的命令行程序到本地使用。 容許用戶將本身編寫的包或命令行程序上傳到 npm 服務器供別人使用。
npm 的背後,是基於 couchdb 的一個數據庫,詳細記錄了每一個包的信息,包括做者、版本、依賴、受權信息等。它的一個很重要的做用就是:將開發者從繁瑣的包管理工做(版本、依賴等)中解放出來,更加專一於功能的開發。
npm 不須要單獨安裝。在安裝 Node 的時候,會連帶一塊兒安裝 npm 。可是,Node 附帶的 npm 可能不是最新版本,最後用下面的命令,更新到最新版本。
1
|
$ sudo npm install npm@latest -g
|
若是是 Window 系統使用如下命令便可:
1
|
npm install npm -g
|
也就是使用 npm 安裝本身。之因此能夠這樣,是由於 npm 自己與 Node 的其餘模塊沒有區別。 而後,運行下面的命令,查看各類信息。
1
2
3
4
5
6
7
8
9
10
11
|
# 查看 npm 命令列表
$ npm help
# 查看各個命令的簡單用法
$ npm -l
# 查看 npm 的版本
$ npm -v
# 查看 npm 的配置
$ npm config list -l
|
npm init 用來初始化生成一個新的 package.json 文件。它會向用戶提問一系列問題,若是你以爲不用修改默認配置,一路回車就能夠了。
若是使用了 -f(表明force)、-y(表明yes),則跳過提問階段,直接生成一個新的 package.json 文件。
1
|
$ npm init -y
|
npm set 用來設置環境變量
1
2
3
4
|
$ npm
set
init-author-
name
'Your name'
$ npm
set
init-author-email
'Your email'
$ npm
set
init-license
'MIT'
|
上面命令等於爲 npm init 設置了默認值,之後執行 npm init 的時候,package.json 的做者姓名、郵件、主頁、許可證字段就會自動寫入預設的值。這些信息會存放在用戶主目錄的 ~/.npmrc文件,使得用戶不用每一個項目都輸入。若是某個項目有不一樣的設置,能夠針對該項目運行 npm config。
npm info 命令能夠查看每一個模塊的具體信息。好比,查看 underscore 模塊的信息。
1
|
$ npm info underscore
|
上面命令返回一個 JavaScript 對象,包含了 underscore 模塊的詳細信息。這個對象的每一個成員,均可以直接從 info 命令查詢。
1
2
3
4
5
|
$ npm info underscore description
$ npm info underscore homepage
$ npm info underscore version
|
npm search 命令用於搜索 npm 倉庫,它後面能夠跟字符串,也能夠跟正則表達式。
1
|
$ npm search <搜索詞>
|
npm list 命令以樹形結構列出當前項目安裝的全部模塊,以及它們依賴的模塊。
1
2
3
4
5
6
7
|
$ npm list
# 加上
global
參數,會列出全局安裝的模塊
$ npm list -
global
# npm list 命令也能夠列出單個模塊
$ npm list underscore
|
使用 npm 安裝包的命令格式爲:
npm [install/i] [package_name]
npm 在默認狀況下會從 https://npmjs.org 搜索或下載包,將包安裝到當前目錄的 node_modules 子目錄下。
若是你熟悉 Ruby 的 gem 或者 Python 的 pip,你會發現 npm 與它們的行爲不一樣,gem 或 pip 老是以全局模式安裝,使包能夠供全部的程序使用,而 npm 默認會把包安裝到當前目錄下。這反映了 npm 不一樣的設計哲學。若是把包安裝到全局,能夠提供程序的重複利用程度,避免一樣的內容的多分副本,但壞處是難以處理不一樣的版本依賴。若是把包安裝到當前目錄,或者說本地,則不會有不一樣程序依賴不一樣版本的包的衝突問題,同時還減輕了包做者的 API 兼容性壓力,但缺陷則是同一個包可能會被安裝許屢次。
咱們在使用 supervisor 的時候使用了 npm install -g supervisor 命令,就是以全局模式安裝 supervisor 。
這裏注意一點的就是,supervisor 必須安裝到全局,若是你不安裝到全局,錯誤命令會提示你安裝到全局。若是不想安裝到默認的全局,也能夠本身修改全局路徑到當前路徑 npm config set prefix "路徑" 安裝完之後就能夠用 supervisor 來啓動服務了。
supervisor 能夠幫助你實現這個功能,它會監視你對代碼的驅動,並自動重啓 Node.js 。
通常來講,全局安裝只適用於工具模塊,好比 eslint 和 gulp 。關於使用全局模式,多數時候並非由於許多程序都有可能用到了它,爲了減小多重副本而使用全局模式,而是由於本地模式不會註冊 PATH 環境變量。
「本地安裝」指的是將一個模塊下載到當前項目的 node_modules 子目錄,而後只有在項目目錄之中,才能調用這個模塊。
本地模式和全局模式的特色以下:
模式 | 可經過 require 使用 | 註冊 PATH |
---|---|---|
本地模式 | 是 | 否 |
全局模式 | 否 | 是 |
1
2
3
4
5
6
|
# 本地安裝
$ npm install <package
name
=
""
>
# 全局安裝
$ sudo npm install -
global
<package
name
=
""
>
$ sudo npm install -g <package
name
=
""
></package></package></package>
|
npm install 也支持直接輸入 Github 代碼庫地址。
1
2
|
$ npm install git://github.com/package/path.git
$ npm install git://github.com/package/path.git#0.1.0
|
安裝以前,npm install 會先檢查,node_modules 目錄之中是否已經存在指定模塊。若是存在,就再也不從新安裝了,即便遠程倉庫已經有了一個新版本,也是如此。
若是你但願,一個模塊無論是否安裝過, npm 都要強制從新安裝,可使用 -f 或 –force 參數。
1
|
$ npm install <packagename>
--force</packagename>
|
install 命令老是安裝模塊的最新版本,若是要安裝模塊的特定版本,能夠在模塊名後面加上 @ 和版本號。
1
2
3
|
$ npm install sax@latest
$ npm install sax@0.1.1
$ npm install sax@
">=0.1.0 <0.2.0"
|
install 命令可使用不一樣參數,指定所安裝的模塊屬於哪種性質的依賴關係,即出如今 packages.json 文件的哪一項中。
–save:模塊名將被添加到 dependencies,能夠簡化爲參數-S。
–save-dev:模塊名將被添加到 devDependencies,能夠簡化爲參數-D。
1
2
3
4
5
|
$ npm install sax
--save
$ npm install node-tap
--save-dev
# 或者
$ npm install sax -S
$ npm install node-tap -D
|
這個能夠說是咱們 npm 核心一項內容,依賴管理,這個對象裏面的內容就是咱們這個項目所依賴的 js 模塊包。下面這段代碼表示咱們依賴了 markdown-it 這個包,版本是 ^8.1.0 ,表明最小依賴版本是 8.1.0 ,若是這個包有更新,那麼當咱們使用 npm install 命令的時候,npm 會幫咱們下載最新的包。當別人引用咱們這個包的時候,包內的依賴包也會被下載下來。
1
2
3
|
"dependencies"
: {
"markdown-it"
:
"^8.1.0"
}
|
在咱們開發的時候會用到的一些包,只是在開發環境中須要用到,可是在別人引用咱們包的時候,不會用到這些內容,放在 devDependencies 的包,在別人引用的時候不會被 npm 下載。
1
2
3
4
5
6
7
8
9
10
11
|
"devDependencies"
: {
"autoprefixer"
:
"^6.4.0"
,0
",
"
babel-preset-es2015
": "
^6.0.0
",
"
babel-preset-stage-2
": "
^6.0.0
",
"
babel-register
": "
^6.0.0
",
"
webpack
": "
^1.13.2
",
"
webpack-dev-middleware
": "
^1.8.3
",
"
webpack-hot-middleware
": "
^2.12.2
",
"
webpack-merge
": "
^0.14.1
",
"
highlightjs
": "
^9.8.0"
}
|
當你有了一個完整的 package.json 文件的時候,就可讓人一眼看出來,這個模塊的基本信息,和這個模塊所須要依賴的包。咱們能夠經過 npm install 就能夠很方便的下載好這個模塊所須要的包。
npm install 默認會安裝 dependencies 字段和 devDependencies 字段中的全部模塊,若是使用 --production 參數,能夠只安裝 dependencies 字段的模塊。
1
2
3
|
$ npm install
--production
# 或者
$ NODE_ENV=production npm install
|
一旦安裝了某個模塊,就能夠在代碼中用 require 命令加載這個模塊。
1
2
|
var backbone = require(
'backbone'
)
console.log(backbone.VERSION)
|
npm 不只能夠用於模塊管理,還能夠用於執行腳本。package.json 文件有一個 scripts 字段,能夠用於指定腳本命令,供 npm 直接調用。
package.json
1
2
3
4
5
6
7
8
9
10
11
12
|
{
"name"
:
"myproject"
,
"devDependencies"
: {
"jshint"
:
"latest"
,
"browserify"
:
"latest"
,
"mocha"
:
"latest"
},
"scripts"
: {
"lint"
:
"jshint **.js"
,
"test"
:
"mocha test/"
}
}
|
顧名思義,就是一些腳本代碼,能夠經過 npm run script-key 來調用,例如在這個 package.json 的文件夾下使用 npm run dev 就至關於運行了 node build/dev-server.js 這一段代碼。使用 scripts 的目的就是爲了把一些要執行的代碼合併到一塊兒,使用 npm run 來快速的運行,方便省事。
npm run 是 npm run-script 的縮寫,通常都使用前者,可是後者能夠更好的反應這個命令的本質。
1
2
3
4
5
6
7
8
9
|
// 腳本
"scripts"
: {
"dev"
:
"node build/dev-server.js"
,
"build"
:
"node build/build.js"
,
"docs"
:
"node build/docs.js"
,
"build-docs"
:
"npm run docs & git checkout gh-pages & xcopy /sy dist\\* . & git add . & git commit -m 'auto-pages' & git push & git checkout master"
,
"build-publish"
:
"rmdir /S /Q lib & npm run build &git add . & git commit -m auto-build & npm version patch & npm publish & git push"
,
"lint"
:
"eslint --ext .js,.vue src"
}
|
npm run 若是不加任何參數,直接運行,會列出 package.json 裏面全部能夠執行的腳本命令。
npm 內置了兩個命令簡寫, npm test 等同於執行 npm run test,npm start 等同於執行 npm run start。
1
|
"build"
:
"npm run build-js && npm run build-css"
|
上面的寫法是先運行 npm run build-js ,而後再運行 npm run build-css ,兩個命令中間用 && 鏈接。若是但願兩個命令同時平行執行,它們中間能夠用 & 鏈接。
寫在 scripts 屬性中的命令,也能夠在 node_modules/.bin 目錄中直接寫成 bash 腳本。下面是一個 bash 腳本。
1
2
3
4
|
#!/bin/bash
cd site/main
browserify browser/main.js | uglifyjs -mc >
static
/bundle.js
|
假定上面的腳本文件名爲 build.sh ,而且權限爲可執行,就能夠在 scripts 屬性中引用該文件。
1
|
"build-js"
:
"bin/build.sh"
|
npm run 爲每條命令提供了 pre- 和 post- 兩個鉤子(hook)。以 npm run lint 爲例,執行這條命令以前,npm 會先查看有沒有定義 prelint 和 postlint 兩個鉤子,若是有的話,就會先執行 npm run prelint,而後執行 npm run lint,最後執行 npm run postlint。
1
2
3
4
5
6
7
8
9
10
11
12
13
|
{
"name"
:
"myproject"
,
"devDependencies"
: {
"eslint"
:
"latest"
"karma"
:
"latest"
},
"scripts"
: {
"lint"
:
"eslint --cache --ext .js --ext .jsx src"
,
"test"
:
"karma start --log-leve=error karma.config.js --single-run=true"
,
"pretest"
:
"npm run lint"
,
"posttest"
:
"echo 'Finished running tests'"
}
}
|
上面代碼是一個 package.json 文件的例子。若是執行 npm test,會按下面的順序執行相應的命令。
1. pretest
2. test
3. posttest
若是執行過程出錯,就不會執行排在後面的腳本,即若是 prelint 腳本執行出錯,就不會接着執行 lint 和 postlint 腳本。
npm bin 命令顯示相對於當前目錄的,Node 模塊的可執行腳本所在的目錄(即 .bin 目錄)。
1
2
3
|
# 項目根目錄下執行
$ npm bin
./node_modules/.bin
|
npm 提供了一個有趣的命令 npm link,它的功能是在本地包和全局包之間建立符號連接。咱們說過使用全局模式安裝的包不能直接經過 require 使用。但經過 npm link 命令能夠打破這一限制。舉個例子,咱們已經經過 npm install -g express 安裝了 express,這時在工程的目錄下運行命令:
1
|
npm link express ./node_modules/express -> /
user
/
local
/lib/node_modules/express
|
咱們能夠在 node_modules 子目錄中發現一個指向安裝到全局的包的符號連接。經過這種方法,咱們就能夠把全局包當作本地包來使用了。
除了將全局的包連接到本地之外,使用 npm link 命令還能夠將本地的包連接到全局。使用方法是在包目錄(package.json 所在目錄)中運行 npm link 命令。若是咱們要開發一個包,利用這種方法能夠很是方便地在不一樣的工程間進行測試。
包是在模塊基礎上更深一步的抽象,Node.js 的包相似於 C/C++ 的函數庫或者 Java、.Net 的類庫。它將某個獨立的功能封裝起來,用於發佈、更新、依賴管理和版本控制。Node.js 根據 CommonJS 規範實現了包機制,開發了 npm 來解決包的發佈和獲取需求。
Node.js 的包是一個目錄,其中包含了一個 JSON 格式的包說明文件 package.json。嚴格符合 CommonJS 規範的包應該具有如下特徵:
。package.json 必須在包的頂層目錄下;
。二進制文件應該在 bin 目錄下;
。JavaScript 代碼應該在 lib 目錄下;
。文檔應該在 doc 目錄下;
。單元測試應該在 test 目錄下。
Node.js 對包的要求並無這麼嚴格,只要頂層目錄下有 package.json,並符合一些規範便可。固然爲了提升兼容性,咱們仍是建議你在製做包的時候,嚴格遵照 CommonJS 規範。
咱們也能夠把文件夾封裝爲一個模塊,即所謂的包。包一般是一些模塊的集合,在模塊的基礎上提供了更高層的抽象,至關於提供了一些固定接口的函數庫。經過定製 package.json,咱們能夠建立更復雜,更完善,更符合規範的包用於發佈。
Node.js 在調用某個包時,會首先檢查包中 packgage.json 文件的 main 字段,將其做爲包的接口模塊,若是 package.json 或 main 字段不存在,會嘗試尋找 index.js 或 index.node 做爲包的接口。
package.json 是 CommonJS 規定的用來描述包的文件,徹底符合規範的 package.json 文件應該含有如下字段:
name: 包的名字,必須是惟一的,由小寫英文字母、數字和下劃線組成,不能包含空格。
description: 包的簡要說明。
version: 符合語義化版本識別規範的版本字符串。
keywords: 關鍵字數組,一般用於搜索。
maintainers: 維護者數組,每一個元素要包含 name 、email(可選)、web(可選)字段。
contributors: 貢獻者數組,格式與 maintainers 相同。包的做者應該是貢獻者數組的第一個元素。
bugs: 提交 bug 的地址,能夠是網址或者電子郵件地址。
licenses: 許可證數組,每一個元素要包含 type(許可證的名稱)和 url(連接到許可證文本的地址)字段。
repositories: 倉庫託管地址數組,每一個元素要包含 type(倉庫的類型,如 git)、URL(倉庫的地址)和 path(相對於倉庫的路徑,可選)字段。
dependencies: 包的依賴,一個關聯數組,由包名稱和版本號組成。
官方文檔https://www.npmjs.cn/getting-started/publishing-npm-packages/
經過使用 npm init 能夠根據交互式回答產生一個符合標準的 package.json。建立一個 index.js 做爲包的接口,一個簡單的包就製做完成了。
修改package.js的name屬性做爲發佈包的名字。
在發佈前,咱們還須要得到一個帳號用於從此維護本身的包,使用 npm adduser 根據提示完成帳號的建立,若是你是在npm網站上註冊的帳號在終端上使用npm login根據提示登錄。
完成後可使用 npm whoami 檢測是否已經取得了帳號,若是登錄已經登錄會顯示你的用戶名。
若是你的包未來有更新,只須要在 package.json 文件中修改 version 字段,而後從新使用 npm publish 命令就好了。
若是你對已發佈的包不滿意,可使用 npm unpublish 命令來取消發佈。
須要說明的是:json 文件不能有註釋。
下載包本身的包方式和下載其它包時同樣,使用npm install packagename就能夠了。
最後在說一下package.js中的"devDependencies"和"dependencies",開始覺得這兩個字段和webpack打包相關,其實沒有一點關係,webpack中依賴的包不管安裝到哪裏均可以使用到。