Electron 繼承了來自 Chromium 的多進程架構,這使得此框架在架構上非常相似于一個現(xiàn)代的網頁瀏覽器。 本指南將對 教程 中應用的概念進行拓展。
網頁瀏覽器是個極其復雜的應用程序。 除了顯示網頁內容的主要能力之外,他們還有許多次要的職責,例如:管理眾多窗口 ( 或 標簽頁 ) 和加載第三方擴展。
在早期,瀏覽器通常使用單個進程來處理所有這些功能。 雖然這種模式意味著您打開每個標簽頁的開銷較少,但也同時意味著一個網站的崩潰或無響應會影響到整個瀏覽器。
為了解決這個問題,Chrome 團隊決定讓每個標簽頁在自己的進程中渲染, 從而限制了一個網頁上的有誤或惡意代碼可能導致的對整個應用程序造成的傷害。 然后用單個瀏覽器進程控制這些標簽頁進程,以及整個應用程序的生命周期。 下方來自 Chrome 漫畫 的圖表可視化了此模型:
Electron 應用程序的結構非常相似。 作為應用開發(fā)者,你將控制兩種類型的進程:主進程 和 渲染器進程。 這類似于上文所述的 Chrome 的瀏覽器和渲染器進程。
每個 Electron 應用都有一個單一的主進程,作為應用程序的入口點。 主進程在 Node.js 環(huán)境中運行,這意味著它具有 require
模塊和使用所有 Node.js API 的能力。
主進程的主要目的是使用 ?BrowserWindow
? 模塊創(chuàng)建和管理應用程序窗口。
?BrowserWindow
? 類的每個實例創(chuàng)建一個應用程序窗口,且在單獨的渲染器進程中加載一個網頁。 您可從主進程用 window 的 ?webContent
? 對象與網頁內容進行交互。
const { BrowserWindow } = require('electron')
const win = new BrowserWindow({ width: 800, height: 1500 })
win.loadURL('https://github.com')
const contents = win.webContents
console.log(contents)
注意:渲染器進程也是為 web 嵌入 而被創(chuàng)建的,例如 ?
BrowserView
? 模塊。 嵌入式網頁內容也可訪問 ?webContents
? 對象。
由于 BrowserWindow
模塊是一個 EventEmitter
, 所以您也可以為各種用戶事件 ( 例如,最小化 或 最大化您的窗口 ) 添加處理程序。
當一個 BrowserWindow
實例被銷毀時,與其相應的渲染器進程也會被終止。
主進程還能通過 Electron 的 ?app
? 模塊來控制您應用程序的生命周期。 該模塊提供了一整套的事件和方法,可以讓您用來添加自定義的應用程序行為 (例如:以編程方式退出您的應用程序、修改應用程序塢,或顯示一個關于面板) 。
這是一個實際的例子,這個app來源于 快速入門,用 app
API 創(chuàng)建了一個更原生的應用程序窗口體驗。
// quitting the app when no windows are open on non-macOS platforms
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') app.quit()
})
為了使 Electron 的功能不僅僅限于對網頁內容的封裝,主進程也添加了自定義的 API 來與用戶的作業(yè)系統(tǒng)進行交互。 Electron 有著多種控制原生桌面功能的模塊,例如菜單、對話框以及托盤圖標。
關于 Electron 主進程模塊的完整列表,請參閱我們的 API 文檔。
每個 Electron 應用都會為每個打開的 BrowserWindow
( 與每個網頁嵌入 ) 生成一個單獨的渲染器進程。 洽如其名,渲染器負責 渲染 網頁內容。 所以實際上,運行于渲染器進程中的代碼是須遵照網頁標準的 (至少就目前使用的 Chromium 而言是如此) 。
因此,一個瀏覽器窗口中的所有的用戶界面和應用功能,都應與您在網頁開發(fā)上使用相同的工具和規(guī)范來進行攥寫。
雖然解釋每一個網頁規(guī)范超出了本指南的范圍,但您最起碼要知道的是:
<script>
? 元素可添加可執(zhí)行的 JavaScript 代碼。此外,這也意味著渲染器無權直接訪問 require
或其他 Node.js API。 為了在渲染器中直接包含 NPM 模塊,您必須使用與在 web 開發(fā)時相同的打包工具 (例如 webpack
或 parcel
)
:::警告
為了方便開發(fā),可以用完整的 Node.js 環(huán)境生成渲染器進程。 在歷史上,這是默認的,但由于安全原因,這一功能已被禁用。
:::
此刻,您或許會好奇:既然這些特性只能由主進程訪問,那渲染器進程用戶界面怎樣才能與 Node.js 和 Electron 的原生桌面功能進行交互。 而事實上,確實沒有直接導入 Electron 內容腳本的方法。
預加載(preload)腳本包含了那些執(zhí)行于渲染器進程中,且先于網頁內容開始加載的代碼 。 這些腳本雖運行于渲染器的環(huán)境中,卻因能訪問 Node.js API 而擁有了更多的權限。
預加載腳本可以在 BrowserWindow
構造方法中的 webPreferences
選項里被附加到主進程。
const { BrowserWindow } = require('electron')
//...
const win = new BrowserWindow({
webPreferences: {
preload: 'path/to/preload.js',
},
})
//...
因為預加載腳本與瀏覽器共享同一個全局 Window
接口,并且可以訪問 Node.js API,所以它通過在全局 window
中暴露任意 API 來增強渲染器,以便你的網頁內容使用。
雖然預加載腳本與其所附著的渲染器在共享著一個全局 window
對象,但您并不能從中直接附加任何變動到 window
之上,因為 上下文隔離 是默認的。
window.myAPI = {
desktop: true,
}
console.log(window.myAPI)
// => undefined
語境隔離(Context Isolation)意味著預加載腳本與渲染器的主要運行環(huán)境是隔離開來的,以避免泄漏任何具特權的 API 到您的網頁內容代碼中。
取而代之,我們將使用 ?contextBridge
? 模塊來安全地實現(xiàn)交互:
const { contextBridge } = require('electron')
contextBridge.exposeInMainWorld('myAPI', {
desktop: true,
})
console.log(window.myAPI)
// => { desktop: true }
此功能對兩個主要目的來說非常有用:
ipcRenderer
? 幫手模塊于渲染器中,您可以使用 進程間通訊 ( inter-process communication, IPC ) 來從渲染器觸發(fā)主進程任務 ( 反之亦然 ) 。window
? 全局變量上添加自定義的屬性,好在 web 客戶端用上僅適用于桌面應用的設計邏輯 。每個 Electron 應用程序都可以使用 UtilityProcess API 從主進程生成多個子進程。實用程序進程在 Node.js 環(huán)境中運行,這意味著它能夠要求模塊并使用所有 Node.js API。實用進程可用于托管例如:不受信任的服務、CPU 密集型任務或容易崩潰的組件,這些組件以前托管在主進程或使用 Node.js child_process.fork API 生成的進程中。實用程序進程與 Node.js child_process 模塊生成的進程之間的主要區(qū)別在于,實用程序進程可以使用 MessagePorts 與渲染器進程建立通信通道。當需要從主進程中 fork 一個子進程時,Electron 應用程序總是更喜歡 UtilityProcess API 而不是 Node.js child_process.fork API。
更多建議: