NPM
是Node.js
的包管理工具,隨著Node.js
的出現,以及前端開發開始使用gulp
、webpack
、rollup
以及其他各種優秀的編譯打包工具(大多數採用Node.js
來實現),大家都開始接觸到一些Node.js
,發現了使用NPM
來管理一些第三方模組會很方便。
大家搬磚的模式也是從之前的去外掛官網下載XXX.min.js
改為了npm install XXX
,然後在專案中require
或者import
。
當然,NPM
上邊不僅僅存在一些用來打包、引用的第三方模組,還有很多優秀的工具(包括部分打包工具),他們與上邊提到的模組的區別在於,使用npm install XXX
以後,是可以直接執行的。
常見的那些包
可以回想一下,webpack
官網中是否有過這樣的字樣:
> npm install webpack -g
> webpack
複製程式碼
當然,現在是不推薦使用全域性安裝模式的,具體原因會在下邊提到
以及非全域性的安裝使用步驟:
> npm install webpack
複製程式碼
然後編輯你的package.json
檔案:
{
"scripts": {
+ "webpack": "webpack"
}
}
複製程式碼
再使用npm run
就可以呼叫了:
> npm run webpack
複製程式碼
以上非全域性的方案是比較推薦的做法
不過還可以順帶一提的是在NPM 5.x
更新的一個新的工具,叫做npx
,並不打算細說它,但它確實是一個很方便的小工具,在webpack
官網中也提到了簡單的使用方法
就像上邊所提到的修改package.json
,新增scripts
然後再執行的方式,可以很簡單的使用npx webpack
來完成相同的效果,不必再去修改額外的檔案。(當然,npx
可以做更多的事情,在這裡先認為它是./node_modules/webpack/bin/webpack.js
的簡寫就好了)
包括其他常用的一些,像n
、create-react-app
、vue-cli
這些工具,都會直接提供一個命令讓你可以進行操作。
自己造一個簡易的工具
最近面試的時候,有同學的回答讓人哭笑不得:
Q:你們前端開發完成後是怎樣打包的呢?
A:npm run build
。
[黑人問號臉.png]。經過再三確認後,該同學表示並沒有研究過具體是什麼,只知道執行完這個命令以後就可以了。
我本以為這僅僅是網上的一個段子,但沒想到真的被我碰到了。也不知道是好事兒還是壞事兒。。
從我個人的角度考慮,還是建議瞭解下你所使用的工具。至少看下scripts
裡邊究竟寫的是什麼咯 :)
P.S. npm scripts
中不僅僅可以執行NPM
模組,普通的shell
命令都是支援的
建立工程
首先的第一步,就是你需要有一個資料夾來存放你的NPM
包,因為是一個簡單的示例,所以不會真實的進行上傳,會使用npm ln
來代替npm publish
+ npm install
。
隨便建立一個資料夾即可,資料夾的名字也並不會產生太大的影響。
然後需要建立一個package.json
檔案,可以通過npm init
來快速的生成,我個人更喜歡新增-y
標識來跳過一些非必填的欄位。
> mkdir test-util
> cd test-util
> npm init -y
複製程式碼
建立執行檔案
因為我們這個模組就是用來執行使用的,所以有沒有入口檔案實際上是沒有必要的,我們僅僅需要建立對應的執行檔案即可,需要注意的一點是:與普通的JS
檔案區別在於頭部一定要寫上#!/usr/bin/env node
#!/usr/bin/env node
// index.js
console.log('first util')
複製程式碼
註冊執行命令
然後就是修改package.json
來告訴NPM
我們的執行檔案在哪:
{
+ "bin": "./index.js"
}
複製程式碼
在只有一個bin
,且要註冊的命令與package.json
中的name
欄位相同時,則可以寫成上邊那種形式,如果要註冊多個可執行命令,那麼就可以寫成一個k/v
結構的引數:
{
"bin": {
"command1": "./command1.js",
"command2": "./command2.js"
}
}
複製程式碼
呼叫時就是 command1 | command2
模擬執行
接下來我們去找另一個資料夾模擬安裝NPM
模組,再執行npm ln
就可以了,再執行對應的命令以後你應該會看到上邊的log
輸出了:
> cd .. && mkdir fake-repo && cd fake-repo
> npm ln ../test-util
> test-util # global
first util
> npx test-util # local
first util
複製程式碼
這樣一個最簡易的可執行包就建立完成了。
npm ln 為 npm link 的簡寫
npm ln <模組路徑> 相當於 cd <模組路徑> && npm ln + npm ln <模組名>
要注意是 模組名,而非資料夾名, 模組名 為package.json
中所填寫的name
欄位
global 與 local 的區別
因為npm link
執行的特性,會將global
+local
的依賴都進行安裝,所以在使用上不太好體現出兩者的差異,所以我們決定將程式碼直接拷貝到node_modules
下:
> npm unlink --no-save test-util # 僅移除 local 的依賴
> cp -R ../test-util ./node_modules/
> npm rebuild
複製程式碼
因為繞過了NPM
的安裝步驟,一定要記得npm rebuild
來讓NPM
知道我們的包註冊了bin
這時候我們修改指令碼檔案,在指令碼中新增當前執行目錄的輸出
#!/usr/bin/env node
- console.log('first util')
+ console.log(process.execPath) // 返回JS檔案上層資料夾的完整路徑
複製程式碼
這時再次執行兩種命令,就可以看到區別了。
之所以要提到global
與local
,是因為在開發的過程中可能會不經意的在這裡踩坑。
比如說我們在開發Node
專案時,經常會用到nodemon
來幫助在開發期間監聽檔案變化並自動重啟。
為了使用方便,很可能會將預定的一個啟動命令放到npm scripts
中去,類似這樣的:
{
"script": {
"start": "nodemon ./server.js"
}
}
複製程式碼
兩者混用會帶來的問題
這樣的專案在你本地使用是完全沒有問題的,但是如果有其他的同事需要執行你的這個專案,在第一步執行npm start
時就會出異常,因為他本地可能並沒有安裝nodemon
。
以及這樣的做法很可能會導致一些其它包引用的問題。
比如說,webpack
實際上是支援多種語言編寫config
配置檔案的,就拿TypeScript
舉例吧,最近也一直在用這個。
> webpack --config webpack.config.ts
複製程式碼
這樣的命令是完全有效的,webpack 會使用 ts 的直譯器去執行對應的配置檔案
因為webpack
不僅僅支援這一種直譯器,有很多種,類似CoffeeScript
也是支援的。
所以webpack
肯定不能夠將各種語言的直譯器依賴都放到自身的依賴模組中去,而是會根據傳入config
的檔案字尾名來動態的判斷應該新增哪些直譯器,這些在webpack
的原始碼中很容易找到:
根據webpack
動態獲取直譯器的模組interpret來看,.ts
型別的檔案會引入這些模組:['ts-node/register', 'typescript-node/register', 'typescript-register', 'typescript-require']
,但是在webpack
的依賴中你是找不到這些的。
在原始碼中也可以看到,webpack
在執行config
之前動態的引入了這些直譯器模組。
這裡也可以稍微提一下Node
中引入全域性模組的一些事兒,我們都知道,通過npm install
安裝的模組,都可以通過require('XXX')
來直接引用,如果一些第三方模組需要引入某些其他的模組,那麼這個模組也需要存在於它所處目錄下的node_modules
資料夾中才能夠正確的引入。
首先有一點大家應該都知道的,目前版本的NPM
,不會再有黑洞那樣深的node_modules
了,而是會將依賴平鋪放在node_modules
資料夾下。比如說你引入的模組A
,A
的內部引用了模組B
,那麼你也可以直接引用模組B
,因為A
和B
都存在於node_modules
下。
還是拿我們剛才做的那個小工具來實驗,我們在fake-repo
中新增express
的依賴,然後在test-util
中新增koa
的依賴,並在test-util/index.js
中require
上述的兩個模組。
你會發現,npx test-util
執行正確,而test-util
卻直接報錯了,提示express
不存在。
我們可以通過NPM
的一個命令來解釋這個原因:
> npm root
<current>/node_modules
> npm root -g
<global>/node_modules
複製程式碼
這樣輸出兩個路徑應該就能看的比較明白了,koa
模組是沒有問題的,因為都是存在於這些路徑下的node_modules
,而express
則只存在於<current>/node_modules/test-util/node_modules
下,全域性呼叫下,require
是找不到express
的。
# global 下的結構
.
├── /usr/local/lib/node_modules # npm root 的位置
│ ├── koa
│ └── test-util # 執行指令碼所處的位置
└── <workspace> # 本地的專案
├── node_modules
│ └── express
└── .
# local 下的結構
└── <workspace> # 本地的專案
├── node_modules # npm root 的位置
│ ├── koa
│ ├── test-util # 執行指令碼所處的位置
│ └── express
└── .
複製程式碼
所以這也從側面說明了為什麼webpack
可以直接在自己的檔案中引用並不存在於自己模組下的依賴。
因為webpack
認為如果你要使用TypeScript
,那麼一定會有對應的依賴,這個模組就是與webpack
同級的依賴,也就是說webpack
可以放心的進行require
,大致這樣的結構:
├── node_modules # npm root 的位置
│ ├── webpack
│ └── typescript
└── . # 在這裡執行指令碼
複製程式碼
以及一個相反的栗子?,如果有些依賴在global
下安裝了,但是沒有在local
下進行安裝,也許會出現這樣的情況,命令直接呼叫的話,完全沒有問題,但是放到npm scripts
中,或者使用npx
來進行呼叫,則發現提示模組不存在各種balabala的異常。
P.S. 在webpack
中,如果模組不存在,並不會給你報錯,而是預設按照JS
的方式進行解析,所以可能會遇到提示語法錯誤,這時候不用想了,一定是缺少依賴
也可以說npx
是個好東西,儘量使用npx
的方式來呼叫,能少踩一些global
、local
的坑
最終的上線
當然了,真實的開發完一個工具以後,就需要進行提交到NPM
上了,這個也是一個很簡單的步驟,npm publish
即可,會自動獲取package.json
中的name
作為包名(重複了會報錯)。
小結
總結了一下關於NPM
可執行的包相關的一些東東,希望能夠幫大家簡單的理解這是個什麼,以及global
和local
下一些可能會遇到的問題,希望能夠讓大家繞過這些坑。
如文中有誤還請指出,NPM
工具相關的問題也歡迎來討論。