跳到主要內容

使用預載腳本

學習目標

在本教學的這部分,您將學習預載腳本是什麼,以及如何使用它來安全地將特權 API 暴露到渲染器程序中。您還將學習如何使用 Electron 的跨程序通訊 (IPC) 模組在主程序和渲染器程序之間進行通訊。

什麼是預載腳本?

Electron 的主程序是一個 Node.js 環境,具有完整的作業系統存取權限。除了 Electron 模組 之外,您還可以存取 Node.js 內建函式庫,以及透過 npm 安裝的任何套件。另一方面,渲染器程序執行網頁,並且基於安全考量,預設情況下不執行 Node.js。

為了橋接 Electron 的不同程序類型,我們需要使用一種稱為預載的特殊腳本。

使用預載腳本擴增渲染器

BrowserWindow 的預載腳本在一個可以存取 HTML DOM 和 Node.js 與 Electron API 的有限子集的上下文中執行。

預載腳本沙箱

從 Electron 20 開始,預載腳本預設為沙箱化,並且不再具有完整 Node.js 環境的存取權限。實際上,這表示您有一個 polyfilled 的 require 函式,該函式僅可存取一組有限的 API。

可用的 API詳細資訊
Electron 模組渲染器程序模組
Node.js 模組eventstimersurl
Polyfilled 全域變數BufferprocessclearImmediatesetImmediate

有關更多資訊,請查看程序沙箱指南。

預載腳本在網頁載入到渲染器之前注入,類似於 Chrome 擴充功能的 內容腳本。若要將需要特權存取權限的功能新增到渲染器,您可以透過 contextBridge API 定義全域物件。

為了示範這個概念,您將建立一個預載腳本,將應用程式的 Chrome、Node 和 Electron 版本暴露到渲染器中。

新增一個 preload.js 腳本,將 Electron 的 process.versions 物件的選定屬性暴露到渲染器程序中的 versions 全域變數。

preload.js
const { contextBridge } = require('electron')

contextBridge.exposeInMainWorld('versions', {
node: () => process.versions.node,
chrome: () => process.versions.chrome,
electron: () => process.versions.electron
// we can also expose variables, not just functions
})

若要將此腳本附加到您的渲染器程序,請將其路徑傳遞給 BrowserWindow 建構函式中的 webPreferences.preload 選項

main.js
const { app, BrowserWindow } = require('electron')
const path = require('node:path')

const createWindow = () => {
const win = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
preload: path.join(__dirname, 'preload.js')
}
})

win.loadFile('index.html')
}

app.whenReady().then(() => {
createWindow()
})
資訊

這裡使用了兩個 Node.js 概念

  • __dirname 字串指向目前正在執行的腳本的路徑(在本例中為專案的根資料夾)。
  • path.join API 將多個路徑區段連接在一起,建立一個跨所有平台皆可運作的組合路徑字串。

此時,渲染器可以存取 versions 全域變數,因此讓我們在視窗中顯示該資訊。可以透過 window.versions 或直接使用 versions 來存取此變數。建立一個 renderer.js 腳本,使用 document.getElementById DOM API 來替換 id 屬性為 info 的 HTML 元素的顯示文字。

renderer.js
const information = document.getElementById('info')
information.innerText = `This app is using Chrome (v${versions.chrome()}), Node.js (v${versions.node()}), and Electron (v${versions.electron()})`

然後,修改您的 index.html,新增一個 id 屬性為 info 的新元素,並附加您的 renderer.js 腳本

index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<meta
http-equiv="Content-Security-Policy"
content="default-src 'self'; script-src 'self'"
/>
<meta
http-equiv="X-Content-Security-Policy"
content="default-src 'self'; script-src 'self'"
/>
<title>Hello from Electron renderer!</title>
</head>
<body>
<h1>Hello from Electron renderer!</h1>
<p>👋</p>
<p id="info"></p>
</body>
<script src="./renderer.js"></script>
</html>

完成上述步驟後,您的應用程式應該看起來像這樣

Electron app showing This app is using Chrome (v102.0.5005.63), Node.js (v16.14.2), and Electron (v19.0.3)

程式碼應該看起來像這樣

const { app, BrowserWindow } = require('electron/main')
const path = require('node:path')

const createWindow = () => {
const win = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
preload: path.join(__dirname, 'preload.js')
}
})

win.loadFile('index.html')
}

app.whenReady().then(() => {
createWindow()

app.on('activate', () => {
if (BrowserWindow.getAllWindows().length === 0) {
createWindow()
}
})
})

app.on('window-all-closed', () => {
if (process.platform !== 'darwin') {
app.quit()
}
})

程序間通訊

如上所述,Electron 的主程序和渲染器程序具有不同的職責,並且不可互換。這表示無法從渲染器程序直接存取 Node.js API,也無法從主程序存取 HTML 文件物件模型 (DOM)。

這個問題的解決方案是使用 Electron 的 ipcMainipcRenderer 模組進行跨程序通訊 (IPC)。若要從您的網頁將訊息傳送到主程序,您可以設定一個 ipcMain.handle 的主程序處理程序,然後暴露一個呼叫 ipcRenderer.invoke 的函式,以觸發預載腳本中的處理程序。

為了說明,我們將在渲染器中新增一個名為 ping() 的全域函式,該函式將從主程序傳回一個字串。

首先,在您的預載腳本中設定 invoke 呼叫

preload.js
const { contextBridge, ipcRenderer } = require('electron')

contextBridge.exposeInMainWorld('versions', {
node: () => process.versions.node,
chrome: () => process.versions.chrome,
electron: () => process.versions.electron,
ping: () => ipcRenderer.invoke('ping')
// we can also expose variables, not just functions
})
IPC 安全性

請注意,我們如何將 ipcRenderer.invoke('ping') 呼叫包裝在輔助函式中,而不是透過 context bridge 直接暴露 ipcRenderer 模組。您絕對不想透過預載直接暴露整個 ipcRenderer 模組。這會讓您的渲染器能夠將任意 IPC 訊息傳送到主程序,這將成為惡意程式碼的強大攻擊媒介。

然後,在主程序中設定您的 handle 監聽器。我們在載入 HTML 檔案之前執行此操作,以確保處理程序在您從渲染器發出 invoke 呼叫之前已準備就緒。

main.js
const { app, BrowserWindow, ipcMain } = require('electron/main')
const path = require('node:path')

const createWindow = () => {
const win = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
preload: path.join(__dirname, 'preload.js')
}
})
win.loadFile('index.html')
}
app.whenReady().then(() => {
ipcMain.handle('ping', () => 'pong')
createWindow()
})

一旦您設定好傳送者和接收者,您現在就可以透過您剛定義的 'ping' 通道,將訊息從渲染器傳送到主程序。

renderer.js
const func = async () => {
const response = await window.versions.ping()
console.log(response) // prints out 'pong'
}

func()
資訊

有關使用 ipcRendereripcMain 模組的更深入說明,請查看完整的跨程序通訊指南。

摘要

預載腳本包含在網頁載入到瀏覽器視窗之前執行的程式碼。它可以存取 DOM API 和 Node.js 環境,並且通常用於透過 contextBridge API 將特權 API 暴露給渲染器。

由於主程序和渲染器程序具有非常不同的職責,因此 Electron 應用程式通常使用預載腳本來設定跨程序通訊 (IPC) 介面,以便在兩種程序之間傳遞任意訊息。

在本教學的下一部分,我們將向您展示有關為應用程式新增更多功能的資源,然後教您如何將應用程式發布給使用者。