前端構建之NPM


NPM的由來

現代前端開發已經離不開Node了。大家都知道在安裝Node時會附贈一個命令行工具Node Package Manager,即npm。或許你已經照着教程輸入過好多遍”npm install xxx”,並且你發現npm的命令林林總總幾十條,package.json的配置項令人眼花繚亂,但不知你有沒有認真想過,我們為什么需要npm?如果沒有它,世界會怎樣?

我的理解,npm所做的一切都是為了解決軟件工程界一個一直以來的追求:代碼復用。抓住這個核心,也就抓住了正確理解和使用npm的鑰匙。

為什么要復用代碼呢?因為基於已有的成熟代碼快速開發新的應用,可以極大地提高開發效率,正所謂“站在巨人肩膀上”“不要重復造輪子”。

So,在Node環境下要復用JS代碼,我們有哪些方案呢? 

1. 刀耕火種——copy&paste
復制粘貼代碼的思路很直接,但如今還在的這樣搞的同學應該是從原始社會穿越來的吧。。這個方案最大的缺點倒還不是代碼冗余,而是一旦所復制的原始代碼發生了變化,那就必須手動修改每一處復本,在稍有規模的項目里根本不可行。 

2. 耕牛犁地——CommonJS

Node實現了一個模塊系統CommonJS,實在是JSer的一大福音。借助它,我們不必再復制粘貼代碼了:假如一個作者開發了一個名為lib1的庫,他只需代碼寫在一個名叫lib1.js的文件里,用module.export語句導出;而使用者只需把lib1.js下載到自己工程目錄,require一下便可直接用啦!(此時lib1也被稱為一個“依賴”)

但這里仍然存在兩個大問題:

一,如果lib1.js本身也復用了別的代碼,比如lib2.js、lib3.js...那你在下載lib1.js的時候,必須手動把它所依賴的這些模塊文件也一並下載;可要是lib2.js還依賴lib5.js、lib6.js....呢?一棵龐大的、深不見底的依賴樹很難手工管理。

二、lib1.js的作者修復了幾個bug,但沒有一個機制能讓他通知你升級舊的模塊文件。

作為一名職業素養良好的程序員,看到這些問題的第一反應是不是“寫個腳本”?哈哈,不用麻煩了,因為已經有人替我們寫好了,這個腳本工具就是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

npm 之於 Node.js ,就像 pip 之於 Python, gem 之於 Ruby, pear 之於 PHP 。

npm 是 Node.js 官方提供的包管理工具,他已經成了 Node.js 包的標准發布平台,用於 Node.js 包的發布、傳播、依賴控制。npm 提供了命令行工具,使你可以方便地下載、安裝、升級、刪除包,也可以讓你作為開發者發布並維護包。

為什么要使用 NPM

npm 是隨同 Node.js 一起安裝的包管理工具,能解決 Node.js 代碼部署上的很多問題,常見的場景有以下幾種:

允許用戶從 npm 服務器下載別人編寫的第三方包到本地使用。 允許用戶從 npm 服務器下載並安裝別人編寫的命令行程序到本地使用。 允許用戶將自己編寫的包或命令行程序上傳到 npm 服務器供別人使用。

npm 的背后,是基於 couchdb 的一個數據庫,詳細記錄了每個包的信息,包括作者、版本、依賴、授權信息等。它的一個很重要的作用就是:將開發者從繁瑣的包管理工作(版本、依賴等)中解放出來,更加專注於功能的開發。

如何使用 NPM

安裝

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

npm init 用來初始化生成一個新的 package.json 文件。它會向用戶提問一系列問題,如果你覺得不用修改默認配置,一路回車就可以了。
如果使用了 -f(代表force)、-y(代表yes),則跳過提問階段,直接生成一個新的 package.json 文件。

1
$ npm init -y

npm set

npm set 用來設置環境變量

1
2
3
4
$ npm set  init-author- name  'Your name'
$ npm set  init-author-email 'Your email'
$ npm set  init-author-url 'https://yourdomain.com'
$ npm set  init-license 'MIT'

上面命令等於為 npm init 設置了默認值,以后執行 npm init 的時候,package.json 的作者姓名、郵件、主頁、許可證字段就會自動寫入預設的值。這些信息會存放在用戶主目錄的 ~/.npmrc文件,使得用戶不用每個項目都輸入。如果某個項目有不同的設置,可以針對該項目運行 npm config。

npm info

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 search 命令用於搜索 npm 倉庫,它后面可以跟字符串,也可以跟正則表達式。

1
$ npm search <搜索詞>

npm list

npm list 命令以樹形結構列出當前項目安裝的所有模塊,以及它們依賴的模塊。

1
2
3
4
5
6
7
$ npm list
 
# 加上 global  參數,會列出全局安裝的模塊
$ npm list - global
 
# npm list 命令也可以列出單個模塊
$ npm list underscore

npm install

使用 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 代碼庫地址。

安裝之前,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

dependencies 依賴

這個可以說是我們 npm 核心一項內容,依賴管理,這個對象里面的內容就是我們這個項目所依賴的 js 模塊包。下面這段代碼表示我們依賴了 markdown-it 這個包,版本是 ^8.1.0 ,代表最小依賴版本是 8.1.0 ,如果這個包有更新,那么當我們使用 npm install 命令的時候,npm 會幫我們下載最新的包。當別人引用我們這個包的時候,包內的依賴包也會被下載下來。

1
2
3
"dependencies" : {
     "markdown-it" : "^8.1.0"
}

devDependencies 開發依賴

在我們開發的時候會用到的一些包,只是在開發環境中需要用到,但是在別人引用我們包的時候,不會用到這些內容,放在 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 run

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/"
   }
}

scripts 腳本

顧名思義,就是一些腳本代碼,可以通過 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"

pre- 和 post- 腳本

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

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中依賴的包無論安裝到哪里都可以使用到。

 

原文:http://www.cnblogs.com/leegent/p/7244660.html


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM