最近流行り(?)の、HTML + JavaScriptでクロスプラットフォームなデスクトップアプリを作成できるフレームワーク、 Electron を触ってみた。
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
要は、ElectronがプログラマブルなChromiumブラウザを提供してくれて、ユーザーはその上でデスクトップアプリを実装できるって感じだろうか。。
利用例
Electronの特徴
- プラットフォーム間の違いを気にせず、単一のソースコードでビルド可能
- ランタイムはNode(io.js)なので、npmのパッケージを利用可能
- Chromiumブラウザということで、いつもChromeで使っているDevToolsを利用しながらデバッグ可能
- Chromiumベースなので、最先端のWeb技術を利用できる
- このへんは片手間でフロントエンドやってるため全然詳しくないのだが、ES6(Babel)とかWebGLとか...
その他、Electronの概要や特徴については、日本語の記事だと以下が分かりやすかった
仕組み
よく説明に利用されていたのが、以下のスライドの4-5枚目の図。
Practice on embedding Node.js into Atom Editor // Speaker Deck
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
モジュールを利用する。
プロセス間通信に関しては、以下の記事が説明分かりやすかった気がする
インストール
> npm install electron-prebuilt -g
> electron -v
v0.30.1
動かしてみる
ドキュメントに書いてあるやつとほぼ同じ。
npm init -y
でプロジェクト作成。
{
...
"main": "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モジュールで、アプリのライフタイムを制御。
Electronの初期化が完了し、ウインドウ(初期画面)を開く準備が整ってからの処理内容が、
app.on('ready', function() { ...
の中身。
上のほうで書いたように、メインプロセスがmain.js
を実行しBrowserWindowインスタンスを作成する。このインスタンスは自身のRendererプロセスでWebページを実行する。
ページの中身としてfile://
ではじまるHTMLを呼んでいる(この場合は同じ階層にあるindex.html
)。
index.html
とそこで読み込んでるjsについては以下。
<!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で適当に書く。
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アプリにネイティブのガワをかぶせた感じ。
まだ何もできないけど、Electronで動く簡単なデスクトップアプリができました。
メニュー作成
menuモジュールを利用することで、アプリのメニュー(Macだと画面上部に出るやつ)が作成できる。
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
に入れてるやつ)。ショートカットキーなんかも設定可能
デスクトップ通知
Electronでは、HTML5のNotification APIを利用して簡単にデスクトップ通知も出せる。
通常のWebブラウザだとパーミッションを取らなくてはいけないが、Electronでは必要ない。
Notification - Web API インターフェイス | MDN
index.html
で以下のjsを読み込み、#js-notification
の要素を作ってそれをクリックすると、デスクトップ通知が出せる。
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モジュールを利用することで、デスクトップ通知をクリックした際に、PCのデフォルトブラウザで指定したURLのページが開く。
↑ こんな通知が出る
アプリのパッケージング
ドキュメントのApplication Packagingに書いてる
asar
asarというツールで、Electronのアプリをパッケージングできる。
> npm install asar -g
> asar pack <Electron Project Directory> ~/sampleapp.asar
これを実行すると、sampleapp.asar
というのが作成され、electron sampleapp.asar
で先ほど作ったアプリが起動する。
electron-packager
asarでアーカイブ化されたファイルはElectronの実行環境が無いと動かせない。
実行環境のない人にも配布する際には、electron-packagerが便利そうだった
> electron-packager <Electron Project Directory> sample --platform=darwin --arch=x64 --version=0.30.1
↑を実行しMac用のappを作ってみる。同じディレクトリにsample-darwin-x64というディレクトリが生成され、中を見るとsample.appというのが入ってる。
(サイズでけぇ...)
ダブルクリックすると、アプリが起動。
これを渡せば他人のMacでも動く。確認してないが、たぶん...
まとめ
とりあえず以上です。
- 自分のようにフロントエンドの技術に慣れてなくても、単機能なデスクトップアプリなら簡単に作れそう
- 情報はまだ多くない(ドキュメントはしっかりしてる印象)
- ちゃんとしたフロントエンドエンジニアの方が触ったらすごく楽しそう...
非常に面白い技術だという印象なので、もう少し触ってみたいなと思います。