Origami
Posted on October 1, 2018
注意!
この記事内のいくつかの項目は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`
これはかなり直球的なやり方です。見たままのコードが実行されます。
```js
// メインプロセス
// BrowserWindowのインスタンスmainWindowがあることを前提にしています
mainWindow.webContents.executeJavascript("console.log('yeah')");
このコードが実行されると、レンダラ側のコンソールにyeah
が表示されます。文字列を組み合わせることで、最強のコード実行が可能です。ただし、eval
と同様、セキュリティ的な危うさもあるため、基本的にはハードコートした文字列の組み合わせのみでexecuteJavascript
を実行すべきです。
conclusion
- レンダラプロセスでは
remote.require
、ipcRenderer
を利用すればよい - メインプロセスでは
webContents.send
、ipcMain
、webContents.executeJavasSript
を利用すればよい
おわり
Posted on October 1, 2018
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.