Electron安裝過程深入解析(讀完此文解決Electron安裝失敗導致的無法啟動,無法打包的問題)

liulun發表於2020-09-30

1. 安裝Electron依賴包

開發者往往通過npm install(或 yarn add)指令完成為Node.js工程安裝依賴包的工作,

安裝Electron也不例外,下面是npmyarn的安裝Electron依賴包的指令:

npm install electron --save-dev
yarn add electron --dev

官方推薦我們把electron依賴包安裝為開發依賴(devDependencies),

這實際上是為了將來製作應用程式安裝包時,

避免把electron包和其可執行檔案包裝兩次。這部分內容後文我們會詳細講解。

如果你希望觀察npm install指令的具體執行細節,可以為其新增兩個引數,如下所示:

npm install electron --save-dev  --timing=true --loglevel=verbose

通過以上指令安裝Electron依賴包,你會觀察到整個安裝過程的執行情況日誌,

這裡我擷取幾個重要的日誌分析一下。

npm http fetch GET 200 https://registry.npm.taobao.org/electron 125ms

這是npm通過http協議獲取electron包的註冊資訊的日誌,

請求上述地址(我用的是npm淘寶源)將得到一個json響應,

json中包含了electron的所有版本的版本資訊,

如果安裝時我們沒有為electron指定版本號,將安裝最新的版本。

> electron@9.2.0 postinstall D:\ElectronDeepDive\capture1\install\node_modules\electron

> node install.js

這是Electron依賴包安裝完成後,npm執行其依賴包內定義的postinstall鉤子的日誌。

npm包管理文件為npm包定義了一系列的鉤子,postinstall鉤子會在npm包安裝完成後被執行,

除了postinstall鉤子之外,常用的還有如下這些鉤子:

  • preinstall包安裝之前執行;
  • postuninstall包被解除安裝之後執行;
  • preuninstall包被解除安裝之前執行;
  • poststartnpm start執行後觸發;
  • poststopnpm stop執行後觸發;
  • posttestnpm test執行後觸發;

詳細的文件請參閱:https://docs.npmjs.com/misc/scripts

postinstall鉤子定義在Electron包內的package.json中,程式碼如下:

"scripts": {

  "postinstall": "node install.js"

}

install.js程式是Electron包內的一個重要程式,用於下載Electron的可執行檔案及相關資源,下一小節我們將講解Electron可執行檔案的下載過程。

2. 下載Electron的二進位制檔案

install.js中,程式獲取了當前作業系統的版本,並通過如下程式碼下載Electron的二進位制檔案與相應的資源:

downloadArtifact({

  version,

  artifactName: 'electron',

  force: process.env.force_no_cache === 'true',

  cacheRoot: process.env.electron_config_cache,

  platform: process.env.npm_config_platform || process.platform,

  arch: process.env.npm_config_arch || process.arch

}).then(extractFile).catch(err => {

  console.error(err.stack)

  process.exit(1)

})

downloadArtifact方法是@electron/get包提供的,這個包是Electron包依賴的一個npm包,

由於自npm 3.x以來,npm把包管理方式從巢狀結構切換到了扁平結構,所以@electron/get位於當前工程的node_modules目錄的根目錄下。

擴充:在npm 3.x以前,npm的包管理方式是巢狀結構的,

也就是說一個工程安裝的依賴包位於當前工程根目錄下的node_modules目錄中,

假設其中一個依賴包又依賴了其他npm包,我們假設這個依賴包叫做packageA

那麼它的依賴包會被安裝在packageA目錄的node_modules目錄下,

以此類推。以這種方式管理依賴包會導致目錄層級很深,

Windows作業系統中,檔案路徑最大長度為260個字元,目錄層級過深會導致依賴包安裝不成功。

並且不同層級的依賴中可能引用了同一個依賴包,

這種結構也沒辦法複用這個依賴包,而且這種情況非常常見,造成了大量的冗餘、浪費。

npm 3.x以來,npm的包管理方式升級為了扁平解構,

無論是當前工程的依賴包還是依賴包的依賴包,都會被優先安裝到當前工程的node_modules目錄下,

在安裝過程中如果npm發現當前工程的node_modules目錄下已經存在了相同版本的某個依賴包,

那麼就會跳過安裝過程,直接讓工程使用這個已安裝的依賴包,只有在版本不同的情況下,

才會在這個包的node_modules目錄下安裝新的依賴包。

這就很好的解決了前面兩個問題。但也引來了新的問題,

直到npm 5.x引入了package lock的機制後,才解決了新的問題,這已超出了本書的討論範圍,

詳情請參閱:https://docs.npmjs.com/configuring-npm/package-lock-json.html

另外,npm判斷兩個依賴包是否版本相同,是有一套複雜的規則的,

這也超出了本書的討論範圍,詳情請參閱:https://docs.npmjs.com/about-semantic-versioning

downloadArtifact方法的入參是一個配置物件,

物件的force屬性標記著是否需要強制下載Electron的二進位制檔案,

如果環境變數force_no_cache的值為"true"則無論本地有沒有快取,都會從Electron的伺服器下載相應的檔案。

配置物件的version屬性是需要下載的Electron可執行程式的版本號,

這個版本號就是定義在Electron npm包的package.json內的版本號。

platform屬性是當前的作業系統的名稱,可能的值為"darwin""win32""linux"等,

arch是你當前作業系統的架構,可能的值為"x32""x64"

這些資訊都是幫你確定下載什麼版本的Electron可執行檔案的。

上述資訊最終被組裝成的下載地址可能是如下的樣子(其中版本號視真實情況而定):

https://github.com/electron/electron/releases/download/v9.2.0/electron-v9.2.0-win32-x64.zip

如果處於windows作業系統內,上述檔案會被首先下載到如下目錄中:

C:\Users\ADMINI~1\AppData\Local\Temp

這個目錄是Node.js通過os.tmpdir()確定的。檔案下載完成後,

程式會把它複製到快取目錄中以備下次使用,這個機制極大的節省了開發者的時間成本,

下一小節我們將深入講解Electron安裝過程中的快取和映象機制。

快取完成後,上述程式碼中的extractFile回撥方法被執行,

此方法會把快取目錄下的二進位制檔案壓縮包解壓到當前Electron依賴包的dist目錄下:

[project]\node_modules\electron\dist

除了下載Electron二進位制檔案的壓縮包外,downloadArtifact還單獨下載了一個SHASUMS256.txt檔案,

這個檔案內記錄了Electron二進位制檔案壓縮包的sha256值,

程式會對比一下這個值與壓縮包檔案的sha256值是否匹配,

以避免使用者請求被截獲,下載到不安全的檔案的情況(這方面的效用只能說聊勝於無),

或者是下載過程意外終止,檔案資料不完整的情況。

3. 快取與映象策略

上文中我們提到Electron的二進位制檔案壓縮包下載成功後,會複製一份到快取目錄,以備下次使用。在Windows環境下,預設的快取目錄為:

C:\Users\Administrator\AppData\Local\electron\Cache

這是通過Node.jsos.homedir()再附加了幾個子目錄確定的。

你可以通過設定electron_config_cache環境變數來提供使用者自定義快取目錄,在命令列下臨時設定這個環境變數的方式為:

> set electron_config_cache=D:\ElectronDeepDive\capture1\cache

 如果你是通過程式設計的方式使用@electron/get包,那麼也可以通過如下方式把環境變數的設定寫到程式碼裡:

process.env.electron_config_cache="D:\\ElectronDeepDive\\capture1\\cache"

如果你希望一勞永逸的解決這個問題,還可以把這個環境變數配置到作業系統中去,如下圖所示:

 

1-1 Electron快取目錄環境變數設定

在國內網路環境不理想的情況下,安裝Electron npm包十有八九會失敗,

這就是Electron的二進位制檔案壓縮包難以下載成功導致的,

知道了快取目錄的位置之後,你就可以先手動把Electron二進位制包安放到相應的快取目錄中,

這樣再安裝Electron npm包時就毫無阻滯了。

你可以從同事的電腦上拷貝相應版本的Electron二進位制包,

也可以從淘寶的映象源手動下載Electron的二進位制包,淘寶Electron映象源的地址為:

https://npm.taobao.org/mirrors/electron/

下載好的壓縮包和雜湊值檔案一定要按照如下路徑放置在快取目錄裡:

//二進位制包檔案的路徑

[你的快取目錄]/httpsgithub.comelectronelectronreleasesdownloadv9.2.0electron-v9.2.0-win32-x64.zip/electron-v9.2.0-win32-x64.zip

//雜湊值檔案的路徑

[你的快取目錄]/httpsgithub.comelectronelectronreleasesdownloadv9.2.0SHASUMS256.txt/SHASUMS256.txt

路徑中[你的快取目錄]下的子目錄的命名方式看起來有些奇怪,

這其實就是下載地址格式化得來的(通過一個叫做sanitize-filename的工具庫,

去除了url路徑中的斜槓,使得其能成為檔案路徑),

在我的電腦上,這兩個路徑是如下形式:

 

 

 

1-2 二進位制包檔案的路徑

 

 

 

1-3 雜湊值檔案的路徑

細心的讀者可能已經注意到了,我的路徑並不是githuburl地址格式化得來的,

而是taobao的映象源地址格式化得來的。下面我們就介紹一下如何設定第三方映象源。

@electron/get庫下載Electron二進位制檔案包的地址被人為的分割成了三部分:

  • 映象部分:https://github.com/electron/electron/releases/download/
  • 版本部分:v9.2.0/
  • 檔案部分:electron-v9.2.0-win32-x64.zip

這三部分聯合起來最終構成了下載地址,每個部分都有其預設值,也有對應的重寫該部分值的環境變數:

  • 映象部分的環境變數:ELECTRON_MIRROR
  • 版本部分的環境變數:ELECTRON_CUSTOM_DIR
  • 檔案部分的環境變數:ELECTRON_CUSTOM_FILENAME

一般情況下,我們只需要設定映象部分的環境變數即可,

比如要設定淘寶的映象源,

只需要把ELECTRON_MIRROR的環境變數的值設定為https://npm.taobao.org/mirrors/electron/即可,

設定方式與設定快取目錄的環境變數方式相同,此處不再贅述。

4. bin目錄下注入命令

Electron依賴包安裝完成後,

npm會自動為其在node_modules/.bin路徑下注入命令檔案,

不帶副檔名的electron檔案是為linuxmac準備的shell指令碼,

electron.cmd是傳統的windows批處理指令碼,

electron.ps1是執行在windows powershell下的指令碼。

 命令檔案中的指令碼程式碼不多,以electron.cmd為例,我們簡單解釋一下:

@ECHO off

SETLOCAL

CALL :find_dp0

IF EXIST "%dp0%\node.exe" (

  SET "_prog=%dp0%\node.exe"

) ELSE (

  SET "_prog=node"

  SET PATHEXT=%PATHEXT:;.JS;=;%

)

"%_prog%"  "%dp0%\..\electron\cli.js" %*

ENDLOCAL

EXIT /b %errorlevel%

:find_dp0

SET dp0=%~dp0

EXIT /b

其中~dp0指執行指令碼的當前目錄,

SET是為一個變數賦值,

%*是執行命令時輸入的引數,

整段命令指令碼的意思是用node執行Electron包內的cli.js檔案,

並把所有命令列引數一併傳遞過去。

關於windows批處理的更多細節請參閱https://docs.microsoft.com/en-us/windows-server/administration/windows-commands/windows-commands

細心的讀者會發現,npm並不會為所有的依賴包注入命令檔案,

而且即使注入了命令檔案的包也不一定存在cli.js檔案,

比如npm就沒有為core-js包注入命令檔案,卻為Mocha注入了兩組命令檔案,

Electron或者Mocha的獨特之處在於它們的package.json裡都有類似如下這樣的配置(Mochabin物件配置了兩個屬性,所以npm為其生成了兩組指令檔案):

"bin": {

  "electron": "cli.js"

}

npm之所以在node_modules/.bin路徑下新增命令檔案,

是因為很多包的作者都希望自己的指令碼能放置在使用者的環境變數裡。

npm為這個需求提供了便利。npm在執行一段指令碼前,

比如:npm run dev,會先自動新建一個命令列環境,然後把當前目錄的node_modules/.bin加入到系統環境變數中,

接著執行scripts配置節指定的指令碼的內容,

執行完成後再把node_modules/.bin從系統環境變數中刪除。

所以當前目錄的node_modules/.bin子目錄裡面的所有指令碼,

都可以直接用指令碼名呼叫,而不必加上路徑。

當然你的專案的package.json裡要配置了dev對應的指令,示例的配置程式碼如下:

"scripts": { "dev": "electron ./index.js" }

有了上面的配置,你就可以通過執行npm run dev命令來啟動你安裝過的Electron了。

下面我們就來看看Electron包內的cli.js是如何啟動Electron的。

5. 使用命令啟動Electron

當開發者在當前專案下執行npm run dev時,其實就是執行electron.cmd批處理檔案,

並傳入了一個命令列引數./index.js

(這個檔案我們還沒有建立,不過沒關係,在這一小節裡這個檔案並不是重點)。

我們知道electron.cmd批處理指令就是用node執行了node_modules\electron\cli.js檔案,

同時也把命令列引數複製過去了。那麼我們就看看cli.js的執行邏輯。

cli.js中最重要的邏輯程式碼如下(為了便於理解,我對這段程式碼略有改動):

var proc = require('child_process')

var child = proc.spawn(electronExePath, process.argv.slice(2), {

stdio: 'inherit',

windowsHide: false

})

這段程式碼就是使用Node.jschild_process物件建立了一個子程式,

讓子程式執行Electron的可執行檔案,並把當前程式的命令列引數傳遞給了這個子程式。

命令列引數之所以從第三位開始取,是因為按照Node.js的約定,process.argv的第一個值為process.execPath

第二個值為正被執行的 JavaScript 檔案的路徑,所以第三個值才是我們需要的./index.js

值得注意的是cli.js檔案的首行程式碼:

#!/usr/bin/env node

這行程式碼是一個Shebang行(https://en.wikipedia.org/wiki/Shebang_(Unix)),

是類Unix平臺上的可執行純文字檔案中的第一行,

通過#!字首後面的命令列告訴系統將該檔案傳遞給哪個直譯器以供執行。

雖然Windows不支援Shebang行,但因為這是npm的約定,所以這一行程式碼仍然是必不可少的。

至於Electron的可執行程式是如何接收這個引數,如何執行這個引數指向的程式檔案的,我們後文會有詳細描述。

6. Electron的版本管理方式

Electron 2.0.0以來,Electron的版本管理方式遵循semver的管理規則,

semver是 語義化版本規範(https://semver.org/lang/zh-CN/)的一個實現,

這是一個由npm的團隊維護的版本管理規範,它實現了版本和版本範圍的解析、計算、比較。

semver的版本號內容分為主版本號次版本號修訂號三個部分,中間以點號分割,

版本號遞增規則如下:

  • 主版本號:當做了不相容修改時遞增
  • 次版本號:當做了向下相容的功能性更新時遞增
  • 修訂號:當做了向下相容的問題修正時遞增

Electron則在這個約束的前提下增加了如下遞增規則:

主版本號更新規則

次版本號更新規則

修訂號更新規則

Electron有不相容的修改時遞增

Electron相容性更新時遞增

Electron問題修復時遞增

Node.js主版本號更新時遞增

Node.js次版本號更新時遞增

Node.js修訂版本號更新時遞增

Chromium更新時遞增

 

為Chromium打補丁時更新

推薦大家使用穩定狀態的最新版本的Electron

如果已經安裝了老版本的Electron或者發現Electron有可用的更新

(關注Electron官網的釋出頁面:https://www.electronjs.org/releases/stable可獲得更新資訊),

大家可以使用如下指令更新本地工程的Electron版本:

npm install --save-dev electron@latest

Electron團隊承諾只維護最近的三個大版本,

比如本文發稿時Electron最新版本為v9.2.0

那麼Electron團隊只會維護v9.x.xv8.x.xv7.x.xv6.x.x則不再維護,當v10.x.x釋出之後,v7.x.x也不再維護了。

而且目前Electron版本釋出相當頻繁,平均一到兩週就會有一個新的穩定版本釋出,

大量的更新不僅僅帶來了更多的新功能、解決了更多的問題,

也意味著你所使用的版本即將成為無人理睬的版本了,這也是為什麼我推薦大家緊跟官方團隊版本釋出步伐的原因。

我們通過npm包管理工具安裝的Electron依賴包都是穩定版本,

除穩定版本外,Electron團隊還維護著beta版本和nightly版本,

這是Electron團隊和一些激進的開發者的演武場,除非特別需要,不推薦在商業專案中使用這些版本。

另外Electron官方github倉儲的issue頁面(https://github.com/electron/electron/issues)也時常會置頂一些重要更新事項,

與社群的開發者一起討論更新方案的細節,等最終方案敲定後,則逐步推動更新落地。

比如近期非常重要的移除remote模組的更新需求,就是置頂在這個頁面的。

這是開發者持續關注官方動向的最佳途徑,

一旦發現有不贊成(deprecate)的內容或破壞性(breaking)的更新被置頂在這個頁面,

就應該儘量在專案當中避免使用它們。

 

最後,推薦一下我自己的新書《Electron實戰》

http://product.dangdang.com/28547952.html

 

相關文章