Electronチュートリアル(初心者向け)その2 preload.js とは
前回は Electron の最小構成で、アプリを起動するところまでやりました。
今回は preload.js を追加して、renderer から安全に Electron / Node.js 側の機能を使う方法を解説します。preload.js は Web ページの読み込み前に実行され、DOM API と Electron / Node.js 側の一部 API にアクセスできる、橋渡し用のスクリプトです。(Electron)
1. preload.js とは
Electron には大きく分けて 2 つのプロセスがあります。
main processrenderer process
main process はウィンドウ作成や OS 操作を担当し、renderer process は HTML / CSS / JavaScript で画面を描画します。Electron アプリはこの 2 つを使い分ける構成になっています。(Electron)
ただし、renderer から何でも直接 Node.js を触れるわけではありません。
そこで使うのが preload.js です。preload.js は renderer に読み込まれる前に実行される特別なスクリプトで、main と renderer の間を安全につなぐ役割を持ちます。(Electron)
2. なぜ preload.js が必要なのか
Electron では contextIsolation が既定で有効です。
そのため、昔のように preload 側で window.myAPI = ... と書いても、renderer 側からはそのまま見えません。安全に公開したい場合は contextBridge.exposeInMainWorld() を使います。(Electron)
また、Electron 20 以降は preload スクリプトもデフォルトでサンドボックス化されており、完全な Node.js 環境がそのまま見えるわけではありません。必要最小限の API を絞って公開する前提で考えるのが大事です。(Electron)
3. 今回作るもの
今回はボタンを押すと、Electron アプリのバージョンを表示するサンプルを作ります。
流れはこうです。
-
main.jsでバージョンを返す IPC ハンドラを作る -
preload.jsでその処理を呼び出す関数を公開する -
renderer.jsからwindow.desktopAPI.getAppVersion()を呼ぶ
Electron 公式でも、renderer → main の通信は ipcMain.handle() と ipcRenderer.invoke() を preload 経由でつなぐ形が基本例として紹介されています。(Electron)
4. フォルダ構成
前回の構成に preload.js と renderer.js を追加します。
electron-tutorial
├─ package.json
├─ main.js
├─ preload.js
├─ renderer.js
└─ index.html
5. main.js
まずはメインプロセス側です。
const { app, BrowserWindow, ipcMain } = require("electron")
const path = require("node:path")
function 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("app:getVersion", () => {
return app.getVersion()
})
createWindow()
app.on("activate", () => {
if (BrowserWindow.getAllWindows().length === 0) {
createWindow()
}
})
})
app.on("window-all-closed", () => {
if (process.platform !== "darwin") {
app.quit()
}
})
ポイントはここです。
webPreferences: {
preload: path.join(__dirname, "preload.js")
}
これで preload.js を BrowserWindow に紐づけられます。preload はページ内の他のスクリプトより前に読み込まれます。(Electron)
6. preload.js
次に preload 側です。
const { contextBridge, ipcRenderer } = require("electron")
contextBridge.exposeInMainWorld("desktopAPI", {
getAppVersion: () => ipcRenderer.invoke("app:getVersion")
})
ここでは desktopAPI という名前で、renderer 側に API を 1 つだけ公開しています。
renderer 側では次のように使えます。
window.desktopAPI.getAppVersion()
contextBridge.exposeInMainWorld() は、preload の隔離されたコンテキストから renderer 側へ API を安全に公開するための仕組みです。(Electron)
7. renderer.js
renderer 側の JavaScript はこうします。
const button = document.getElementById("btn")
const result = document.getElementById("result")
button.addEventListener("click", async () => {
const version = await window.desktopAPI.getAppVersion()
result.textContent = `App Version: ${version}`
})
renderer 側は window.desktopAPI を通してしか main 側に触れません。
この「直接触らせず、必要な関数だけを渡す」形が preload の基本です。renderer から main への呼び出しに invoke / handle を使う形も Electron 公式のチュートリアルと同じです。(Electron)
8. index.html
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self'">
<title>Electron Preload Sample</title>
</head>
<body>
<h1>preload.js のサンプル</h1>
<button id="btn">アプリのバージョンを取得</button>
<p id="result"></p>
<script src="./renderer.js"></script>
</body>
</html>
9. 実行
前回と同じように起動します。
npm start
ボタンを押して、バージョンが表示されれば成功です。
10. やってはいけない書き方
次のように ipcRenderer をそのまま全部公開するのは NG です。
const { contextBridge, ipcRenderer } = require("electron")
contextBridge.exposeInMainWorld("badAPI", {
send: ipcRenderer.send
})
これは renderer 側から任意の IPC メッセージを送れてしまうので危険です。Electron 公式でも、IPC はメソッド単位でラップして必要なものだけ公開するように案内されています。(Electron)
今回のように、
getAppVersion: () => ipcRenderer.invoke("app:getVersion")
のように 1 機能ずつ公開する形にしましょう。
11. まとめ
preload.js は、Electron における main と renderer の橋渡し役です。
特に今の Electron では contextIsolation が標準で有効なので、contextBridge を使って必要最小限の API だけを公開する考え方が重要です。ipcMain.handle() と ipcRenderer.invoke() を組み合わせると、初心者でも安全に IPC を組めます。(Electron)