瞭解可執行的NPM包
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
的原始碼中很容易找到:
- ofollow,noindex">獲取配置檔案字尾
- 獲取對應的直譯器並引入模組註冊
根據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 └── .# 在這裡執行指令碼 複製程式碼
以及一個相反的栗子:chestnut:,如果有些依賴在global
下安裝了,但是沒有在local
下進行安裝,也許會出現這樣的情況,命令直接呼叫的話,完全沒有問題,但是放到npm scripts
中,或者使用npx
來進行呼叫,則發現提示模組不存在各種balabala的異常。
P.S. 在webpack
中,如果模組不存在,並不會給你報錯,而是預設按照JS
的方式進行解析,所以可能會遇到提示語法錯誤,這時候不用想了,一定是缺少依賴
也可以說npx
是個好東西,儘量使用npx
的方式來呼叫,能少踩一些global
、local
的坑
最終的上線
當然了,真實的開發完一個工具以後,就需要進行提交到NPM
上了,這個也是一個很簡單的步驟,npm publish
即可,會自動獲取package.json
中的name
作為包名(重複了會報錯)。
小結
總結了一下關於NPM
可執行的包相關的一些東東,希望能夠幫大家簡單的理解這是個什麼,以及global
和local
下一些可能會遇到的問題,希望能夠讓大家繞過這些坑。
如文中有誤還請指出,NPM
工具相關的問題也歡迎來討論。