IPCとは
Electronは、アプリケーション本体を管理している メインプロセス と、画面(html)を表示している レンダラプロセス の2種類のプロセスから構成されます。
基本的に、レンダラプロセスはブラウザなので通常の Javascript が使え、メインプロセスでは Node.js が使えます。したがってユーザーの操作をUIで受け取り、OSネイティブの機能を利用しようとすると、プロセス間での通信(IPC)が必要になります。
イメージはこんな感じです。
メインプロセス
最初に起動するエントリポイントとなっているファイル、package.json の “main” に設定されているjsファイルが動くのがメインプロセスです。
Node.js で動くのでOSネイティブのAPIを使えます。Fileの読み書きなどを行う場合はこのメインプロセスで行う必要があります。
このメインプロセスを終了するとアプリケーションが終了します。
レンダラプロセス
画面のレンダリングを行っているプロセスです。ブラウザで表示されているhtmlファイルの数だけ、つまりタブの数だけレンダラプロセスがあるイメージです。
ElectronはChromiumで動いているのでこのような仕組みになっています。
したがって当然ブラウザ上で動くJavascriptと同じ機能しか使えません。
Electron でのプロセス間通信の実現方法
ElectronではIPCを行うためのモジュールである ipcMain
と ipcRenderer
が提供されます。これを使ってプロセス間での通信を実現します。
IPCには同期通信と非同期通信の2種類があります。基本的には非同期で使うのがいいと思います。
それぞれの使い方を確認します。
electron/electron-quick-start: Clone to try a simple Electron app
上記レポジトリをcloneして使っていくのが簡単です。
# クローン
git clone https://github.com/electron/electron-quick-start
# ディレクトリに移動
cd electron-quick-start
# パッケージのインストール
npm install
# 起動
npm start
上記手順で構築した環境で説明を行います。
ipcMain
ipcMain
モジュールはメインプロセスで、レンダラプロセスから送信されるメッセージを受信して処理します。基本的にメインプロセスは受け身で受信したら送信元のレンダラプロセスに対して返信するみたいな動きになります。
ipcRenderer
メインプロセスに対して、同期、非同期のメッセージを送信できるメソッドが用意されています。もちろん返信を受け取ることもできます。
IPCでの非同期送受信のコード
では動作を確認してみます。まずは非同期メッセージの送受信です。
main.js に以下のコードを追記します。
const { ipcMain } = require('electron')
// 非同期メッセージの受信と返信
ipcMain.on('asynchronous-message', (event, arg) => {
// 受信したコマンドの引数を表示する
console.log(arg) // ping
// 送信元のチャンネル('asynchronous-reply')に返信する
event.reply('asynchronous-reply', 'pong')
})
// 同期メッセージの受信と返信
ipcMain.on('synchronous-message', (event, arg) => {
console.log(arg) // ping
// 非同期との違いは reply を使うか returnValue を使うか
event.returnValue = 'pong'
})
ついでに開発ツールが表示されるように1行コメントアウトされている内容を元に戻してください。
mainWindow.webContents.openDevTools()
次に renderer.js に以下のコードを追記します。
// Node.jsの機能は使えないので preload.js で最低限の機能のみ参照できるようにする
// const { ipcRenderer } = require('electron')
const { ipcRenderer } = window.native;
// チャンネル 'asynchronous-reply' で非同期メッセージの受信を待機
ipcRenderer.on('asynchronous-reply', (event, arg) => {
// 受信時のコールバック関数
console.log(arg) // pong
});
// 非同期メッセージの送信
ipcRenderer.send('asynchronous-message', 'ping')
// 同期メッセージを送信して、返信内容を表示する
const retValue = ipcRenderer.sendSync('synchronous-message', 'ping');
console.log(retValue) // pong
最後に preload.js
で ipcRenderer
を参照できるようにしておきます。以下のコードを preload.js に追記してください。
const { ipcRenderer } = require('electron');
process.once('loaded', () => {
global.native = {
ipcRenderer: ipcRenderer,
};
});
preload.js では Node.js のすべての機能が使用できます。ここで必要な Node.js の機能をレンダラプロセスで参照できるように、global の中に詰め込みます。当然ですがセキュリティ的にも必要最小限の機能のみ設定するようにしましょう。
ここまでのコードを実行してみると、Node.js側には ping, 画面側には pong がコンソールに表示されることが確認できます。同期と非同期の2回分表示されているはずです。
renderer.js から "ping" 送信
↓
main.js で "ping" 表示, "pong" 返信
↓
renderer.js で "pong" 受信して表示
上記のような流れです。同期通信の場合は返信を待つ間ブロックして待機します。
Uncaught ReferenceError: require is not defined
javascript – electron 5.0.0 “Uncaught ReferenceError: require is not defined” – Stack Overflow
このエラーが発生するときはレンダラプロセス(ブラウザ側JS)で require
を使っているため発生しているエラーです。Electron v5 以降だとデフォルトで Node.js がブロックされているのでこのようになるみたいです。
Node.jsを許可する設定もできますが、preload
で必要なパッケージのみ使えるようにしておきましょう。
上の例では、preload.js
において ipcRenderer
を使用できるようにしています。
main.js で以下のようにプリロード設定をしているのがわかります。
mainWindow = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
preload: path.join(__dirname, 'preload.js')
}
})
あとは上の例を参考にして使ってみてください。
以上。
2020年に紹介するのならば、promiseベースのinvokeやhandleを使った手法の方がエレガントなのでオススメです