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工具相關的問題也歡迎來討論。
