読者です 読者をやめる 読者になる 読者になる

Umi Uyuraのブログ

Titaniumを使ったスマートフォンアプリ開発を中心に、プログラミング関連の作業ログ。

Meld LauncherをElectronに移植してみた

ちまたではNW.jsよりElectronの方がフィーバー(死語)している感じなので、ちょっと触ってみることにしました。

とりあえずHello World的なものをやろうかと思ったのですが、ざっと眺めた感じ、作り方はわりと似ている印象だったので、ここはひとつ、NW.jsで作ったアプリをElectronに移植する方が違いがわかるかもと思い、以前React + NW.jsで作っていたMeld Launcherを移植してみることにしました。

umi-uyura.hatenablog.com

Electronへの移植

Electronプロジェクトの準備

Electronの開発を始めるにあたり、まずはランタイムとなる electron-prebuilt を導入する必要があります。NW.jsにおける nw ですね。

グローバルにインストールしてしまうのが手っ取り早そうですが、ここはNW.jsでもやっていた方式に従い、プロジェクトローカルに導入する方法にしました。

そこで、

$ mkdir meld-launcher-electron                  # プロジェクトディレクトリ作成
$ cd meld-launcher-electron                     # ディレクトリ内へ移動
$ npm init                                      # package.jsonを生成
$ npm install electron-prebuilt --save-dev

といった感じで、プロジェクトフォルダを作成し、その中に electron-prebuilt をインストールしました。

合わせて、プロジェクトに必要なnode_modulesをインストール。

$ npm install react --save
$ npm install react-tap-event-plugin --save
$ npm install react-loading --save
$ npm install material-ui --save
$ npm install browserify --save-dev
$ npm install babelify --save-dev
$ npm install watchify --save-dev

これ以外に、NW.jsのときは nw とパッケージングツールnw-builder をインストールしていましたが、Electronのパッケージングツールとして electron-packager を入れておきます。

$ npm install electron-packager --save-dev

プロジェクトの構造

プロジェクトの構造についても、まずはNW.jsで試していた構造を引き継ぐことにしました。

.
├── app
│   ├── css
│   ├── index.html
│   └── js
├── dist
├── node_modules
├── package.json
├── resources
│   └── icon
│       └── nw.icns
└── src
    └── jsx

アプリとしての構造は app フォルダ内にあり、変換前のJSXは src フォルダで開発。

最終的に配布用のビルドを dist フォルダへ出力する想定です。

アイコンはそのまま流用したので名前が nw.icns のままです。

移植

元になるNW.js版の umi-uyura/meld-launcher から、必要なものを移植します。

HTML/CSSをコピー

まずは app 内をごそっとコピー。具体的には、JavaScriptは後で生成するため、 index.htmlapp/css/app.css をコピーしました。

index.htmlからNW.js依存のコードを除去

app/index.html のうち、NW.jsの機能を使っているアプリ終了用のメニュー部分は削除。

<script>
 var gui = require('nw.gui');
 var win = gui.Window.get();
 var menubar = new gui.Menu({type: 'menubar'});
 menubar.createMacBuiltin('Meld Launcher', {hideEdit: true,  hideWindow: true});
 win.menu = menubar;
</script>

エントリポイントを作成

NW.jsではエントリポイントとしてHTMLを指定しますが、ElectronではJavaScriptを指定します。

そこで、エントリポイントとなる app/main.js を作成します。

中身は Electron公式サイト のQuick Start内にある Write your First Electron App に書かれているものを、ほぼコピペしました。

app/main.js

'use strict';

var app = require('app');
var BrowserWindow = require('browser-window');

require('crash-reporter').start();

var mainWindow = null;

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

app.on('ready', function() {
  mainWindow = new BrowserWindow({width: 600, height: 480});
  mainWindow.loadUrl('file://' + __dirname + '/index.html');
  //mainWindow.openDevTools();
  mainWindow.on('closed', function() {
    mainWindow = null;
  });
});

package.json のエントリポイントも書き換えておきます。

{
  ...
  "main": "app/main.js",
  ...
}

実行

Electronの実行は electron コマンドに package.json か、エントリポイントとなるJavaScriptファイルへのパスを指定して実行します。

