概要
Electron アプリ開発における、プレロードスクリプトの使い方について解説します。
メインプロセスとレンダラープロセス
Chrome では、アプリケーション全体を管理する単一のプロセスと、タブごとにページの描画を担当する複数のプロセスに分かれています。Electron でも同様の設計となっており、ウィンドウを管理する単一のプロセスをメインプロセス (main process)、ウィンドウごとに表示するページの描画を担当するプロセスをレンダラープロセス (renderer process) といいます。
- メインプロセスでは、
BrowserWindow()
によりウィンドウを作成し、管理を行います。Node.js の API を使用することができます。 - レンダラープロセス:
BrowserWindow()
でウィンドウを作成すると、ウィンドウごとにレンダラープロセスが作成され、ウィンドウに表示するページの描画が行われます。DOM にアクセスできる一方、セキュリティ上の理由からデフォルトでは、Node.js の API にはアクセスできないようになっています。もし、レンダラープロセスから OS に対する操作を可能にする Node.js の API へのアクセスを許可すると、XSS などの脆弱性の危険があるためです。
メインプロセスとレンダラープロセス間でやり取りを行いたい場合、プロセス間通信を行う必要があります。 例えば、レンダラープロセスで画面のボタンをクリックしたら、プロセス間通信によりそのイベントをメインプロセス側に通知し、メインプロセス側でウィンドウを開く処理を実行します。
プロセス間通信
プロセス間通信 (Inter-Process Communication, IPC) とは、複数のプロセス間でデータをやり取りする仕組みです。Electron では、メインプロセスとレンダラープロセスでデータをやり取りする際に使用されます。
プリロードスクリプト
プリロード (preload) スクリプトは、レンダラープロセスでページの描画の前に実行されるスクリプトです。
プリロードスクリプト内では、Node.js や Electron の API の一部にアクセスできます。レンダラープロセスと Window インタフェースを共有しているので、レンダラープロセスから直接はアクセスできない関数を使用した API をプリロードスクリプト内に定義し、レンダラープロセスに公開できます。
この仕組みにより、Node.js や Electron の API を利用する機能を安全にレンダラープロセスに追加できます。
ただし、デフォルトでは、プリロードスクリプトとレンダラープロセスでは、Javascript コンテキストを共有しないようになっています。そのため、グローバル変数 window
に対して、window.myAPI = 関数
のように直接 API を追加することはできず、contextBridge
モジュールを通して安全に行います。
プリロードスクリプトを読み込むようにするには、BrowserWindow()
のウィンドウ作成時に以下のように、プリロードスクリプトのパスを指定します。
const win = new BrowserWindow({
webPreferences: {
preload: path.join(__dirname, "preload.js"),
},
});
nodeIntegration
レンダラープロセスで Node.js を利用できるようにするかどうかを指定する BrowserWindow()
の引数です。レンダラープロセスから直接 Node.js を利用できるのは危険であるため、デフォルトでは、nodeIntegration = false
になっています。
const win = new BrowserWindow({
webPreferences: {
nodeIntegration: true, // 危険
},
});
contextIsolation
プリロードスクリプトとレンダラープロセスで、Javascript コンテキストを共有するかどうかを指定する BrowserWindow()
の引数です。デフォルトでは、contextIsolation = true
になっています。
const win = new BrowserWindow({
webPreferences: {
contextIsolation: false, // 危険
},
});
プリロードスクリプトの使い方
コード
コード全体は以下を参照ください。
electron-examples/05_electron-tutorial-use-preload-script
1. プリロードスクリプトを作成する
preload.ts
を作成します。
preload.ts
import { contextBridge } from "electron";
contextBridge.exposeInMainWorld("versions", {
node: () => process.versions.node,
chrome: () => process.versions.chrome,
electron: () => process.versions.electron,
});
contextBridge.exposeInMainWorld(API名, API の一覧)
とすると、レンダラープロセス内で、window.API名
として、第2引数の API の一覧が公開されます。
今回の例では、window.versions.node()
、window.versions.chrome()
、window.versions.electron()
という形で、レンダラープロセスで利用できるようになります。(window
オブジェクトなので、省略して、versions.node()
、versions.chrome()
、versions.electron()
でも可)
このとき、ディレクトリ構成は以下のようになっています。
electron-examples
├── build <-- ビルドすると生成
| ├── main.js
| └── preload.js
├── index.html
├── main.ts
├── node_modules
├── package-lock.json
├── package.json
├── preload.ts
└── tsconfig.json
2. メインプロセスでプリロードスクリプトを指定する
メインプロセスでは、BrowserWindow()
でウィンドウを作成する際に、読み込むプリロードスクリプトを追加します。今回は Typescript であるため、プリロードスクリプトのパスは preload.ts
ではなく、ビルド後の ./build/preload.js
のパスを指定します。
main.ts
import { app, BrowserWindow } from "electron";
import * as path from "path";
const createWindow = () => {
const win = new BrowserWindow({
webPreferences: {
preload: path.join(__dirname, "preload.js"),
},
});
win.webContents.openDevTools();
win.loadFile("index.html");
};
app.whenReady().then(createWindow);
3. レンダラープロセスで公開した API を利用する
contextBridge
で公開した API を利用する例を記載します。window
オブジェクトに versions
という名前で、プリロードスクリプトで定義した API が追加されています。
index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
</head>
<body>
<h1>Hello World</h1>
<p id="info"></p>
</body>
</html>
<script>
const info = document.getElementById("info");
info.innerText = `This app is using Chrome (v${versions.chrome()}), Node.js (v${versions.node()}), and Electron (v${versions.electron()})`;
</script>
これを実行すると、画面上に Node.js、Chrome、Electron のバージョンが表示されます。
コメント