はじめに
JSをほぼほぼ書いたことない私がElectronでアプリを作りながらJavaScriptを勉強すれば、JSに触れられて、HTML&CSSの理解を深められて、今HotなElectronもできて一石四鳥じゃんと思いアプリを作り始めました。
しかし、BrowserProcessとRendererProcessの関係(あとJS自体の仕様も関係ある?)で勘違いしてハマりにハマったので、理解したイメージを図にします。
ドキュメントにはちゃんと文章で書いてありますが、イメージとしてつかんだ方がわかりやすかったです。
動作確認したElectronのバージョンは 0.37.8 です。
イメージ図
こんな感じです。解説は後述してます。
Electronとは
webの技術(HTML,CSS,JS)でデスクトップアプリを作れるもの。
しかもクロスプラットフォーム対応。
AtomやSlackやVisual Studio Codeを作ってるやつ。
詳しくは->Electron
BrowserProcess と RendererProcess
Electronでは BrowserProcess(Main Process) と RendererProcess の2つのプロセスが走っています。
超噛み砕くと、
BrowserProcess は、GUIパーツの処理を行います。(公式)
RendererProcess は、ブラウザの中のJSを処理します。(公式)
つまり、
BrowserProcessは重い処理もできますよ。
RendererProcessはDOM描画系の処理をさせる用ですよ。
といった感じです。
それぞれのプロセスは別物なので基本的に通信はできません。
しかし、それらのプロセス間通信をするモジュールが提供されています。
それが、remoteモジュールになります。
remote モジュール
BrowserProcessとRendererProcess間のプロセス間通信(IPC)の仕組みを提供するモジュールです。
RendererProcessからBrowserProcessのモジュールを使えるようにするモジュールです。(公式)
RendererProcess上で
const remote = require('electron').remote;
const BrowserWindow = remote.BrowserWindow;
var win = new BrowserWindow({ width: 800, height: 600 });
win.loadURL('https://github.com');
こんな感じにすると、GUI操作ができてしまう。
ということです。
問題の背景
mylib.jsが状態を保持する、かつ、重い処理をするプログラムでした。
そこで、BrowerProcessに処理してもらおうと、 remote.require
して処理してもらいました。
index.jsから remote.require
した時のmylib.jsの状態と、 require
したhoge.jsから require
した時のmylib.jsの状態が違いました。
つまり、 重い処理の時はremote経由でrequireしておけばいいんでしょ。 という感じでした。
remoteを経由しようがしまいが、 同じ参照にアクセスできると思っていたことが間違い でした。
原因と解決策
原因
remoteを経由してモジュールをrequireすると、remoteなしでrequireした時とは別の参照にアクセスするっぽい。
解決策
remoteの使い所は考える。
remote使うとremote無しの場合と別ものを参照する。
重い処理の時はremote経由でrequireしておけばいいんでしょ
-> 重い処理はBrowserProcessで動かして、RendererProcessからremoteモジュール経由でアクセスする
イメージ図解説
- Electron実行で BrowserProcess 内で
package.json
のmainに書かれたファイル(main.js
)が実行される -
BrowserProcess から
mylib.js
をrequire
。mylib.js
の実体ができる。 -
main.js
でnew BrowserWindow
することで、GUIのウィンドウが生成されて RendererProcess が動き出します。 - ブラウザで
index.html
を読み込みます。 -
index.html
からindex.js
が呼ばれます。 -
remote
経由 でmylib.js
を呼び出すと、mylib.js
が BrowserProcess 下で動きます。 -
remote
なし でmylib.js
を呼び出すと、mylib.js
が RendererProcess 下で動きます。
2と7の mylib.js
の実体はそれぞれ別ものになります。
なので、valueの値も別ものになります。
例
ソース
{
"name" : "my-app",
"version" : "0.1.0",
"main" : "main.js"
}
'use strict';
const electron = require('electron');
const app = electron.app;
const BrowserWindow = electron.BrowserWindow;
var mainWindow = null;
app.on('ready', function() {
mainWindow = new BrowserWindow({width: 800, height: 600});
mainWindow.loadURL('file://' + __dirname + '/index.html');
mainWindow.on('closed', function() {
mainWindow = null;
});
});
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Hello World!</title>
</head>
<body>
<h1 id="h">Hello World!</h1>
<p id="p1"></p>
<p id="p2"></p>
<p id="p3"></p>
<p id="p4"></p>
<script type="text/javascript" src="index.js"></script>
</body>
</html>
'use strict';
var remote = require("remote");
// RenderereProcess
var mylib1 = require("./mylib");
var mylib2 = require("./mylib");
// BrowserProcess
var mylib1_r = remote.require("./mylib");
var mylib2_r = remote.require("./mylib");
window.onload = function(){
mylib1.reg("hogehoge 1");
mylib2.reg("hogehoge 2");
mylib1_r.reg("foobar 1");
mylib2_r.reg("foobar 2");
document.getElementById("p1").innerHTML = mylib1.value;
document.getElementById("p2").innerHTML = mylib2.value;
document.getElementById("p3").innerHTML = mylib1_r.value;
document.getElementById("p4").innerHTML = mylib2_r.value;
};
var mylib = {
reg: function(val){
this.value = val;
},
value: null,
};
module.exports = mylib;
結果
index.js
で mylib1.value != mylib1_r.value
となっています。
これは、それぞれがRendererProcess上とBrowserProcess上の参照で異なっているからです。
さらに、 index.js
で
mylib1.value == mylib2.value
、 mylib1_r.value == mylib2_r.value
となっています。
RendererProcess上のとBrowserProcess上はそれぞれ同じ実体を参照しています。
まとめ
- Electronには BrowserProcess と RendererProcess がある。
- BrowserProcessとRendererProcessは 別の空間 がある。
- それぞれの通信には remoteモジュール を使う。
JSの参照の仕組みの理解がなかったことが事態をややこしくしていました。