macOS下由yarn與npm差異引發的Electron映象地址讀取問題

2023-05-23 18:00:42

記錄macOS下由yarn與npm差異引發的Electron映象地址讀取問題

寫在前面:該問題僅僅出現在Linux和macOS上,Windows上不存在該問題!

初始背景

最近筆者重新拾起了Electron,把最新版Electron的官方檔案閱讀了一遍。眾所周知,Electron作為依賴在安裝的時候,其二進位制檔案下載在國內一直以來都是問題(因為預設會從github上下載),好在現在Electron的官方檔案已經寫的非常詳細了:安裝指導 | Electron (electronjs.org),只需要設定一個映象地址到.npmrc中:

ELECTRON_MIRROR="https://npmmirror.com/mirrors/electron/"

記住這個大寫的Key

筆者由於是新的機器,還沒有設定改值,所以找到.npmrc檔案的設定了上述的映象後,便開開心心的準備進行專案搭建了。

問題出現

然而,當筆者準備使用yarn執行如下命令的時候,卻出了問題:

yarn add -D electron

執行啟動以後,在Electron安裝的環境一直卡住了很久很久。

咦,難道映象設定寫錯了嗎?仔細對比以後,沒有問題。難道因為我的網路存取很慢嗎?等到存取超時以後,發現一個IP地址超時了,心想國內映象再怎麼也不應該超時,盲猜映象地址沒有生效。於是乎,準備嘗試對下載Electron二進位制檔案的過程進行debug。

問題排查

首先定位到node_module/electron包,能夠看到有一段安裝後指令碼執行命令(postinstall):

關於postinstall的詳細說明:scripts | npm Docs (npmjs.com)

也就是說,node_module/electron本身npm包install完成以後,還會執行其包內的install.js。

定位進入了node_module/electron包下的install.js,該指令碼內部主要邏輯是先檢查Electron的二進位制快取,如果不存在快取,則使用來自@electron/get包中提供的downloadArtifact方法從遠端下載Electron二進位制製品檔案。

我們暫時先不看快取讀寫的邏輯,著重瞭解遠端下載的邏輯,所以我們進入@electron/get包中的downloadArtifact

檢視@electron/get包下的index.js內容:

前面我們提到,懷疑映象地址沒有生效導致下載超時,所以我們重點關注一下這裡通過getArtifactRemoteURL方法得到的url值,

由於每一次這個包都會重新安裝,我們不太好偵錯這個值,所以,我們做一個簡單的trick:

  1. 找到這個包的快取(macOS上的路徑為:~/Library/Caches/Yarn/v6/npm-@electron-get-xxxx):

  1. 找到上述indexjs程式碼,並新增一段紀錄檔列印:

  1. 準備完畢以後,我們重新在demo專案下執行yarn add -D electron。執行以後,等到超時以後,發現控制檯紀錄檔列印如下:

Why!?為什麼這個下載的Electron二進位制檔案地址依然是github的?於是,我們有必要進一步檢視這個URL是如何得到。

繼續檢視程式碼,這個url來源於artifact-utils中的getArtifactRemoteURL方法,而這個方法裡面關於最終返回的url最重要的部分是下圖所示的base的值:

而這個base值來源於mirrorVar這個方法:

根據上面程式碼的邏輯,name值為"mirror",options未使用,defaultValue為:

"https://github.com/electron/electron/releases/download/"

也就是說,在後面的邏輯中,如果沒有從process.env中找到對應的值,那麼就會使用預設的github官方製品地址的值。按照程式碼邏輯,執行到這個方法的時候,會從process.env中嘗試獲取:

  1. "NPM_CONFIG_ELECTRON_MIRROR"
  2. "npm_config_electron_mirror"
  3. "npm_package_config_electron_mirror"
  4. "ELECTRON_MIRROR"

環境變數—— 設定 | npm 中文網 (nodejs.cn)

任何以 npm_config_ 開頭的環境變數都將被解釋為設定引數。 例如,將 npm_config_foo=bar 放入您的環境中會將 foo 設定引數設定為 bar。 任何未賦值的環境設定都將被賦值為 true。 設定值不區分大小寫,因此 NPM_CONFIG_FOO=bar 的工作方式相同。 但是,請注意,在 scripts 內部,npm 將設定自己的環境變數,並且 Node 會更喜歡那些小寫版本,而不是您可能設定的任何大寫版本。 詳情見此問題

請注意,您需要使用下劃線而不是破折號,因此 --allow-same-version 將變為 npm_config_allow_same_version=true

此外,如果是設定在npmrc裡面的設定,也會在npm/yarn啟動的時候被作為環境變數放到process.env中被存取。

那我們在.npmrc中設定的ELECTRON_MIRROR,在process.env中變成了什麼呢?通過新增紀錄檔列印,我們會看到:

可以看到,在process.env中,這個鍵為"npm_config_ELECTRON_MIRROR"npm_config小寫,ELECTORN_MIRROR大寫)。我們知道,nodejs中object物件的屬性值是大小寫敏感的!所以,當上面的mirrorVar程式碼執行,嘗試獲取process.env中的值的時候,根本找不到了,因為沒有"NPM_CONFIG_ELECTRON_MIRROR""npm_config_electron_mirror""npm_package_config_electron_mirror""ELECTRON_MIRROR"這些屬性。

然而,如果我們使用npm進行安裝的時候:

npm install -D electron

又能夠很快安裝。Why?!難道npm和yarn下的執行環境有差異嗎?為了驗證,我們編寫一個簡單的index.js程式碼:

console.log("process.env['npm_config_electron_mirror']", process.env['npm_config_electron_mirror']);
console.log("process.env['NPM_CONFIG_ELECTRON_MIRROR']", process.env['NPM_CONFIG_ELECTRON_MIRROR']);
console.log("process.env['npm_config_ELECTRON_MIRROR']", process.env['npm_config_ELECTRON_MIRROR']);

然後,在package.json中新增指令碼:

{
  "name": "simple-electron-main-app",
  "version": "1.0.0",
  "scripts": {
+   "start": "node index.js"
  },
  "devDependencies": {}
}

最後,我們分別使用yarn(yarn start)和npm(npm run start)來執行指令碼:

在yarn執行上下文中,.npmrc中的"ELECTRON_MIRROR"直接拼接到了"npm_config_"後邊,作為process.env的一個屬性,所以你只能存取process.env["npm_config_ELECTRON_MIRROR"]得到值;

在npm執行山下文中,.npmrc中的"ELECTRON_MIRROR"首先被轉為了小寫,然後拼接到了"npm_config_"後邊,作為了process.env的屬性,所以你需要存取process.env["npm_config_electron_mirror"]來得到值。

macOS解決方式

終於,我們能解釋為什麼當我們在.npmrc設定大寫的ELECTRON_MIRROR的時候,使用yarn add -D electron安裝electron的時候,二進位制映象地址沒有生效了。那麼,解決的辦法也非常簡單,兩種:

  1. .npmrc設定改為小寫key:electron_mirror="https://npmmirror.com/mirrors/electron/"
  2. 使用npm上下文環境進行安裝。

個人更加建議按照第一種方式設定,不然大小寫敏感的坑太容易發生了。

關於Windows的特別說明

process.env | Node.js API 檔案 (nodejs.cn)

在 Windows 作業系統上,環境變數不區分大小寫。

const { env } = require('node:process');

env.TEST = 1;
console.log(env.test);
// => 1

也就是說,在Windows機器上,即使process.env中的key為"npm_config_ELECTRON_MIRROR",你也可以通過"npm_config_electron_mirror"或者是"NPM_CONFIG_ELECTRON_MIRROR"來存取這個值: