【 Node.js 進階】你應該知道的 NPM 知識都在這!
爲什麼寫這篇文章
很多 Node.js
開發者,都知道有 package.json
這個文件,也多少都瞭解一些 npm
知識,但是可能沒有系統的學習過,這部分的知識對於開發一個 cli
工具,發佈自己的 npm
包都很常用,開發中也會經常用到 npm script
內容,系統的學習一下確實會有所幫助,上面三個場景如果你都用不到,配置上節約時間,知其所以然也是有必要的!
本文你能學到哪些?
文章比較長,希望耐心看完!
寫文本的目標:希望再遇到 package.json
, npm
相關的問題,不用再去搜索,一篇文章全覆蓋,不一定全掌握,知道我這裏都有,需要時候能直接來查就好了,也爲了方便自己後面查閱。(本文主要講的部分是 npm script
,個人認爲很常用,一些文章都沒有講)
好了,開始我們的正文學習吧。
package.json 如何產生的
npm init
npm init
命令用來初始化一個簡單的 package.json
文件,執行該命令後終端會依次詢問 name
, version
, description
等字段。
npm init 默認執行行爲
我們在執行 npm init
的時候,會有一個初始化 pacakge.json
過程,然後一路回車,其實可以直接使用 npm init --yes
在命令後追加 --yes
參數即可,其作用與一路回車相同,這樣生成的文件中就包含 package.json
文件
自定義 npm init 行爲
npm init
命令的原理並不複雜,調用 shell 腳本,輸出一個初始化的 package.json
文件。所以相應地,自定義 npm init
命令的實現方式也很簡單,在電腦 npmStudy
目錄創建一個 .npm-init.js
即可,該文件的 module.exports
即爲 package.json
配置內容,需要獲取用戶輸入時候,使用 prompt()
方法即可。例如編寫這樣的 ~/.npm-init.js
const desc = prompt('description?', 'A new package...') const bar = prompt('bar?', '') const count = prompt('count?', '42') module.exports = { key: 'value', foo: { bar: bar, count: count }, name: prompt('name?', process.cwd().split('/').pop()), version: prompt('version?', '0.1.0'), description: desc, main: 'index.js', }
然後在 /npmStudy
目錄下執行 npm init
會出現下圖中對一系列操作
然後生成 package.json
文件。到這裏一個 npm init
簡單自定義過程結束,知道了兩種生成 pacakge.json
的方式
package.json 中的常規屬性
對於常規屬性都知道的可以忽略,繼續往下看 npm script
主要想講的部分。
npm 中的依賴包
這裏只說我們常用的兩個依賴包 dependenices
和 devDependenices
,其它的一些依賴包只有作爲包的發佈者纔會用到,需要的小夥伴自行查看文檔。
dependenices
通過命令 npm install/i packageName -S/--save
把包裝在此依賴項裏。如果沒有指定版本,直接寫一個包的名字,則安裝當前 npm
倉庫中這個包的最新版本。如果要指定版本的,可以把版本號寫在包名後面,比如 npm i [email protected] -S
。
從 npm 5.x
開始,可以不用手動添加 -S/--save
指令,直接執行 npm i packageName
把依賴包添加到 dependencies
中去。
"dependencies": { "lodash": "^4.17.13", "moment": "^2.24.0", }
devDependenices
有一些包有可能你只是在開發環境中用到,例如你用於檢測代碼規範的 eslint
,用於進行測試的 jest
,用戶使用你的包時即使不安裝這些依賴也可以正常運行,反而安裝他們會耗費更多的時間和資源,所以你可以把這些依賴添加到 devDependencies
中,這些依賴照樣會在你本地進行 npm install
時被安裝和管理,但是不會被安裝到生產環境:
"devDependencies": { "jest": "^24.3.1", "eslint": "^6.1.0", }
二者簡單對比
-
devDependencies
主要是存放用於本地開發的 -
dependencies
會在我們開發的時候帶到線上 -
-D devDependencies -S dependencies
-
--save-dev
也會添加到devDependencies
-
--save
會添加到dependencies
-
從
npm 5.x
開始,如果什麼參數都不帶,那麼默認添加到dependencies
中
# 添加到 devDependencies npm install -D xxxx # 添加到 dependencies npm install -S xxxx
bin
"bin": { "vm2": "./bin/vm2" },
bin
字段指定了各個內部命令對應的 可執行文件
的位置。如果全局安裝模塊報, npm
會使用符號鏈接把可執行文件鏈接到 /usr/local/bin
,如果項目中安裝,會鏈接到 ./node_modules/.bin/
。
上面的這種當你的包安裝到全局時:npm 會在 /usr/local/bin
下創建一個以 vm2
爲名字的軟鏈接,指向全局安裝下來的 vm2
包下面的 "./bin/index.js"
。這時你在命令行執行 vm2
則會調用鏈接到的這個 js
文件。
main
一個常用的npm包
{ "main": "lib/index.js", }
main
屬性指定程序的主入口文件,其他項目在引用這個 npm
包時,實際上引入的是 lib/index
中暴露出去的模塊。
npm script(本文重點)
npm script
工作中應用到的一個場景,決定看一下原理。
什麼是 npm script 腳本?
在生成的 package.json
文件中,有一個 scripts
對象,在這個對象中, npm
允許使用 scripts
字段定義腳本命令。
"scripts": { "test": "test.js" "build": "tsc", },
scripts
對象中每一個屬性,對應一段腳本。比如, test
命令對應的腳本是 node test.js
。
命令行下使用 npm run
命令,就可以執行這段腳本。
查看當前項目的所有 npm 腳本命令
,可以使用不帶任何參數的 npm run
命令。
原理
我們每次在運行 scripts
中的一個屬性時候( npm run
),**實際系統都會自動新建一個shell(一般是Bash),在這個shell裏面執行指定的腳本命令。因此 凡是能在 shell 中允許的腳本,都可以寫在npm scripts中。
特別的點, npm run 新建的 shell
,會在當前目錄的 node_modules/.bin
子目錄加入到 PATH 變量,執行結束後,再將 PATH 變量恢復原樣。也就是說,當前項目目錄 node——modules/.bin
子目錄中所有的腳本,都可以直接用腳本名稱調用,不需要增加路徑.(簡單總結:通過 npm 啓動的腳本,會默認把 node_modules/.bin
加到 PATH 環境變量中。)
例子
當前項目的依賴裏面有 Mocha,只要直接寫 mocha test
就可以了。
"test": "mocha test"
而不用寫成下面這樣。
"test": "./node_modules/.bin/mocha test"
然後我們就可以直接執行 npm run test
了。 npm
腳本的退出碼,也遵守 Shell 腳本規則。如果退出碼不是0,npm 就認爲這個腳本執行失敗。
這裏有的小夥伴可能會有疑問, node_modules目錄下的.bin文件是哪裏來的 ?我之前也有這樣的疑問,打開了一個 .bin/tsc
,裏面的內容是這樣的
#!/usr/bin/env node require('../lib/tsc.js')
npm install
安裝的某個模塊,如果模塊在 package.json
中配置了 bin
屬性,在安裝時候會自動軟鏈接到 node_modules/.bin
中,舉個例子:如 mocha
源碼 配置了:
{ "name":"mocha", "bin":{ "mocha":"./bin/mocha" } }
腳本默認值
正常情況下, npm
腳本是用戶自己定義。但是 npm
本身對兩個腳本提供了默認值,這兩個腳本不用在 script
屬性中定義,可以直接使用
"start": "node server.js" "install": "node-gyp rebuild"
-
npm run start node server.js server.js
-
npm run install node-gyp rebuild binding.gyp
擴展小知識,本文不重點說, node-gyp
是什麼, binding.gyp
文件是什麼?GYP 是一種構建自動化工具。
-
node gyp node-gyp C++ C++ node-gyp Node
-
Node.js C++ binging.gyp .gyp Python 鍵-值對
看一段簡單的 .gyp 文件,應該好理解一些。
{ "targets": [ { "target_name": "nodecat", "sources": [ "src/nodecat.cc", ], "include_dirs": [ "include" ], "libraries": [ "-lcatclient" ] } ] }
想了解更多詳細的可以看下面的文檔。
binging.gyp 參數說明書/文檔/指南(國外的一篇文檔抄錄,棒!)binging.gyp 參數說明書/文檔/指南
關於 node-gyp
和 binding.gyp
後面會單獨寫一篇文章,這裏先簡單介紹,小夥伴們瞭解下。
鉤子(生命週期)
好多語言或者框架我們學的時候都會考慮到生命週期,其實 package.json
中的 script
也是有生命週期的。 npm
腳本有兩個鉤子, pre
和 post
,當我們執行 start
腳本時候, start
的鉤子就是 prestart
和 poststart
。
當我們執行 npm run start
的時候,npm 會自動按照下面的順序執行
npm run prestart && npm run start && npm run poststart
那這個鉤子有什麼用呢,在實際開發中,我們可以做一些準備或者清理工作,下面是個例子(引用的阮一峯老師文章中的例子)
"clean": "rimraf ./dist && mkdir dist", "prebuild": "npm run clean", "build": "cross-env NODE_ENV=production webpack"
鉤子好用,但是不可亂用,舉個開發過程中遇到的坑,有一次想設置運行時的環境變量,當時想優雅一點,就在 prestart
裏面設置了一個環境變量,但是在項目 start
的時候,無法拿到設置的環境變量,因爲 script
的屬性運行的時候都會新啓動一個 shell
,所以在 prestart
中設置的環境變量只對應了那個 shell
的運行時。
env 環境變量
我們在執行 npm run
腳本時候, npm
會設置一些特殊的env環境變量。其中 package.json
中的所有字段,都會被設置爲以 npm_package_
開頭的環境變量。看個簡單的例子
{ "name": "npm-demo", "version": "1.0.0", "script": { "build": "webpack --mode=production" }, "files": ["src"] }
可以得到 npm_package_name、npm_package_version、npm_package_script_build、npm_package_files_0
等變量。注意上面 package.json 中對象和數組中每個字段都會有對應的環境變量。
同時, npm
相關的所有配置也會被設置爲以 npm_config_
開頭的環境變量。此外,還會設置一個比較特殊的環境變量 npm_lifecycle_event
,表示正在運行的腳本名稱。比如執行 npm run serve
的時候, process.env.npm_lifecycle_event
值爲 serve
,通過判斷這個變量,可以將一個腳本使用在不同的 npm scripts
中。這裏還要提一下上面說的鉤子, npm_lifecycle_event
可以和鉤子配合使用,利用這個變量,在同一個腳本文件裏面,爲不同的 npm scripts
命令編寫代碼。請看下面的例子。
const TARGET = process.env.npm_lifecycle_event; if (TARGET === 'service') { console.log(`Running the service task!`); } if (TARGET === 'preservice') { console.log(`Running the preservice task!`); } if (TARGET === 'postservice') { console.log(`Running the postservice task!`); }
強調:這些環境變量只能在 npm run
的腳本執行環境內拿到,正常執行的 node 腳本是獲取不到的。所以,不能直接通過 env $npm_package_name
的形式訪問,但可以在 scripts 中定義腳本 "scripts": {"bundle": "echo $npm_package_name"}
來訪問。
環境變量常用小技巧
-
env 命令可以列出所有環境變量
npm run env
-
在shell腳本中輸出環境變量
echo PATH
-
在 shell 腳本設置環境變量
echo PATH = /usr/local/lib
有的時候我們需要設置的環境變量是相對項目的 再補充一個shell腳本中設置環境變量時候如何拼接相對路徑
echo PATH = ${pwd}/lib/include //使用${},也可以直接使用雙引號
腳本傳入參數
說到腳本傳入參數,需要再次提到前面說的 pacakge.json
中的 bin
字段, bin
字段指定了各個內部命令對應的可執行文件的位置。前面已經說了 bin
文件的產生,有了 bin
字段,在安裝這個模塊的時候, node_modules
下面的 .bin/文件夾
下會有對應模塊的文件,和模塊中的文件相同,然後我們就可以通過調用這個文件腳本中的方法傳入參數了。
在我的 node_module
中找到一個簡單 .bin/文件
下的腳本,大家感受一下。
#!/usr/bin/env node 'use strict'; var pkg = require('./package.json'); var osName = require('./'); var argv = process.argv; function help() { console.log([ '', ' ' + pkg.description, '', ' Example', ' os-name', ' OS X Mavericks' ].join('\n')); } if (argv.indexOf('--help') !== -1) { help(); return; } if (argv.indexOf('--version') !== -1) { console.log(pkg.version); return; } console.log(osName());
node
處理 scripts
中的參數,除了 屬性後面的第一個命令,以空格分割的任何字符串(除特別shell語法)都是參數 ,並且都能通過 process.argv
屬性訪問。
process.argv
屬性返回一個數組,數組包含了啓動 node
進程時的命令行參數。第一個元素爲啓動 node
進程的可執行文件的絕對路徑名 process.execPath
,第二個元素爲當前執行的 jacascript
文件路徑。 剩餘的元素爲其他命令行參數。
如下 script
例子
"scripts":{ "serve": "vue-cli-service serve --mode=dev --mobile -config build/example.js" }
當我們執行 npm run server
命令的時候, process.argv
的具體內容爲:
[ '/usr/local/Cellar/node/12.14.1/bin/node', '/Users/mac/Vue-projects/hao-cli/node_modules/.bin/vue-cli-service', 'serve', '--mode=dev', '--mobile', '-config', 'build/example.js']
再列舉幾個傳參可能有的方式
npm run serve --params // 參數params將轉化成process.env.npm_config_params = true npm run serve --params=123 // 參數params將轉化成process.env.npm_config_params = 123 npm run serve -params // 等同於--params參數 npm run serve -- --params // 將--params參數添加到process.env.argv數組中 npm run serve params // 將params參數添加到process.env.argv數組中 npm run serve -- params // 將params參數添加到process.env.argv數組中
對比下 npm install koa2 --save
是不是知道了bin腳本中接收到的 process.env.npm_config_save = true;
我想是這樣的,有興趣的小夥伴去看源碼驗證下。
執行順序
npm
腳本執行多任務分爲兩種情況
-
並行任務(同時的平行執行),使用&符號
$ npm run script1.js & npm run script2.js
-
串行任務(前一個任務成功,才執行下一個任務),使用 && 符號
$ npm run script1.js && npm run script2.js
任意腳本
我們配置的腳本命令,如 "start": "node test.js"
, node test.js
會當做一行代碼傳遞給系統的 shell
去解釋執行。實際使用的 shell
可能會根據系統平臺而不同,類 UNIX
系統裏,如 macOS
或 linux
中指代的是 /bin/sh
, 在 windows
中使用的是 cmd.exe
。原理我們也看了,因爲交給 shell
去解釋執行的,說明配置的腳本可以是任意能夠在 shell
中運行的命令,而不僅僅是 node
腳本或者 js
代碼。如果你的系統裏安裝了 python
(或者說 系統變量 PATH
裏能找到 python
命令),你也可以將 scripts
配置爲 "myscript": "python xxx.py"
npm 配置
npm
的配置操作可以幫助我們預先設定好 npm
對項目的行爲動作,也可以讓我們預先定義好一些配置項以供項目中使用。所以瞭解 npm
的配置機制也是很有必要。
npm config
npm cli 提供了 npm config 命令進行 npm 相關配置,通過 npm config ls -l 可查看 npm 的所有配置,包括默認配置。npm 文檔頁爲每個配置項提供了詳細的說明 https://docs.npmjs.com/misc/config . 修改配置的命令爲 npm config set
-
proxy, https-proxy
: 指定 npm 使用的代理 -
registry npm https://registry.npmjs.org/ Registry
-
package-lock
指定是否默認生成package-lock
文件,建議保持默認 true -
save true/false npm install dependencies npm 5 true
刪除指定的配置項命令爲 npm config delete <key>
.
這裏最常見的一個操作是 npm
太慢,設置淘寶鏡像
npm config set registry https://registry.npm.taobao.org
恢復使用之前的 npm
npm config set registry https://registry.npmjs.org
env 環境變量
如果 env
環境變量中存在以 npm_config_
爲前綴的環境變量,則會被識別爲 npm
的配置屬性。比如在 env
環境變量中設置 npm_config_package_lock
變量:
export npm_config_package_lock=false //修改的是內存中的變量,只對當前終端有效
這時候執行 npm install
, npm
會從環境變量中讀取到這個配置項,從而不會生成 package-lock.json
文件。
查看某個環境變量:echo $NODE_ENV 刪除某個環境變量:unset NODE_ENV
npmrc 文件
除了使用 CLI
的 npm config
命令顯示更改 npm
配置,還可以通過 npmrc
文件直接修改配置。
這樣的 npmrc
文件優先級由高到低包括:
-
工程內配置文件:
/path/to/my/project/.npmrc
-
用戶級配置文件:
~/.npmrc
-
全局配置文件:
$PREFIX/etc/npmrc
(即npm config get globalconfig 輸出的路徑) -
npm內置配置文件:
/path/to/npm/npmrc
很多時候我們在公司內網需要通過代理才能訪問 npm
源,通過這個機制,我們可以方便地在工程跟目錄創建一個 .npmrc
文件來共享需要在團隊間共享的 npm
運行相關配置。比如如果我們在公司內網環境下需通過代理纔可訪問 registry.npmjs.org
源,或需訪問內網的 registry
, 就可以在工作項目下新增 .npmrc
文件並提交代碼庫。
proxy = http://proxy.example.com/ https-proxy = http://proxy.example.com/ registry = http://registry.example.com/
因爲項目級 .npmrc
文件的作用域只在本項目下,所以在非本目錄下,這些配置並不生效。對於使用筆記本工作的開發者,可以很好地隔離公司的工作項目、在家學習研究項目兩種不同的環境。
將這個功能與 ~/.npm-init.js
配置相結合,可以將特定配置的 .npmrc
跟 .gitignore
, README
之類文件一起做到 npm init
腳手架中,進一步減少手動配置。
npm 包發佈
規範的 npm 模塊目錄
一個 node.js 模塊是基於 CommonJS 模塊化規範實現的,嚴格按照 CommonJS 規範,模塊目錄下除了必須包含包描述文件 package.json 以外,還需要包含以下目錄:
-
bin:存放可執行二進制文件的目錄
-
lib:存放js代碼的目錄
-
doc:存放文檔的目錄
-
test:存放單元測試用例代碼的目錄
如何寫好一個模塊的 README 文件
這裏不單獨寫,推薦一篇不錯的討論
https://www.zhihu.com/question/29100816
如何發佈自己的 npm 包
-
先去 npm 註冊個賬號,然後在命令行使用
npm adduser #根據提示輸入用戶名密碼即可
-
使用命令發佈你的包
在推送之前,可以通過配置一個 .npmignore
文件來排除一些文件, 防止大量的垃圾文件推送到 npm
, 規則上和你用的 .gitignore
是一樣的。 .gitignore
文件也可以充當 .npmignore
文件
npm publish
-
發佈成功之後,你就可以像下載安裝其他包一樣使用你自己的開發工具了
npm install koalanpmstudy
關於 npm 包的更新
更新 npm 包也是使用 npm publish 命令發佈,不過必須更改 npm 包的版本號,即 package.json 的 version 字段,否則會報錯,同時我們應該遵 Semver (語義化版本號) 規範,npm 提供了 npm version 給我們升級版本
# 升級補丁版本號 $ npm version patch # 升級小版本號 $ npm version minor # 升級大版本號 $ npm version major
本地開發的 npm 包如何調試
在本地開發的模塊包的時候,可以使用 npm link
調試,將模塊鏈接到對應的運行項目中去,方便地對模塊進行調試和測試。具體使用步驟如下
-
假如我的項目是
koalaNpmStudy
,假如我的 npm 模塊包名稱是npm-ikoala
-
進入到 模塊包
npm-ikoala
目錄中,執行npm link
-
在自己的項目
koalaNpmStudy
中創建連接執行npm link npm-ikoala
-
在自己項目的
node_module
中會看到鏈接過來的模塊包,然後就可以像使用其他的模塊包一樣使用它了。 -
調試結束後可以使用
npm unlink
取消關聯
npm link
主要做了兩件事:
-
npm node /usr/local/lib/node_modules/
-
npm bin node /usr/local/bin/
爲 了方便進行探討和交流,我爲大家建立了一個讀者羣,一起學習,一起進步。
:heart:愛心三連擊
1.看到這裏了就點個在看支持下吧,你的 「在看」 是我創作的動力。
2.關注公衆號 達達前端
, 「每天爲您分享原創或精選文章」 !
3.特殊階段,帶好口罩,做好個人防護。
4.添加微信【xiaoda0423】,拉你進 技術交流羣 一起學習
掃碼關注公衆號,訂閱更多精彩內容。
好文章,我 在看
參考文章
-
https://juejin.im/post/5cb3f1ef5188256d917874ff
-
https://www.ruanyifeng.com/blog/2016/10/npm_scripts.html
-
https://zhuanlan.zhihu.com/p/23493436
-
https://juejin.im/post/5ab3f77df265da2392364341#heading-22
-
https://www.cnblogs.com/nanvann/p/3913880.html