Smart Communication Design Company
ホーム > ナレッジ > Blog > Apps Blog > 2015年12月 > Electronでプロセス間通信をする

Electronでプロセス間通信をする


インタラクションアーキテクト 大室

前回は、Electronを使って一番簡単なアプリを作ってみました。今回は、Node.jsと連携させる方法について紹介します。

メインプロセスとレンダラプロセス

Electronは、起動時にNode.js側で実行するJavaScriptと、そのNode.jsから立ち上げたBrowserWindow(HTML)で実行するJavaScriptの、2つのJavaScriptが存在し、それぞれ別のプロセスがあります。
前者を「メインプロセス」、後者を「レンダラプロセス」と呼んでいます。

レンダラプロセスからメインプロセスへ必要に応じて処理を依頼することで、ブラウザのJavaScriptだけではできなかった様々なことが実現可能になります。
そして、Electronではそのプロセス間通信を行うためのモジュールとして、ipcモジュールがあらかじめ定義されています。

例えばブラウザで実行されるJavaScriptはセキュリティ上、ファイルが設置されているドメイン以外のURLにあるファイルを取得することはできません。しかし、Electronのメインプロセスを担っているNode.jsはサーバサイドで使うことを想定した技術のため、ブラウザの制限を受けず、通信することが可能になります。

非同期通信の実装例

まずは、レンダラプロセスとメインプロセス間で非同期通信をするサンプルを紹介します。
レンダラプロセスからメインプロセスへ値を送り、メインプロセスでその値を2倍にしてレンダラプロセスへ返し、HTMLで表示するというサンプルです。

非同期通信は、レンダラプロセスからメインプロセスへ処理を依頼し、メインプロセスで処理が終わったら値を返します。 非同期のため、レンダラプロセスはメインプロセスから値が帰ってくることを待たずに次の処理に進みます。 そのため、レンダラプロセスにメインプロセスからの処理結果用のコールバック関数をあらかじめ定義する必要があります。

pakckage.json

{
  "name"    : "async-app",
  "version" : "0.1.0",
  "main"    : "async.js"
}

HTML(レンダラプロセス)

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Async Test</title>
<script>
  // IPC通信を行う
  var ipc = require('ipc');
  var info;
  window.onload = function () {
    info = document.getElementById('info');
    testAsync();
  };
  // 非同期に通信を行う
  function testAsync() {
    // 非同期通信の結果を受けたときのコールバック
    ipc.on('async-reply', function(arg) {
      insertMsg("result = " + arg);
    });
    // メインプロセスに引数を送信
    ipc.send('async', { value:200 });
  }
  function insertMsg(msg) {
    info.innerHTML += msg + "<br>";
  }
</script>
</head>
<body>
<h1>Async Test</h1>
<p id="info"></p>
</body>
</html>

JavaScript(メインプロセス)

var app = require('app');
var BrowserWindow = require('browser-window');
var ipc = require('ipc');
var mainWindow = null;
// 起動
app.on('ready', function(){
    mainWindow = new BrowserWindow({ width:600, height:600 });
    mainWindow.loadUrl('file://' + __dirname + '/async.html');
    mainWindow.on('closed', function(){
        mainWindow = null;
    });
});
// 非同期プロセス通信
ipc.on('async', function( event, args ){
    console.log( args );
    var result = args.value * 2;
    // レンダラプロセスへsend
    event.sender.send('async-reply', result);
});

ターミナル(Windowsならコマンドプロンプト)で上記ファイルのあるディレクトリへ移動し、electronコマンドを実行すると、以下のようになるはずです。
レンダラプロセスから送った値が、2倍になって表示されていれば成功です。



実用例

冒頭で述べたとおりブラウザで実行されるJavaScriptから別ドメインにあるファイルをAjaxで取得しようとすると、クロスドメイン制約のため取得できません。
しかし、Node.jsが使えるElectronでは、レンダラプロセスのJavaScriptからメインプロセスのJavaScript(Node.js)を実行できるため、ブラウザのセキュリティを考慮する必要がなく通信可能になります。

