前端項目中運行 npm run xxx 的時候發生了什么?
大家都知道目前的 node 是捆綁 npm 的。npm 是 node 的依賴管理器,雖然它不是唯一的選擇,我們還有 pnpm/yarn/cnpm/ni 。
但是,的依賴管理器都是在解決 npm 的某個痛點。對於 npm 依賴聲明文件 package.json
本身是基本沒有變化的。
例如我們可以使用 npm run serve
運行某個命令, 也可以使用 yarn serve
運行某個命令。
可以看到在這個地方 yarn 可以省略 run 這個參數。
但是,他們都只是對 package.json
進行解析而已,例如下面的文件,當運行 npm run serve
時,其實就是運行該 json 文件中的 scripts
下的 serve
鍵對應的命令。
{
"name": "h5",
"version": "1.0.7",
"private": true,
"scripts": { "serve": "vue-cli-service serve" },
"dependencies": { "axios": "^0.19.2", "vuex": "^3.4.0" },
"devDependencies": { "node-sass": "^4.12.0" } }
上面說是 命令
只是用於方便理解,例如:
npm run server
# 類似於在命令行運行以下命令
vue-cli-service serve
通過 npm run 與直接運行命令的區別
還是用上面的配置來描述:
{
"scripts": { "serve": "vue-cli-service serve" } }
npm 在運行 vue-cli-service serve
這條命令的時候,會先在當前 node_modules/.bin
下面看有沒有同名的可執行文件,如果有,則使用其運行。
這里我們可以打開這個目錄看看:
如果直接在命令行中運行 vue-cli-service serve
這條命令,是不會從 node_modules 中查找可執行程序的。
運行可執行文件
那么什么叫可執行文件呢?上面的圖中有很多個同名的 vue-cli-service ,到底是運行哪個?
我們先來分析這幾個文件怎么來的?
例如 @vue/cli-service
有以下 package.json
文件,注意 bin 字段,當我們運行 npm i @vue/cli-service
這條命令時,npm 就會在 node_modules/.bin/
目錄中創建好以 vue-cli-service
為名的幾個可執行文件了。
{
"name": "@vue/cli-service",
"version": "4.4.6",
"description": "local service for vue-cli projects",
"main": "lib/Service.js",
"typings": "types/index.d.ts",
"bin": { "vue-cli-service": "bin/vue-cli-service.js" } }
對於可執行
這個定義,每個系統不一樣。在 windows 系統上,可執行文件是通過組策略和環境變量決定的。
使用 set pathext
可以查看 pathext
這個環境變量,他定義了可以作為可執行文件的后綴。
# 查看可執行文件后綴
set pathext
由上面的配置可以發現,我們常見的 exe 也在其中,這個可執行文件在 windows 上,在命令行中輸入文件名或雙擊時即可以運行。
可以查看這個短視頻窺探一波。
在 unix 系統上面,是通過設置文件的屬性為可執行,再在文件中的第一行聲明解釋器來運行的。
如果我們在 cmd 里運行的時候,windows 一般是調用了 vue-cli-service.cmd
這個文件,這是 windows 下的批處理腳本:
@ECHO off
SETLOCAL
CALL :find_dp0
SET _maybeQuote="
IF EXIST "%dp0%\node.exe" (
SET "_prog=%dp0%\node.exe"
) ELSE (
SET _maybeQuote=
SET "_prog=node"
SET PATHEXT=%PATHEXT:;.JS;=;%
)
%_maybeQuote%%_prog%%_maybeQuote% "%dp0%\..\_@vue_cli-service@4.4.6@@vue\cli-service\bin\vue-cli-service.js" %*
ENDLOCAL
EXIT /b %errorlevel%
:find_dp0
SET dp0=%~dp0
EXIT /b
所以當我們運行 vue-cli-service serve
這條命令的時候,就相當於運行 node_modules/.bin/vue-cli-service.cmd serve
。
然后這個腳本會使用 node 去運行 vue-cli-service.js
這個 js 文件,由於 node 中可以使用一系列系統相關的 api ,所以在這個 js 中可以做很多事情,例如讀取並分析運行這條命令的目錄下的文件,根據模板生成文件等。
# unix 系默認的可執行文件,必須輸入完整文件名
vue-cli-service
# windows cmd 中默認的可執行文件,當我們不添加后綴名時,自動根據 pathext 查找文件
vue-cli-service.cmd
# Windows PowerShell 中可執行文件,可以跨平台
vue-cli-service.ps1
這里多提了下,在 windows 中 cmd 腳本使用得比較多,兼容性也較好。 powerShell 雖然比較強大,但他運行命令的方式由於和 cmd 命令有較大不同,這會導致你常常搞不清什么命令應該在什么解釋器里運行。
示例:運行命令的方式不兼容
示例:windows 很多系統會默認禁止此腳本運行,導致 npm 命令運行錯誤
所以如果遇到 powerShell 相關錯誤時建議用 cmd 試試。
注入相關運行時信息
這一節我們通過調試 npm 的源碼來進行說明。
首先我們在 mockm 這個前端接口聯調工具的源碼中先來個 debugger, 注意有從 process.env 中獲取 NPM_CONFIG_REGISTRY 這個環境變量,這是 npm 安裝時可配置的鏡像地址。
然后我們再看一下這個環境變量,在當前系統中是沒有定義的。
讓我們開始調試 mockm package.json 中的 scripts npm run s2
{
"scripts": { "s2": "node run.js remote --config=D:/git2/mockm/server/example/full.mm.config.js", } }
npm run s2
為了節省篇幅,這里直接斷點在關鍵地點:
這是 npm@v6.x 的源碼,可以發現 npm 使用了 npm-lifecycle 這個依賴來運行的子進程調用我們的 run.js
文件。
在通過 spawn 運行 run.js 的時候,同時設置了進程相關的一些信息,這是由 node 原生支持的。
例如剛剛說到的 NPM_CONFIG_REGISTRY 環境變量。
下面把繼續進入下一個斷點, run.js 文件:
可以發現子進程成功獲取了父進程給予的信息。
總結
- 運行
npm run xxx
的時候,npm 會先在當前目錄的 node_modules/.bin 查找要執行的程序,如果找到則運行; - 沒有找到則從全局的 node_modules/.bin 中查找,
npm i -g xxx
就是安裝到到全局目錄; - 如果全局目錄還是沒找到,那么就從 path 環境變量中查找有沒有其他同名的可執行程序。