最近あちこちでElectronを使ったアプリが出てきたり、入門記事もちらほら見かけるようになってきました。
流行りものには乗っておこうということで、Electronをちょろっと使ってみました。
Electron
JavaScriptでの入門は、良質な記事がネット上に多々あるので、 ここではTypeScriptを使ってElectronアプリを作ってみたいと思います.
サンプルコード一式は、以下に置いておきました。
GitHubからcloneした後、npm install
してnpm start
すれば実行できます。
準備
Node.jsがインストールされていて、npmが使える状態を前提としてます。
自分はNode.js v4系を使ってますが、v0.12系でも同じようにできるかと思います。
プロジェクトの作成
まずは適当にフォルダ作ってnpm init
してpackage.jsonを作ります。
electronのインストール
続いてelectronを--save-devでインストールします。
npm install electron-prebuilt --save-dev
TypeScriptコンパイラ
続いてTypeScriptコンパイラもインストールします。
一応、開発環境のバージョンも構成管理できるように、グローバルなtscではなく、プロジェクト内にtscをインストールしておきます。
npm install typescript --save-dev
この、プロジェクト配下にインストールしたtscを使うので、コンパイルなどのコマンドはnode_modules\\.bin\\tsc
と呼び出します。
グローバルにインストールしたコンパイラを使う場合には、このコマンドをただのtsc
と読み替えて進めてください。
tsconfig.jsonの作成
TypeScriptのコンパイル時に使う設定をtsconfig.jsonに用意します。
プロジェクトのルート(package.jsonなどがあるのと同じ階層)に、tsconfig.jsonというファイルを以下の内容で作ります。
tsconfig.json
{"compilerOptions": {"target": "es5", "module": "commonjs", "sourceMap": true}, "exclude": ["node_modules", "dist"]}
設定の内容はおおまかに以下のような感じです。
- node_modulesは各種外部ライブラリのフォルダなのでコンパイル対象外
- distというフォルダもコンパイル対象外(まだdistフォルダは作ってませんが、後でリリース物の出力場所としてこの名前のフォルダを作ります。)
- 「files」プロパティを省略してるので、「exclude」の内容に一致するパス以外のすべての.tsファイルがコンパイル対象になります。
これでnode_modules\\.bin\\tsc
というコマンドでプロジェクト一式のコンパイルをする準備ができました。
tsdを使って型定義ファイルを用意
tsdのインストール
npmから以下のコマンドでtsdをグローバルにインストールします。
もうグローバルにインストールしてるような場合はここの手順はスキップ。
npm install tsd -g
型定義ファイルの取得
electron用の型定義ファイルを取得します。
DefinitelyTypedのページを見てみると、他のライブラリの型定義ファイルとは異なり、なぜかelectronのディレクトリには、型定義ファイルがやたらたくさんあります。
https://github.com/borisyankov/DefinitelyTyped/tree/master/github-electron
rendererプロセスと、mainプロセス用にわかれてるような感じですが、両方取得するとipcモジュールの定義が重複してエラーになってしまうので、ここではmainプロセス用の型定義のみ取得してます。
これ、うまくやる方法ないのかなぁ。それともmainプロセスとrendererプロセスは別々にコンパイルしたほうがいいのだろうか・・・ この辺はまだまだお悩み中・・・
tsd init tsd query github-electron-main -rosa install
作ってみる
これで準備が整ったので、ここからelectronを使ったアプリ作成に入ります。
ボタンを押したらalert表示するだけのサンプル
エントリポイントの作成
プロジェクトのルートフォルダに、main.tsというファイルを作ります。
そして、package.jsonに以下のようなプロパティを追加します。
{// 省略"main": "main.js", // 省略}
package.jsonのmainプロパティに書かれているjsファイルがアプリのエントリポイントとなります。
main.tsは以下のとおり。
import app = require('app'); import BrowserWindow = require('browser-window'); require('crash-reporter').start(); // メインウィンドウの参照をグローバルに持っておく。var mainWindow: GitHubElectron.BrowserWindow = null; // すべてのウィンドウが閉じられた際の動作 app.on('window-all-closed', function() {// OS X では、ウィンドウを閉じても一般的にアプリ終了はしないので除外。if (process.platform != 'darwin') { app.quit(); }}); app.on('ready', function() {// 新規ウィンドウ作成 mainWindow = new BrowserWindow({ width: 800, height: 600 }); // index.htmlを開く mainWindow.loadUrl('file://' + __dirname + '/index.html'); // ウィンドウが閉じられたら、ウィンドウへの参照を破棄する。 mainWindow.on('closed', function() { mainWindow = null; }); });
BrowserWindowのインスタンスを作り、loadUrlするとウィンドウを開くことができます。
型定義ファイルを使いTypeScriptで書いているので、コード補間も効くので快適にコーディングできます。
electronの各種モジュールやメソッドがうろ覚え状態なので、呼び出せるメソッドの候補などが出てくれるのは特に助かります。
表示内容の作成
続いて、main.jsから開かれるindex.htmlとこのページから読み込むindex.jsを作ります。
それぞれのファイルの内容は以下のとおり。
index.html
<!DOCTYPE html><html><head><metacharset="UTF-8"><title>Hello World!</title><scriptsrc="index.js"></script></head><body><h1>Hello World!</h1><hr/><buttononclick="hello()">Show Message</button></body></html>
index.ts
function hello(){alert('hello'); }
画面にはHello World!!と表示され、ボタンが一つ表示されているだけです。
このボタンを押すと、index.tsのhelloメソッドが呼ばれ、「hello」とだけ書かれたメッセージボックスが表示されます。
アプリの起動
アプリを起動するには、以下のようにelectron.exeに対し、実行対象のフォルダのパスを渡すことで起動できます。
node_modules\.bin\electron .
package.jsonがあるプロジェクトのルートがカレントディレクトリになってる場合には、「.」の指定でOK。
こんな風に、ボタンを押したらメッセージボックスが表示されます。
electronのdialogモジュールを使ってダイアログを表示してみる
せっかくなので、もう少し手の込んだことをしてみます。
先ほどはjavascriptのalertメソッドを使ってダイアログの表示をしてましたが、
今度はelectronのdialogモジュールを使ってやってみます。
electronのプロセスについて
dialogモジュールを使う前に、electronのアプリの実行プロセスについてさらっと解説。
electronのアプリは複数のプロセスで動作します。
プロセスは、BrowserProcessとRendererProcessの2種類があります。
エントリポイントとなるmain.tsはBrowserProcessで動作し、BrowserWindow.loadUrlで開かれたウィンドウのhtmlからロードされるindex.tsなどはRendererProcessで動作します。
↓のドキュメントなどにも書いてありますが、BrowserProcessとRendererProcessで使えるモジュールが異なっているので、今書いているスクリプトがどちらのプロセスなのかを意識しておく必要があります。
http://electron.atom.io/docs/v0.34.0/
これからダイアログ表示に利用しようとしているdialogモジュールはBrowserProcess用のモジュールのため、RendererProcessで動作しているindex.tsからはそのままでは利用できません。
RendererProcess側から、BrowserProcessのモジュールを使いたい場合には、require('remote').require(・・・・)
という感じで、remoteを経由してrequireします。
index.tsを以下のように修正します。
index.ts
var remote = require('remote'); var app = remote.require('app'); var BrowserWindow = remote.require('browser-window'); var dialog = remote.require('dialog'); function hello(){var options = { title: 'ダイアログのタイトル', type: 'info', buttons: ['OK', 'Cancel'], message: 'メッセージ', detail: 'hello'}; var win = BrowserWindow.getFocusedWindow(); dialog.showMessageBox(win, options); }
※補足
github-electron-rendererの型定義ファイルを読み込んでいないので、RendererProcessの方ではTypeScriptでの型情報が利用できてません。。。
github-electron-mainとgithub-electron-rendererの両方を同時に使おうとするとコンパイルエラーになるので。。。
そのため、ここではrequireの結果をvar
で受けて使っています。
ここまでの修正で、こんな風にelectronのdialogモジュールを使ったメッセージボックス表示ができました。
全体の動作のイメージは↓みたいな感じです。
もうちょい扱いやすくしてみる
開発時にもっと便利にコンパイル/実行できるように、npm経由で実行できるようにしたり、VSCodeから簡単に呼び出せるようにしてみます。
この辺は自分の開発スタイルに合わせてお好みの方法を取捨選択してもらえればよいかと。
package.jsonを使って起動
これで、npm run build
とやればビルドでき、npm start
とするとアプリを起動できるようになります。
またnpm start
でアプリを起動する前にはビルド処理も行うようになります。
package.json
{// 省略"scripts": {"build": "tsc", "prestart": "npm run build", "start": "electron ."}, // 省略}
VSCodeから呼び出す
ビルド設定
tasks.jsonを以下のようにしておくと、VSCodeから「Ctrl+Shift+B」でビルドできるようになります。
tasks.json
{"version": "0.1.0", // The command is tsc. Assumes that tsc has been installed using npm install -g typescript"command": "node_modules\\.bin\\tsc", // The command is a shell script"isShellCommand": true, // Show the output window only if unrecognized errors occur."showOutput": "silent", "args": ["-p", "."], // use the standard tsc problem matcher to find compile problems// in the output."problemMatcher": "$tsc"}
VSCodeから起動する設定
launch.jsonを以下のように書くと、VSCodeからF5で起動できます。
ただし、「OpenDebug process has terminated unexpectedly」というエラーが表示され、デバッガが正常に起動できません。
以下のページでも同じような現象の報告があります。
http://www.mylifeforthecode.com/a-better-way-to-launch-electron-from-visual-studio-code/
https://code.visualstudio.com/Issues/Detail/19279
デバッガの動作は、今後のアップデートとかいろんな情報が出てくるのを待ったほうがいいかな。
launch.json
{"version": "0.1.0", // List of configurations. Add new configurations or edit existing ones."configurations": [{// Name of configuration; appears in the launch configuration drop down menu."name": "Launch Electron App", // Type of configuration."type": "node", // Workspace relative or absolute path to the program."program": "main.js", // Automatically stop program after launch."stopOnEntry": false, // Command line arguments passed to the program."args": [], // Workspace relative or absolute path to the working directory of the program being debugged. Default is the current workspace."cwd": ".", // Workspace relative or absolute path to the runtime executable to be used. Default is the runtime executable on the PATH."runtimeExecutable": "node_modules\\.bin\\electron", // Optional arguments passed to the runtime executable."runtimeArgs": [], // Environment variables passed to the program."env": {}, // Use JavaScript source maps (if they exist)."sourceMaps": false}, {"name": "Attach", "type": "node", // TCP/IP address. Default is "localhost"."address": "localhost", // Port to attach to."port": 5858, "sourceMaps": false}]}
アプリのパッケージング
最後に、アプリをexeファイルとしてパッケージングしてみます。
パッケージングには、electron-pacakgerというモジュールを使います。
以下のコマンドでインストール
npm install electron-packager --save-dev
npmのscriptにパッケージング用のスクリプトを追加
npmのスクリプトでexe化できるようにしてみます。
package.jsonに以下のようなスクリプトを追加します。
package.json
{// 省略"scripts": {"build": "tsc", "prestart": "npm run build", "start": "electron .", "pack": "electron-packager . sample --out=dist --arch=x64 --platform=win32 --version=0.34.0 --overwrite --prune --ignore=dist --ignore=typings"}, // 省略}
これで、npm run pack
と実行すると、distフォルダにパッケージングした一式のフォルダが出来上がります。
distフォルダの中身は以下のようになります。sample.exeを実行すると先ほどのアプリが起動します。
補足
- --out
- 出力先のディレクトリを指定
- --ignore
- パッケージに含めないフォルダを指定
- ただし、このプロパティで特に指定しなくても、node_modules以下のelectron-prebuildやelectron-packagerは除外されます。
- --prune
- パッケージにする際に、devDependenciesのフォルダを含めないようにする
ここまでざっと見てみましたが、思ってたより簡単にできました。
最初の準備はちょっと面倒だけど、一度ひな形を作っておけば、サクっと作れそうですね。