RailsエンジニアがElectronを触ってみた


最近流行り（？）の、HTML + JavaScriptでクロスプラットフォームなデスクトップアプリを作成できるフレームワーク、 **Electron** を触ってみた。

[Electron](http://electron.atom.io/)

![Screen Shot 2015-07-27 at 6.17.48 PM.png](https://qiita-image-store.s3.amazonaws.com/0/1795/0f3938af-3c25-cc6f-513e-f9d493ca8c5b.png "Screen Shot 2015-07-27 at 6.17.48 PM.png")


## Electronとは
- GitHubが開発しているオープンソース
- 旧名Atom-Shell
- リッチなAPIを持ったランタイムを提供、ユーザーはHTML, JavaScriptといった普段使っているWeb技術でデスクトップアプリを作成可能
- Mac、Windows、Linux対応

以下の2つの要素がベースとなっている

- Chromiumブラウザ
	- Chromeブラウザのオープンソース版
- io.js
	- ランタイム

> Electron uses web pages as its GUI, so you could also see it as a minimal Chromium browser, controlled by JavaScript.

by [Quick Start](http://electron.atom.io/docs/v0.29.0/tutorial/quick-start/)

要は、ElectronがプログラマブルなChromiumブラウザを提供してくれて、ユーザーはその上でデスクトップアプリを実装できるって感じだろうか。。

## 利用例
- [Atom](https://atom.io/)
- [Kobito for Windows](http://kobito.qiita.com/win)
- [Slack for Windows](https://slack.com/apps)
- [Visual Studio Code](https://code.visualstudio.com/)
- [Kitematic](https://kitematic.com/)

## Electronの特徴
- プラットフォーム間の違いを気にせず、単一のソースコードでビルド可能
- ランタイムはNode(io.js)なので、npmのパッケージを利用可能
- Chromiumブラウザということで、いつもChromeで使っているDevToolsを利用しながらデバッグ可能
- Chromiumベースなので、最先端のWeb技術を利用できる
	- このへんは片手間でフロントエンドやってるため全然詳しくないのだが、ES6(Babel)とかWebGLとか...

その他、Electronの概要や特徴については、日本語の記事だと以下が分かりやすかった

- [HTML5でクロスプラットフォームなデスクトップアプリを開発できる「Electron」とは (1/2)：CodeZine（コードジン）](http://codezine.jp/article/detail/8782)
- [Electron(旧atom shell)基礎+入門](http://www.slideshare.net/mainya/electronatom-shell)


## 仕組み
よく説明に利用されていたのが、以下のスライドの4-5枚目の図。

[Practice on embedding Node.js into Atom Editor // Speaker Deck](https://speakerdeck.com/zcbenz/practice-on-embedding-node-dot-js-into-atom-editor)

> Atom Shell is a Node.js programmable Chromium Browser

とあるように、もともとChromiumブラウザではC++によって制御していたところにNodeを組み込んでいるのがElectron。

Electron（Chromium）の実行においては、2種類のプロセスが存在する（マルチプロセスアーキテクチャ）。上記スライドの図にも載っている

### メインプロセス（Browserプロセス）
io.jsが組み込まれ、`package.json`のメインスクリプトを実行する。
GUIにWebページを表示、ウインドウやメニューの操作・作成を行う

### Rendererプロセス
Electron上のWebページは、自身のプロセスで実行される。これがレンダラープロセス。（Webブラウザでいうところの各タブを実行するプロセス？）
こちらにもio.jsが組み込まれ、APIを通してDOMだけでなくネイティブリソースにアクセスできる。通常のブラウザは、Webページをサンドボックス化された環境で実行するためこういったことはできないが、Electronでは低レベルのオペレーティングシステムとのインタラクションが可能になっている。

### 双方の役割
メインプロセスは、`BrowserWindow`インスタンスを作成することでWebページを作成、それぞれのインスタンスは自身のRendererプロセスでページを実行。インスタンスが破棄されれば、そのRendererプロセスも終了する。

メインプロセスが全てのWebページとそのRendererプロセスを管理。各Rendererプロセスは独立し、その関心は関連するWebページの実行についてのみ。

RenererプロセスからGUI操作（= メインプロセスの役割）を行いたいときは、メインプロセスとやりとりし合う必要がある。（Webページで直接ネイティブGUIリソースを扱うのは危険で、呼び出しが許されてないため。）このプロセス間通信を行う際`ipc`モジュールや`remote`モジュールを利用する。

- [Ipc Renderer](http://electron.atom.io/docs/v0.29.0/api/ipc-renderer/)
- [Remote](http://electron.atom.io/docs/v0.29.0/api/remote/)

プロセス間通信に関しては、以下の記事が説明分かりやすかった気がする

- [Electronでipcを使ってプロセス間通信を行う - Qiita](http://qiita.com/Misumi_Rize/items/dde76dbf89abee13991c)
- [Electronのremoteでプロセス間通信を高レベルに扱う - Qiita](http://qiita.com/Misumi_Rize/items/a434cc0d17e5c6b85147)

## インストール
```
> npm install electron-prebuilt -g

> electron -v
v0.30.1
```

## 動かしてみる
ドキュメントに書いてあるやつとほぼ同じ。
`npm init -y`でプロジェクト作成。

```json:package.json
{
	...
	"main": "main.js"
	...
}
```

```Javascript:main.js
var app = require('app');
var BrowserWindow = require('browser-window');

app.on('window-all-closed', function() {
  if (process.platform != 'darwin') {
    app.quit();
  }
});

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;
  });
});
```

Electronのエントリーポイントはjs。`package.json`で指定した`main.js`を見ていく。

[app](http://electron.atom.io/docs/v0.29.0/api/app/)モジュールで、アプリのライフタイムを制御。
Electronの初期化が完了し、ウインドウ（初期画面）を開く準備が整ってからの処理内容が、
`app.on('ready', function() { ...`の中身。

上のほうで書いたように、メインプロセスが`main.js`を実行し[BrowserWindow](http://electron.atom.io/docs/v0.29.0/api/browser-window/)インスタンスを作成する。このインスタンスは自身のRendererプロセスでWebページを実行する。
ページの中身として`file://`ではじまるHTMLを呼んでいる（この場合は同じ階層にある`index.html`）。


`index.html`とそこで読み込んでるjsについては以下。

```html:index.html
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>Electron Sample</title>
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css">
  </head>
  <body>
    <div class="container">
      <h1>Qiita Item Reader</h1>
      <div id="item-container"></div>
      <script src="dest/app.js"></script>
    </div>
  </body>
</html>
```

`react`と`superagent`, `reactify`あたりを`npm install`しておく。
QiitaのAPIを使って、記事名のリストが表示されるやつをReactで適当に書く。

```javascript:src/app.jsx
var React = require('react')
var request = require('superagent');

var QiitaItemList = React.createClass({
  getInitialState: function() {
    return {
      items: []
    };
  },
  componentDidMount: function() {
    var _this = this;
    var url = "http://qiita.com/api/v2/items";
    request
      .get(url)
      .query({ token: "<Qiita Token>" })
      .end(function(err, res) {
      _this.setState({ items: res.body });
    });
  },
  render: function() {
    var _this = this;
    var itemList = _this.state.items.map(function(item, index) {
      return (
        <div key={index}>
          <h2>
            <a href='#'>{item.title}</a>
          </h2>
          <hr />
        </div>
      );
    });

    return (
      <div className="col-md-10 col-md-offset-1">{itemList}</div>
    );
  }
});

React.render(<QiitaItemList />, document.getElementById('item-container'));
```

`watchify`で、`src/app.jsx`をコンパイルして`dest/app.js`にする。

```
> watchify -t reactify src/app.jsx -o dest/app.js -v
```

で、Electronでアプリ起動。プロジェクトのルートディレクトリで

```
> electron .
```

これでウインドウが起動して、以下のようなのが出る。Webアプリにネイティブのガワをかぶせた感じ。

![Screen Shot 2015-07-28 at 4.09.17 PM.png](https://qiita-image-store.s3.amazonaws.com/0/1795/3a1bb6ed-d20a-ba8f-e943-d0a47b995863.png "Screen Shot 2015-07-28 at 4.09.17 PM.png")

まだ何もできないけど、Electronで動く簡単なデスクトップアプリができました。

### メニュー作成
[menu](http://electron.atom.io/docs/v0.29.0/api/menu/)モジュールを利用することで、アプリのメニュー（Macだと画面上部に出るやつ）が作成できる。

`main.js`にちょっと追加。

```javascript:main.js
...

var Menu = require('menu');

app.on('ready', function() {
  ...
  
  Menu.setApplicationMenu(menu);
}

var template = [
  {
    label: 'Quit',
    submenu: [
      {
        label: 'Quit Qiita Reader',
        accelerator: 'Command+Q',
        click: function() {
          app.quit();
        }
      }
    ]
  },
  {
    label: 'View',
    submenu: [
      {
        label: 'Open DevTools',
        accelerator: 'Alt+Command+I',
        click: function() {
          BrowserWindow.getFocusedWindow().toggleDevTools();
        }
      }
    ]
  }
];
var menu = Menu.buildFromTemplate(template);
```

これでメニューが表示できるようになる。
上記のようにオブジェクトを作成していくと簡単に作成できる（変数`template`に入れてるやつ）。ショートカットキーなんかも設定可能

![Screen Shot 2015-07-28 at 11.22.50 PM.png](https://qiita-image-store.s3.amazonaws.com/0/1795/d499df42-872a-356a-081d-5bcdee695e9d.png "Screen Shot 2015-07-28 at 11.22.50 PM.png")

### デスクトップ通知
Electronでは、HTML5のNotification APIを利用して簡単にデスクトップ通知も出せる。
通常のWebブラウザだとパーミッションを取らなくてはいけないが、Electronでは必要ない。

[Notification - Web API インターフェイス | MDN](https://developer.mozilla.org/ja/docs/Web/API/notification)

`index.html`で以下のjsを読み込み、`#js-notification`の要素を作ってそれをクリックすると、デスクトップ通知が出せる。

```javascript:dest/notification.js
var remote = require('remote');
var shell = remote.require('shell');

function notificationTest() {
  var n = new Notification('Electron!!', {
    body: 'Electronのドキュメントを開きます'
  });
  n.onclick = function () {
    shell.openExternal('http://electron.atom.io/docs/v0.29.0/');
  };
}
document.getElementById('js-notification').addEventListener('click', notificationTest, false);
```

[shell](http://electron.atom.io/docs/v0.29.0/api/shell/)モジュールを利用することで、デスクトップ通知をクリックした際に、PCのデフォルトブラウザで指定したURLのページが開く。

![Screen Shot 2015-07-28 at 11.49.28 PM.png](https://qiita-image-store.s3.amazonaws.com/0/1795/62bd2128-dc5e-ccd1-cf8f-79d73d6db428.png "Screen Shot 2015-07-28 at 11.49.28 PM.png")

↑ こんな通知が出る

## アプリのパッケージング
ドキュメントの[Application Packaging](http://electron.atom.io/docs/v0.29.0/tutorial/application-packaging/)に書いてる

### asar
[asar](https://github.com/atom/asar)というツールで、Electronのアプリをパッケージングできる。

```
> npm install asar -g
```

```
> asar pack <Electron Project Directory> ~/sampleapp.asar
```

これを実行すると、`sampleapp.asar`というのが作成され、`electron sampleapp.asar`で先ほど作ったアプリが起動する。

### electron-packager
asarでアーカイブ化されたファイルはElectronの実行環境が無いと動かせない。
実行環境のない人にも配布する際には、[electron-packager](https://github.com/maxogden/electron-packager)が便利そうだった

```
> electron-packager <Electron Project Directory> sample --platform=darwin --arch=x64 --version=0.30.1
```

↑を実行しMac用のappを作ってみる。同じディレクトリにsample-darwin-x64というディレクトリが生成され、中を見るとsample.appというのが入ってる。

![Screen Shot 2015-07-29 at 12.13.49 AM.png](https://qiita-image-store.s3.amazonaws.com/0/1795/9f8e7211-3af5-892c-14b7-7d657a4e221d.png "Screen Shot 2015-07-29 at 12.13.49 AM.png")

（サイズでけぇ...）

ダブルクリックすると、アプリが起動。
これを渡せば他人のMacでも動く。確認してないが、たぶん...

## まとめ
とりあえず以上です。

- 自分のようにフロントエンドの技術に慣れてなくても、単機能なデスクトップアプリなら簡単に作れそう
- 情報はまだ多くない（ドキュメントはしっかりしてる印象）
- ちゃんとしたフロントエンドエンジニアの方が触ったらすごく楽しそう...

非常に面白い技術だという印象なので、もう少し触ってみたいなと思います。