MainProcessからRendererProcessにパラメータを渡す(Electron)

Electron

ウィンドウを表示する際に初期化のための値や識別ID等をウィンドウに渡したいケースって多いですよね。Main Processでコンフィグを読み込んで各ウィンドウに配ったり、使う場面は多岐にわたります。
new BrowserWindowする時のoptionでwindowのグローバル変数が定義出来たら非常に便利だと思うのですが、残念ながらそんなに都合よくはできていません。

私が試してみた方法を順に並べます。

  • preload.jsでremote経由で渡す
  • Main -> RendererへIPCで渡す
  • URL Search Paramsで渡す

最終的にはURLにparamsを渡すのが簡単で早いと思ってこれを利用しています。

preload.jsでremote経由で渡す

検索するとこの手の手法が一番ヒットします。
ただ、わざわざpreload.jsを準備するのが手間な上に、remoteやrederer process側でnode.jsを実行するには非推奨になっていますし、敢えてこの手段を取る理由がありません。

Main -> Renderer へIPCで渡す

次に思いついたのは直接IPCでMain Processから必要な情報を渡すことでした。

// main process

const ipcMain = require('electron').ipcMain;

mainWindow = new BrowserWindow();
mainWindow.webContents.once('dom-ready', () => {
    mainWindow.webContents.send('somethingYouWantToPass', somethingYouWantToPass);
});

これが結構ハマりました。
メソッドの呼び出し順に気を使わなくてはならず、RendererProcess側のjsでIPCを受けるコードをどこに書くかが問題でした。

document.addEventListener(‘DOMContentLoaded’, function() {
}
この中に書いちゃうと上記のようにMain Process側で’dom-ready’イベントを待ってから発行しても、RendererProcess側にきちんと伝達しませんでした。
RendererProcessがIPCを受ける体制が整うのが先か、Main Processが送信するのが先かの競争になります。そりゃ通信が成立しないわけだ。

ということで回避するためにはRendererProcessのjsのトップに書けば機能します。
個人的にはなんのスコープにも入っていない領域に変数を定義することになるのは避けたいので、この方法も却下です。

Renderer -> Main へIPCで情報をリクエストする

呼び出し順で困るのならRendererProcess側から呼び出せばいいじゃないかという発想です。

// renderer

const whatIWant = ipcRenderer.sendSync('somethingYouWantToPass');

// MainProcess

const ipcMain = require('electron').ipcMain;

ipcMain.on("somethingYouWantToPass", (event, args) => {
    event.returnValue = whatIWant ;
});

これは問題なく動きます。
気になるのはsendSyncを使っていること。
IPCを同期通信でやると処理がブロックされてしまうので原則非推奨です。

ただし、今回渡したいパラメータはrendererProcessの初期化に必要なパラメータのため、同期的に取得しないと以後の初期化処理で失敗します。

じゃあawaitで値を取ってから処理すれば、と思って以下のように書いてみました。

async ()=>{
    const whatIWant = await ipcRenderer.invoke('somethingYouWantToPass');
    // do something
}

これの何に困ったかというと、constを宣言したいのですがasyncメソッドのブロックスコープ内にあるせいでこのメソッドの外から参照できません。
じゃあasyncの外で変数を定義すればいいじゃないかと思うかもしれませんが、私が定義したいのは定数なんです。letやvarで定義したいわけじゃない。

それならばこの定数を利用する全てのメソッドをasync内で呼べばいいかというと、それも違う。定数の値が即時必要なメソッドと、参照さえあればいいメソッドがあるのです。具体的にはイベント購読で利用するだけならすぐには値は必要ないですよね。なので全てのメソッドをasync内に入れてしまうと、不必要に大きなブロックができてしまいます。

結果的にはsendSyncを行うことと大差ない結果になると思うのですが、不適切なブロックは可読性が下がるのでまだSyncメソッドの方が好みです。
上記のコードで動きはするのですが、ただ初期値を渡すだけでIPC通信が必要なのは大げさな気がします。

URL Search Paramsで渡す

しばらくは仕方なくsendSyncで書いていたのですが、ふと思いついたのがこちら。

mainWindow = new BrowserWindow();
mainWindow.loadURL(url);

あれ?これURLを渡してるんならParamsが付けれるんじゃない?
loadURLならWindowが開いた時点でパラメータは渡っているので、RendererProcess側のjsはいつでも呼び出せるはず。

// main process

var url = 'file://' + __dirname + `./index.html?whatIWant=${whatIWant}`;
mainWindow.loadURL(url);

// renderer process

let params = new URLSearchParams(location.search);
const whatIWant= params.get('whatIWant');

取れる!値が取れます!
paramsならいくらでもパラメータを増やせるし、IPCを全く考えなくていい!
つまりデバッグの手間も減る!コードを書く量も減る!
バグも減ってwinwinwinです!

知ってる人は知っているのかもしれませんが、同じように悩んでいる人も多いのでは?
めちゃくちゃ楽になるのでSeachParamを使うことをおススメします!

コメント