ここで、小さなサンプルアプリを作ってみたので紹介します。
任意のWebサイトのHTMLファイルを動的に取得し、その中にあるすべてのAタグのhref属性に入っている値を抽出するアプリです。
今回は当社のWebサイトからリンク先を抽出してみたいと思います。

pakckage.json

{
  "name"    : "linkExtraction-app",
  "version" : "0.1.0",
  "main"    : "linkExtraction.js"
}

HTML(レンダラプロセス)

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>リンク抽出</title>
<script>
  // IPC通信を行う
  var ipc = require('ipc');
  var result;
  // onloadのタイミングで実行
  window.onload = function () {
    result = document.getElementById('result');
    getLink();
  };
  // 非同期通信の関数
  function getLink() {
    // 非同期通信の結果用のコールバック
    ipc.on('link-extraction-reply', function( args ) {
      if( args === null ){
        return;
      }
      var len = args.length;
      var resultStr = '<ul>';
      for(var i = 0; i < len; i++){
        var text = args[i]['text'];
        var href = args[i]['href'];
        resultStr += '<li>' + text + ' : ' + href + '</li>';
      }
      resultStr += '</ul>';
      insertResult( resultStr );
    });
  }
  // メインプロセスに引数を送信
  ipc.send('link-extraction-async', { target: 'https://www.mitsue.co.jp/knowledge/'} );
  // 返却値をHTMLに挿入
  function insertResult( msg ) {
    result.innerHTML += msg;
  }
</script>
</head>
<body>
<div id="result"></div>
</body>
</html>

JavaScript(メインプロセス)

var app = require('app');
var BrowserWindow = require('browser-window');
var ipc = require('ipc');
var client = require('cheerio-httpcli');
var URL = require('url');
var mainWindow = null;
// 起動
app.on('ready', function(){
    mainWindow = new BrowserWindow({width:600, height:600});
    mainWindow.loadUrl('file://' + __dirname + '/linkExtraction.html');
    mainWindow.on('closed', function(){
        mainWindow = null;
    });
});
// 非同期プロセス通信
ipc.on('link-extraction-async', function( event, arg ){
    var target = arg['target'];
    client.fetch( target, {}, function(err, $, res) {
        if( err ){
            // エラーの場合、レンダラプロセスへnullを返す
            event.sender.send('link-extraction-reply', null);
            return;
        }
        // 一時保存用のArray
        var resulrObjArr = [];
        $("a").each(function( idx ) {
            var text = $(this).text();
            var href = $(this).attr('href');
            if( !href ){
                return;
            }
            // URLモジュールを使い、絶対パスに変換
            var href2 = URL.resolve( target, href );
            // Objectにする
            var tempObj = {
                text : text,
                href : href2
            };
            // 保存しておく
            resulrObjArr.push( tempObj );
        });
        // レンダラプロセスへ返す
        event.sender.send('link-extraction-reply', resulrObjArr);
    });
});

以下のようになれば成功です。

Electronは、Web技術がベースになっているため、SassのようなCSSプリプロッサも使えますし、AngularJSやReactなどのJavaScriptフレームワークも使えます。
さらにそこにNode.jsの強力なモジュールを組み合わせることで、業務効率化を図るちょっとしたアプリでも、Visual Studio Codeのような大規模なアプリでも、用途にあった様々なアプリをWeb技術で作ることが可能になります。

今回は触れていませんが、ElectronやNode.jsを使うことでユーザーのローカルにあるファイルの読み書きや操作など、できることがとても多くなるため、モラルやセキュリティにもこれまで以上に配慮する必要があります。また、アプリは操作感も大切になるため、UXにもこだわるとより良いアプリになるのではないでしょうか。
こういった部分は、大変ではありますが、制作者としては非常に面白い部分でもあると思います。
ぜひ、試してみてください。