package.json はプロジェクト直下にあるので、 その場で以下のようにカレントフォルダを指定することで実行できました。

$ $(npm bin)/electron .

パッケージング

最後にパッケージングしてみました。

パッケージングには electron-packager コマンドを使用。

nw-builder に比べるとオプションが多い印象ですが、その分細かい制御ができるとも言えるでしょう。

$ $(npm bin)/electron-packager . 'Meld Launcher Electron' --out=dist --ignore='dist|node_modules|resources|src' --icon=resources/icon/nw --platform=darwin --arch=x64 --version=0.32.3 --overwrite

nw-builder と異なり、 --ignore オプションでビルドに含めないファイルやフォルダを指定できる点は良いですね。これでパッケージング後には不要となる src フォルダや、開発時のみの node_modules などを除外することができます。(Meld LauncherではBrowserifyで全て結合しているので、 node_modules 自体を除外しています)

NW.js版では同様のことをするために、テンポラリの build フォルダを作って、一度その中にパッケージングするファイルだけをコピーして、それをパッケージングするという一手間をかけていたのですが、それがいらなくなります。

アイコンも --icon オプションで .icns へのパスを指定すればOK。

npm run scripts

パッケージングがNW.jsと比べるとシンプルになったので、 npm run scripts の設定もNW.js版と比べると少なくできました。

...
"main": "app/main.js",
"scripts": {
  "start": "electron .",
  "babelify": "browserify -t babelify src/jsx/index.jsx -o app/js/bundle.js",
  "watchify": "watchify -t babelify src/jsx/index.jsx -o app/js/bundle.js -v",
  "dist:mkdir": "mkdir -p dist",
  "dist:package": "electron-packager . 'Meld Launcher Electron' --out=dist --ignore='dist|node_modules|resources|src' --icon=resources/icon/nw --platform=darwin --arch=x64 --version=0.32.3 --overwrite",
  "dist": "npm run babelify && npm run dist:mkdir && npm run dist:package",
  "clean": "rm -rf dist/*",
  "test": "echo \"Error: no test specified\" && exit 1"
},
...

f:id:umi-uyura:20150925234811p:plain

NW.jsとElectronの比較

環境の差はあるものの、アプリ本体のコードには手を加えずに、Electron版を作ることができました。

また、そもそものアプリが超単機能なこともあり、正直なところ、NW.jsとElectronの差のようなものもわかりませんでした。

今回のアプリのソースコードレベルでは、エントリポイントのJavaScriptがあるかないか、というくらいの違いしかありません。

ただ、なんとなくですが、個人的には以下のような使い分けを想像しました。

NW.js

エントリポイントがHTMLであることから、作ろうとしているものが普通にブラウザ内で完結できそうなものだったり、既存のWebサイトをアプリ化するといった目的ならNW.jsで充分そう。

Node.jsに詳しくなくても、Webのシングルページアプリケーションが作れるのであれば、Electronよりは少ない手順でそれをデスクトップアプリ化できるので、フロントエンドメインの人がアプリ化してみたいということであれば、こちらの方がとっつきやすいのかも。

Electron

Node.jsベースで一からデスクトップアプリを作るという目的なのであれば、Electronを採用しておいたほうが後々良さそう。

エントリポイントがJavaScriptということで、あくまでブラウザ上で動かすものを単独のブラウザに切り出す印象が強いNW.jsに比べると、Node.jsとの親和性という面でもElectronの方が高そうなので、OSとのやり取り(ファイルシステムなど)が重要なアプリになると、こちらの方が力を発揮しそうな感じがしました。

また、Electronの方が開発が活発なようですし、迷うのであればElectron使っておいた方が良いのかもしれないです。

まとめ

というわけで、移植版のソースもGitHubへ。

umi-uyura/meld-launcher-electron

NW.jsとElectronどちらを使うかという点については、作るものにもよりますが、都度使い分けるというよりは、自分に合った方を採用する、という感じでも良いのかな―という感想です。

自分の場合、そこまで複雑なアプリは作っていないので、たぶんどちらでも良いレベルなのだと思いますが、次に何か作るときには改めてElectronで作ってみようかなと思いました。

参考