注意!
この記事内のいくつかの項目はoutdatedになりました。特に、remote.requireは非推奨です。
バーチャルユーチューバーです。皆さんもバーチャルユーチューバーですか?
前回のReact+Electronアプリを作る話ではアプリを作るまでの事しか話しませんでした。そして、React及びRedux上で物事がだいたい済むのならば、ElectronやNode.jsについて深く思い悩むこともないでしょう。
しかし、Electronでアプリケーションを作りたいのならば、Node.jsおよびその上で動作するパッケージを使用したいことや、Electron上で取得したデータをReact上で表示したい、という望みがあるはずです。
ここで問題になるのは、そのElectron-メインプロセス-とReact+Redux-レンダラプロセス-の間をどのように処理するか、についてです。
remote.require
次に示すコードは、レンダラプロセスで使用している部分です。
// レンダラプロセス
//@flow
const { remote } = window.require('electron');
const app = remote.require('electron').app;
export default (): Promise<string> => new Promise((resolve) => {
resolve(app.getAppPath()); //アプリケーションの実行場所が絶対パスとして返ります
});
remote.requireはレンダラプロセスで利用可能なrequireの変わりだと思えば十分です。内部的にはinter-process communication (IPC)を利用してメインプロセスのメソッドを呼び出しており、最もシンプルにメインプロセスとのやり取りが可能です。
基本的に、シリアライズ可能なデータであれば受け渡しが可能です。
// レンダラプロセス
//@flow
const { remote } = window.require('electron');
const app = remote.require('electron').app;
const fs = remote.require('fs');
export default (): Promise<any> => new Promise((resolve, reject) => {
try {
fs.writeFileSync(app.getAppPath()+"/internet.json", {googlePublicDns: "8:8.8.8"});
resolve();
} catch (e) {
reject(e);
}
});
ipcMain、ipcRenderer
ipcMainとipcRendererはそれぞれEvent-Drivenな方策でメインプロセスとレンダラプロセスのデータの受け渡しを可能にする機能です。基本的な使い方はElectron ipcMainのドキュメントに載っていますが、実際に使用するとなると(データフローをすべてredux上で済ませたいという気持ちがあるため)reduxとそのmiddlewareを利用した上でipcRendererを利用する場合があります。
少し複雑になりますが、redux-sagaを用いてipcRendererを利用した例を次のコードに示します。
// レンダラプロセス
// @flow
import { eventChannel, END } from "redux-saga";
import { fork, take } from 'redux-saga/effects';
const { ipcRenderer } = window.require('electron');
const ipc = function* () {
try {
const channel = yield eventChannel(emit => {
ipcRenderer.on('asynchronous-reply', (event, arg) => {
console.log(arg);
emit({type: "EVENT_RECEIVED", payload: {}});
});
return () => {};
});
while (true) {
yield take(channel)
}
} catch (e) {
console.error("ipc connection disconnected with unknown error");
}
};
export default function*() { // redux-sagaのrootsagaで1度だけ呼び出す
yield fork(ipc);
}
eventChannelはイベントエミッターを監視するのによく使用されます。emit => {}の中でリスナーを実装し、emitでactionをdispatchすることができます。
本来であればoncloseかなにかが送信された時点でemit(END)を利用しイベントが終了したことをsagaに通知すべきですが、ipcRendererには無いので無視します。
// メインプロセス
ipcMain.on('asynchronous-message', (event, arg) => {
console.log(arg); // prints "ping"
event.sender.send('asynchronous-reply', 'pong')
});
今回の場合、asynchronous-messageという名前のついたイベントがレンダラプロセスから送信された場合、メインプロセスはasynchronous-replyイベントを返送し、データとして'pong'を返送するようにしています。勿論、イベント名は何でもよく、レンダラプロセスとメインプロセス側で名前さえ一致していればよいです。
そして、やはりシリアライズ可能なものであれば何であれ送受信可能です。
試しに、レンダラプロセスの適当な場所で'asynchronous-message'イベントを投げてみましょう。
// レンダラプロセス
const ipcRenderer = window.require('electron').ipcRenderer;
setInterval(() => {
console.log('sending...');
ipcRenderer.send('asynchronous-message', 'ping');
}, 1000);
成功しているならば、コンソールは次のように表示されているでしょう。
WebContents.send
ipcMainのドキュメンテーションにレンダラプロセスに一方的に送信するメソッドがないことに気付きましたか?
レンダラプロセスからメインプロセスへ一方的に送信しまくることはできますが、ipcMainでは基本的に「返送」しかできませんでした。
メインプロセスからレンダラプロセスに何かを一方的に送信しまくりたいときもあります。その時は、WebContents.sendを用います。
// メインプロセス
// BrowserWindowのインスタンスmainWindowがあることを前提にしています
mainWindow.webContents.send("asynchronous-message-from-love", "yeah");
8
ここまで読んできたならもう分かると思いますが、"asynchronous-message-from-love"イベントを送信しています。これを受信するには先程のipcRendererの実装を使い回せばよいだけです。
WebContents.executeJavaScript
これはかなり直球的なやり方です。見たままのコードが実行されます。
// メインプロセス
// BrowserWindowのインスタンスmainWindowがあることを前提にしています
mainWindow.webContents.executeJavascript("console.log('yeah')");
このコードが実行されると、レンダラ側のコンソールにyeahが表示されます。文字列を組み合わせることで、最強のコード実行が可能です。ただし、evalと同様、セキュリティ的な危うさもあるため、基本的にはハードコートした文字列の組み合わせのみでexecuteJavascriptを実行すべきです。
conclusion
- レンダラプロセスでは
remote.require、ipcRendererを利用すればよい - メインプロセスでは
webContents.send、ipcMain、webContents.executeJavasSriptを利用すればよい
おわり

Top comments (0)