Electronを探す日常

ごきげんよう皆様、精神が壊れてしまい休職中の erukiti ですが、[第2のドワンゴ Advent Calendar 2015 - Qiita](http://qiita.com/advent-calendar/2015/dwango2) の第20日目を5日ほど遅れてお送りします。遅れてすみません。

* [erukiti/rize-filer](https://github.com/erukiti/rize-filer)

## 今回の記事は…

さて、Electronの解説記事は割と多いですが、ちゃんと使えるアプリを作るために必要な情報は色々散らばっています。そこで今回はリゼ先輩＆シャロちゃんと一緒に一つ一つそれらを見ていきましょう。ごちうさ風味の書き方ですが内容は普通にElectronの記事です。

Electronに興味があるけれどよく知らない方は [第8羽 トランスペアレントプレイング・プレイヤーストーリー (ごちうさ Advent Calendar 2015) - Qiita](http://qiita.com/erukiti/items/0cf9741bbbff26446c1e) をまずはお読みください。Electron でちょっとした動画プレイヤーを作ってみるという記事でした。

### Gochiusa Driven Development

ラビットハウス勤務の[gomi_ningen](http://qiita.com/gomi_ningen)さんが提唱するGDD(Gochiusa Driven Development)という概念があります。今回この記事のために作成するアプリケーションは、GDDにおいて開発されたものになります。

ちなみにgomi_ningenさん、ラビットハウス勤務ですがなぜか[ドワンゴ Advent Calendar 2015](http://qiita.com/advent-calendar/2015/dwango)の[ニコニコ漫画アプリの中身、全部見せます！　〜iOSアプリ開発事例のご紹介〜](http://qiita.com/gomi_ningen/items/21089658b4b024ab21d3)という力作を書いてました。GDD! GDD!

### リゼ先輩のための記事です

「なー、シャロ。[前回](http://qiita.com/erukiti/items/0cf9741bbbff26446c1e) 教えてくれてなんとなくElectronがどんなものかはわかったんだけど、実際にMacのアプリを作って配布するにはまだ色々と足りないわけだよな？」「そうですね、アプリとしての体裁も整えないといけないですし、アプリとしてパッケージングして実行ファイルを作成する必要もあります。」「あと、Electronアプリって結構自動アップデート持ってるよな？」「はい。最近のアプリケーションはいちいちユーザーにアップデートさせるのはイケてないですけど、Electronなら大丈夫ですね！」

### リゼ先輩の想定

* ごきげんよう症候群を罹患している (罹患していなくてもかまいません)
* ポニーテール (ツインテールでもかまいません)
* HTML+JavaScript 少しわかる
* コマンドライン(CLI)にはアレルギーが無い
* 何らかのLL言語(Ruby, PHPなど)を触ったことがある
* [前回の記事](http://qiita.com/erukiti/items/0cf9741bbbff26446c1e) でElectronのアプリを作ろうとしてみた

### この記事を読めばできるようになること

* Macのデスクトップアプリケーションを作成できる
* Macのデスクトップアプリケーションを配布できる
* [ポエム - 業務システムに Electron を組み入れるという提案 - Qiita](http://qiita.com/erukiti/items/d10596b83d10de7bfe42) を実現することができる

Macを強調している理由としてはWindows/Linux向けはほんの少しの追加的なコードや仕組みが必要になってしまうからです。今のところまだMacで、Mac向けのコードしか試してないので、今回はMacのアプリケーションということにさせていただきます。いずれWindowsをバリバリ使ってる実業家の千夜ちゃんの為にココア先生のWindows対応記事も書きます。きっと。

## Nodeのバージョンを合わせる

「なー、シャロ。Electronアプリ作る時にネイティブモジュールで'Error: Module version mismatched. Expected 46, got 47.'みたいな感じで怒られてしまうときがあるんだけど、これどうしたらいいんだ？」「Electronに組み込まれたNodeのバージョンと開発に使っているNodeのバージョンに差違があるとこういうことが起きることがあります。最近のElectronはNodeの5系を組み込んでいるので、開発用のNodeが4系だと問題が出ちゃいますね。」

```js
console.dir(process.verions)

{ http_parser: '2.6.0',
  node: '5.1.1',
  v8: '4.7.80.23',
  uv: '1.7.5',
  zlib: '1.2.8',
  ares: '1.10.1-DEV',
  modules: '47',
  openssl: '1.0.2e',
  electron: '0.36.1',
  'atom-shell': '0.36.1',
  chrome: '47.0.2526.73' }
```

「たとえば、2015/12/21現在の最新である0.36.1は、Node 5.1.1を組み込んでいますね。なので、開発用のNodeも5系、5.1.1か5.3.0あたりを使うのが良いかと思います。」「前回の記事で推奨されていたndenvなら簡単にバージョンも切り替えられるし、やはりNodeのアプリ開発にはanyenv+ndenvが便利なんだな。」「そうですね、たぶん今後もElectronのバージョンアップでカジュアルに内蔵のNodeのバージョン上がりそうな気もします。」

「ただ、今回の記事ではアプリ配布を考えているんですが、ネイティブモジュールが絡むと話がややこしくなるというか、私もまだちゃんと把握できていないので、今回はネイティブモジュールを使わない方向性でいきたいと思います。」「了解だ。サンプルアプリでPOSIXモジュール使いたかったのだけど、仕方あるまい。」「何を作ろうとしてたんですか！？」

## 準備する

「リゼ先輩、一つ覚悟してください。ちゃんとアプリケーションを作り上げるためには色々な仕組みを利用する必要が出てきます。色々インストールすることになります。」「手加減してくれよ？」「なるべくややこしくならないように最小限にはします。」

```
$ npm init -y
$ npm i archiver browserify electron-connect electron-packager electron-prebuilt envify gulp gulp-sass gulp-shell vinyl-buffer vinyl-source-stream --save-dev
```

### package.json

「Electronで自分用のアプリを書いて遊ぶ位なら適当なpackage.jsonでも良かったんですが、github.comでソースを公開するならある程度しっかり書いた方がいいですね。」「ソースを公開しないなら適当でもいいのか？」「npmパッケージとして配布したい場合も必要になりますね。」

プロパティ |内容
-----------|-----
name       |アプリケーション/ライブラリの名前
version    |バージョン番号
description|簡単な説明 (npmを考えると英語が望ましい)
main       |実行時エントリポイント (Electronの場合も重要)
repository |github.comなどで公開してるならそのアドレス
keywords   |検索キーワード
author     |作者の名前
license    |ライセンス ([SPDX License List](https://spdx.org/licenses/)のIdentifierを指定しましょう。たとえばApache-2.0

「ライセンスはどうするのがいいんだ？」「OSI承認のものを選ぶのが望ましいですが、個人的にはその中でもしがらみの少ないBSD-3-ClauseやMITやApache-2.0あたりが優しさにあふれていていいですね。」「優しさか、それは大切だな。」「下手にGPLとか選ぶと面倒なことになりますしね。」

### gulp

「gulpって最近よく使われてるビルドツールだよな？」「そうですね、gruntが廃れてしまって今はビルドツールとしてはgulpを使うのがベターです。ただgulpも少し大仰なので、npmコマンドだけ使う人も増えています。特に複雑なビルドが必要無ければnpmで十分ですね。」

```json
{
  "main": "src/browser/app.js",
  "scripts": {
    "start": "electron ."
  }
}
```

「たとえばこういうpackage.jsonを書けば、`npm start`ってたたくだけで、Electronを起動できます。ただ、複雑なビルドが必要な場合はpackage.jsonでがんばるよりもgulpfile.js書く方が楽です。」

### パッケージングツール

「electron-packagerを使うのが標準的なパッケージングの手順ですね。」「ほうほう、Qiitaで検索すると[記事が色々ヒット](http://qiita.com/search?q=electron-packager&sort=rel)するな。」「そうですねQiita以外も含めて記事の数は多いと思います。[Electron を試すシリーズ](http://akabeko.me/blog/series/#series_electron)の記事なんかも参考になります。」

「簡単に言うとelectron-packagerはElectronのアプリケーションパッケージのリソースを置き換えるツールですね。必要なソースコード、アイコンなどを入れる感じです。注意点として、そのままだとJSのソースコードとかが丸見えだということでしょうか。」「なんか対策はあるんだろ？」「まず、カジュアルにJSのソースを隠す方法としてはasarという仕組みを使うことですね。[electron/application-packaging.md at master · atom/electron](https://github.com/atom/electron/blob/master/docs/tutorial/application-packaging.md) に説明が書いてますが、PHPのpharみたいなものでしょうか。electron-packagerの引数に``--asar``ってつけるだけで、asar化してくれます。」「なるほど、asarを知らない人なら中身を見ることができなくなるわけか。」「はい。その上でuglifyとか使って難読化をしていくことになるのが一般的なやり方になりますね。」

「ちなみにelectron-builderというのもあって、こっちはインストーラを作成してくれます。が、今回はちょっといったん置いておきましょう。」

### browserify

```js
const hoge = require('hoge')

hoge('fuga')
```

「このようなコード、Nodeでは一般的に見かけますがウェブブラウザ上ではそのままでは実行できません。そこで、browserifyやwebpackというツールを使うことになるんですが、簡単に言うと、requireを全部展開して一つのJSファイルに変換するというものです。」「なるほど、一つのJSになればブラウザ上でも普通に読み込めるな。」「最近はウェブフロント開発の世界でも、browserify or webpackが必須と言われる時代になりました。覚えておいて損はありません。」

「で、それとElectron開発はどう関係するんだ？」「実はElectronアプリって色々と肥え太る傾向があります。browserifyでJSを統合すると不要なファイルを入れなくて済むようになるので、たとえば150MBの実行ファイルが100MBにまで縮んだりします。」「縮んでも、100MBは大きいな…。」「chromium+Nodeという組み合わせのものなのでどうしてもその分大きさはどうしようもない部分があるんですよね…。」(筆者注: ミスって余計なものリンクしちゃってる可能性ありそう、後ほど追加調査＆アップデート予定です。)

「なーシャロ、browserifyとwebpackだったらどっちがいいんだ？」「今はbrowserifyの方が好みですね。webpackはかなり高度な事ができるんですがプラグインがgulpなどと被る・バギーなプラグインがあって変なハマり方をするから疲れるなどの問題がありますね。ただ、うまくやればJS以外の様々なリソースを統合できるので、いずれまた再挑戦しようかなとは思うんですが。」

### vendorディレクトリ

「リゼ先輩、今回npmでインストールできる以外のものを二つ導入したいと考えています。[Photon](http://photonkit.com/)と[Font Awesome](http://fontawesome.io/)です。これらをvendorディレクトリに入れようと思います。」「bowerとかは使わないのか？」「個人的にbowerの役目はもう終わったと思ってます。少なくともJSに限ればnpmに一本化されるようになりましたし、CSSやフォントを管理する方法としてもあまり筋がいいと思えません。」「手厳しいな。で、vendorディレクトリってなんか一昔前の管理方法に戻った気がしなくもないんだけど、面倒くさくないか？」「大丈夫です。vendorディレクトリの下に`git submodule`でサブモジュールを展開するだけですから。」

```
$ cd vendor
$ git submodule add https://github.com/FortAwesome/Font-Awesome.git
$ git submodule add https://github.com/connors/photon.git
```

「なるほど管理方法としてはいいとして、これをどうやって、プロジェクトに組み込むんだ？」「フォントファイルはただのコピー、スタイルシートはsassでimportします。」「スタイルシートに関してはもっとモダンなやり方は考えないのか？」「PostCSS+CSSNextっていうのは手ですがまだ早すぎるかなっていうのと、Photonなんかが実際にsass使ってたりするので、今回はこれでいこうかなと思います。将来的にはCSSNextとか積極的に使っていきたいですけどね。」

## gulpfileを書く

さて、ここからが長くなります。gulpfile.jsを色々と書いていくことになります。gulpの設定というかスクリプトはjs以外にもcoffee, TypeScriptなどでかけますが、JS (ES2015) でいいでしょう。

```js
"use strict"
const gulp = require('gulp')
const sass = require('gulp-sass')

gulp.task('build:sass', () => {
  gulp.src('src/style/*.scss')
      .pipe(sass())
      .pipe(gulp.dest('build/style/')

  gulp.src('./vendor/photon/fonts/*')
      .pipe(gulp.dest('./build/fonts/'))

  gulp.src('./vendor/font-awesome/fonts/*')
      .pipe(gulp.dest('./build/fonts/'))
})
```

「たとえばsassをコンパイルするタスクは上のように定義されます。`gulp build:sass`で実行できます。」「割と普通にJSのコードだな。gulp.srcから続くメソッドチェインで色々加工するわけか。」「そうですね。Nodeの[Stream](https://nodejs.org/api/stream.html)という仕組みですね。私も実は詳しくは知らないので今度まじめに[gulp/API.md at master · gulpjs/gulp](https://github.com/gulpjs/gulp/blob/master/docs/API.md)や[gulpjs/vinyl-fs](https://github.com/gulpjs/vinyl-fs)を読んでみようと思っているところです。とりあえずは`pipe`メソッドで加工して、最後に`gulp.dest`で出力先を指定すると覚えておけば事足ります。」

「build:sassのタスクの後半二つでは何をやってるんだ？」「フォントファイルをコピーしています。sassファイルだけだと相対位置の都合でPhotonやFont-Awesomeのアイコンが表示できなくなってしまいますから。」

### アプリのアイコンを作成する

[JavaScript - Yosemite風のアイコンが簡単に作れるツール作った - Qiita](http://qiita.com/trkbt10/items/b9218efca6d932ce849e) がとても良かったのでこれでアイコンを作ってみます。

「なーシャロ、やっぱりラビットハウスでバイトしている私としてはウサギアイコンがかわいいと思うんだがどうだ？」「いいですね！リゼ先輩にぴったりです！」「そうか？うれしいな。」

![うさぎアイコン](https://raw.githubusercontent.com/erukiti/rize-filer/master/icon/rabbit-base.png)

<div>Icons made by <a href="http://www.freepik.com" title="Freepik">Freepik</a> from <a href="http://www.flaticon.com" title="Flaticon">www.flaticon.com</a>             is licensed by <a href="http://creativecommons.org/licenses/by/3.0/" title="Creative Commons BY 3.0">CC BY 3.0</a></div>

```
$ curl ls8h.com/yosemite-icon/api -F "icon_image=@icon/rabbit-base.png" -F "base_color=#8877c0" > build/icon.png
```

「せっかくなのでリゼ先輩の制服の色に合わせてみました。ビルドのたびにAPIをたたくのは少し心苦しいのですが、APIしか公開されてないようなので…。」

![icon_512x512.png](https://qiita-image-store.s3.amazonaws.com/0/3134/1c870710-fc6b-0755-1222-155434d3944a.png)

```
$ sips -Z 16 build/tmp/icon.png --out build/tmp/app.iconset/icon_16x16.png
$ sips -Z 32 build/tmp/icon.png --out build/tmp/app.iconset/icon_16x16@2x.png
$ sips -Z 32 build/tmp/icon.png --out build/tmp/app.iconset/icon_32x32.png
$ sips -Z 64 build/tmp/icon.png --out build/tmp/app.iconset/icon_32x32@2x.png
$ sips -Z 128 build/tmp/icon.png --out build/tmp/app.iconset/icon_128x128.png
$ sips -Z 256 build/tmp/icon.png --out build/tmp/app.iconset/icon_128x128@2x.png
$ sips -Z 256 build/tmp/icon.png --out build/tmp/app.iconset/icon_256x256.png
$ sips -Z 512 build/tmp/icon.png --out build/tmp/app.iconset/icon_256x256@2x.png
$ sips -Z 512 build/tmp/icon.png --out build/tmp/app.iconset/icon_512x512.png
$ sips -Z 1024 build/tmp/icon.png --out build/tmp/app.iconset/icon_512x512@2x.png
```

「png画像のままだとアプリケーションのアイコンとしては使えないので、Macのicnsを作成しますが、必要なリサイズ処理をします。画像のリサイザには色々あるのですが今回はMacに標準で入ってるsipsコマンドを使いました。」

```
$ (cd build && iconutil -c icns tmp/app.iconset)
```

「最後にMacに標準で入ってるiconutilでicnsファイルを作成しました。これで準備はOKです。」

### jsをビルドする

```js
const browserify = require('browserify')
const source = require('vinyl-source-stream')
const buffer = require('vinyl-buffer')

gulp.task('build:js:browser', () => {
	browserify({
		entries: ['src/browser/app.js'],
		extensions: ['.js'],
    	ignoreMissing: true,
    	detectGlobals: false,
    	builtins: [],
    	debug: true,
	})
	.transform({
		NODE_ENV: 'production',
		GIT_HASH: git_hash,
		NAME: conf.name,
		VERSION: conf.version,
		ROOT: __dirname,
	}, 'envify')
	.transform('unassertify')
	.bundle()
	.pipe(source('app.js'))
	.pipe(buffer())
	.pipe(gulp.dest('build/browser'))
})
```

「これはbrowserプロセスのためのタスク定義ですが、rendererプロセスもふくめ、Electronのソースをbrowserifyする時にはいくつかオプションを付けないとエラーがでてしまったりします。たとえば require('electron')なんかは、ファイルとしてrequireされるわけではないのでbrowserifyからすると異物に見えてしまうんですがignoreMissingで無視させます。ほかのオプションはぐぐって出てきた文書から適当に取ってきました！」「すがすがしいな！」

「二つ指定されているtransformは環境変数経由でコンパイル前に情報を埋め込めるenvifyと、リリース時にassert文を無視させるための[twada/unassertify](https://github.com/twada/unassertify)ですね。」「なるほど、NODE_ENVにproductionを指定してこれはプロダクションビルドだというのをアプリに知らせるわけだな。」「そして、後者はt-wada神が作成した[twada/unassert](https://github.com/twada/unassert)をbrowserifyのプラグインにしたもので、[契約プログラミング - Wikipedia](https://ja.wikipedia.org/wiki/%E5%A5%91%E7%B4%84%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%9F%E3%83%B3%E3%82%B0)ができるようになります。」「事前条件や事後条件をassert文で書いておいて開発中はassertで落ちるけど、プロダクションモードでビルドするとassert文が消えるっていうことか。」「そうですね、開発中は特に[power-assert-js/power-assert](https://github.com/power-assert-js/power-assert)を使ってassertをpower-assert化しておけば開発が捗りますね。」

「後半の`pipe(source('app.js'))`と`.pipe(buffer())`は、gulpのストリーム構造に乗っ取っていないbrowserifyを対応させるためのものです。」

### リリースする

```js
const packager = require('electron-packager')
const archiver = require('archiver')

gulp.task('release:osx', ['build'], (done) => {
	let packagerConf = {
		dir: 'build',
		out: 'build/release/',
		name: conf.name,
		arch: 'x64',
		platform: 'darwin',
		version: '0.36.1',
		icon: 'build/tmp/app.icns',
		ignore: ['tmp', 'release'],
		overwrite: true,
	}

	if (process.env.ELECTRON_SIGN) {
		packagerConf['sign'] = process.env.ELECTRON_SIGN
	}

	packager(packagerConf, (err, path) => {
		let archive = archiver.create('zip', {})
		let output = fs.createWriteStream(`build/release/${conf.name}-${conf.version}.zip`)
		output.on('end', () => {
			done()
		})
		archive.pipe(output)
		archive.directory(`build/release/${conf.name}-darwin-x64/`, false)
		archive.finalize()
	})
})
```

「リリースするためのタスクです。packagerとarchiveの二つを使います。」

* build/ 下に必要ファイルを集めている
* build/release 下にリリースファイルが作成される
* ignore で tmp/ release/ を除外してリリースパッケージングする

「ignoreを使うのはちょっと微妙感あるな。」「すみません、確かにそうですね、tmp/ build/ release/ とうまくわけておけば良かったかもしれません。それは今後の課題とします。」

「ELECTRON_SIGNという環境変数は何に使うんだ？」「Macのアプリケーションは最近はMac Application Storeに登録しないアプリであっても、コードサイニングしておかないとセキュリティ設定に引っかかるようになっているので、それを避けるためのものです。[Mac アプリケーションの配布 - Apple Developer](https://developer.apple.com/osx/distribution/jp/)に従って署名する必要があります。すみません、作業履歴を取ってなかったので省略しちゃいますが、Apple様にお布施(年間1万円強)を支払ってIDを取得してから、手元でリクエストを発行、Appleのサイトにアップロード、Appleのサイトから証明書をダウンロード、キーマネージャーに証明書を登録、登録された名前をELECTRON_SIGNに設定するという作業が必要になります」「そ…そうかちょっと敷居が高いな…。」「そうですねぇ。痛い出費です。おっとこんなところに[erukiti のほしいもの](http://www.amazon.co.jp/gp/registry/wishlist/JQ28X5KI9FRY)なんてものが！」「浅ましい著者だな！」

「ゴホン、気を取り直して続きです。electron-packager を実行すると、build/release/ 下に、さらにappname-darwin-x64/ ってディレクトリが掘られて、その中にいくつかのファイルとappname.appができます。」「後半のarchiverはそこからさらにzipファイルを作成するわけだな。」「そうですね、appname.app(が作成されるだけだと配布にきわめて不便ですから、zipで固めてしまいます。」

```
$ ls -al build/release/rize-filer-darwin-x64/
drwxr-xr-x  6 erukiti  staff   204B 12 24 18:48 .
drwxr-xr-x  4 erukiti  staff   136B 12 24 18:48 ..
-rw-r--r--  1 erukiti  staff   1.0K 12 24 18:48 LICENSE
-rw-r--r--  1 erukiti  staff   1.1M 12 24 18:48 LICENSES.chromium.html
drwxr-xr-x  3 erukiti  staff   102B 12 24 18:48 rize-filer.app
-rw-r--r--  1 erukiti  staff     7B 12 24 18:48 version
```

## electron-connect を使ってライブリロードする

「electron-connectを使うと、ソースを書き換えたときにライブリロードすることができるようになります。ソースコードの変更検知は外部プロセスになるので、gulpfile に混ぜるのが得策です。」

```js
gulp.task('serve', ['build:sass'], () => {
	const electron = require('electron-connect').server.create({path: 'src/', spawnOpt: {env: {
		GIT_HASH: git_hash,
		ROOT: __dirname,
		NAME: conf.name,
		VERSION: conf.version,
	}}})
	electron.start()
	gulp.watch('src/browser/**/*', electron.restart)
	gulp.watch('src/renderer/**/*', electron.reload)
	gulp.watch('src/style/*.scss', ['build:sass', electron.reload])
	electron.on('quit', () => {process.exit(0)})
})
```

「さっきenvifyで組み込んでいたものをspawnOptで指定してるのか。」「そうですね、コマンドラインならいくらでも方法はあるんですが、gulpのストリームに乗せるよりかはこの方法で指定する方が楽でしたのでこうしています。」「gulp.watchで、browser/ 以下が編集されたらeletcron自体をリスタート、renderer/以下が編集されたらリロードと、リスタートとリロードを使い分けるんだな。それはいいとして、このelectron.on('quit') はなんだ？」「それはちょっと苦肉の策でして、electon-connectは、対象のelectronのプロセスが終了してもelectron-connect自体が終了しません。electronのプロセス終了時に'quit'メッセージを送るようにしています。あと、ブラウザプロセスやレンダラープロセスにクライアント側のコードを組み込む必要もあるので、そこらへんのコードも紹介します。」

```js
const client = require('electron-connect').client

global.appEnv = {
	title: process.env.NAME,
	version: process.env.VERSION,
	env: process.env.NODE_ENV || 'development',
	isDebug: process.env.NODE_ENV !== 'production',
	gitHash: process.env.GIT_HASH,
	root: process.env.ROOT,
}

app.on('ready', () => {
	win = new BrowserWindow(....)
	...
	if (appEnv.isDebug) {
		let cl = client.create(win)
		app.on('quit', () => {
			cl.sendMessage('quit')
		})
	}
})
```

「ちょっとしたハックなんですが、app.quit()を実行すると、Electronは'quit'イベントを発行するので、sendMessage('quit')をすることで、electron-connectのサーバープロセスにquitメッセージを送ることができます。」「appEnv.isDebugはNODE_ENVがproductionかどうかを見て判定してるんだな。」「本当はNODE_ENVがdevelopmentかどうかを見た方が良かったかもしれませんね。」

## アプリケーションとしての体裁を整える

「しゃーろー、gulpと格闘するの飽きたぞー。」「わわっ、すみません、確かにそうですね。では次はアプリケーションコードに移りましょう。」

### Photon

「今回導入した[Photon](http://photonkit.com/)ですが、OS Xアプリ風のGUIを簡単に作れるスタイルシートです。」「スタイルシートって事は制御自体はJSでがんばらないといけないわけか。まぁBootstrapみたいなものか。」「そうですね、タブを選択した時の挙動とかうまく記述する必要があります。」

統合されたcssファイルとsassファイル、どちらを使ってもいいですが、どうせならsassファイルを使う方がカスタマイズとかもできますし、今回はsassファイルを使います。

あと、WindowsとMacでうまく見た目を変えてくれるツールキットがあったような気がするんですが名前などを失念してしまったため、思い出したら追記します。

「使い方は[Photon · Getting started](http://photonkit.com/getting-started/)と[Photon · Components](http://photonkit.com/components/)を読めば大体はわかると思うんですが少し不親切なところがあるので、[photon/sass at master · connors/photon](https://github.com/connors/photon/tree/master/sass)を調べる事になると思います。と言ってもリポジトリを見ればわかるとおり、サイズ・数ともにほどほどなのですぐわかると思います。」「なるほど、まぁ静的なHTMLでモック作成しながら色々試してみるのが良さそうだな。」

「ところで、Photonのフォントと、Font-Awesomeのフォント、被るものも割と多くないか？」「まぁそうですね、実のところFont-Awesomeは必要なかったかもしれませんが、アプリを作成する上で便利なアイコンが多いので組み込んでいます。ロード画面に使えるスピナーアイコンとかもありますし。」

#### 基本構造

「基本構造は[Photon · Getting started](http://photonkit.com/getting-started/)を見ることになります。windowクラスをルートにしてペインを構成していくことになりますね」

```html
    <div class="window">
      <header class="toolbar toolbar-header">
        <h1 class="title">タイトル</h1>
      </header>

      <div class="window-content">
        <div class="pane-group">
          <div class="pane-sm sidebar">サイドバー</div>
          <div class="pane">
            <div class="padded-more">
              <h1>welcome to rabbit house.</h1>
              <p>
                ラビットハウスへようこそ
              </p>
            </div>
          </div>
        </div>
      </div>
    </div>
```

「まずアプリケーションのルートがwindowクラスです。bodyの直下に来ますね。」「いっそbodyにwindowクラスを指定するのはどうなんだ？」「ど…どうなんでしょうか？あまり行儀良くなさそうですが、実害があるかどうかはよくわかりません。」

「私が引っかかった罠としては pdadded-more を忘れて、パディングなしの間抜けな表示になったことでしょうか。」「paneとかはレイアウト用のタグで、中にパディングとかがない状態だから、そのままh1とかpとか流し込むことは考えられてないわけか。」

#### タイトル

```html
      <header class="toolbar toolbar-header">
        <h1 class="title">タイトル</h1>
      </header>
```

「このタイトル、実はタイトルバーの描画用のものなんですが、これだけだとあまり意味がなくて、`toolbar-actions` なんかとくみあわせて使うことが想定されていると思います。そうじゃなければ、OSの標準のタイトルバーを表示する方がいいですね。」

あと、ほかのcomponentに関しては、[Photon · Components](http://photonkit.com/components/) を見てわからないこと、ハマったことなどあれば是非コメントをください。追記します。

### アプリケーションメニュー

「Electronでメニューを作成する場合、[electron/menu.md at master · atom/electron](https://github.com/atom/electron/blob/master/docs/api/menu.md)を読む必要がありますが、An example of creating the application menu in the render process with the simple template API: のサンプルコード、一つトラップがあります。」「なんだ？Windows だと表示が微妙というやつか？」「あ、それもありますが、というか後ほど説明しますが、それ以上に大きなトラップがタイミングです。BrowserWindow を作成してからじゃないと中身が空のメニューができてしまいます。」「せめてエラーになるとかしてほしいところだな…。」「しかも、これ以前のバージョンだと問題のないタイミングで発生しちゃうんです…。」

ちらっとリゼ先輩が触れたWindowsだと微妙な件は、そのままのサンプルだとWindowsの場合、メニューの文字にアンダーラインが引かれないという問題ですね。これはまた詳しく実験してから記事にします。

実のところ、メニュー周りはそのまま扱うにはかなり面倒が多いので、何らかのライブラリ経由で操作した方がいいかもしれません。

### コンテキストメニュー

今回実験できず…。

### ダイアログを作る

「MacだとElectron組み込みのAboutダイアログを出すことができるんですが、Electronのバージョン表記しか出てこないんですよね。」「それはむなしいな。せっかく作ったアプリなのに、使ってる技術の一つのバージョンを見ても全然意味がない。」「で、どうするか？と言いますと、そのままダイアログ用のウィンドウを作成することになります。」

現状では、[electron/dialog.md at master · atom/electron](https://github.com/atom/electron/blob/master/docs/api/dialog.md) を読む限り、Electronのダイアログモジュールは、任意のダイアログを作れる感じではなさそうです。なので、ウィンドウを作成することになりますが、モーダルダイアログを作ることはできなさそうです。個人的にはモーダルダイアログはUIとして嫌いなので僕的には別に全然問題ないわけですが…。

先日書いた [Electronでフォント選択を実現する - Qiita](http://qiita.com/erukiti/items/2515cd5144829e60878f) という記事では Electronアプリでもフォント選択機能を実装できるよというのを書きました。設定ダイアログに組み込みたいものです。

## 最終的にできたもの

「シャロ、今回の成果を踏まえて [erukiti/rize-filer](https://github.com/erukiti/rize-filer) というサンプルアプリケーションを作ってみたぞ。」「先輩！？サンプルと言いつつ力作っぽい雰囲気が！」

ちなみにRxJS+WebRxという技術を用いています。最近のJS界隈の流行りだとReact使う人多いですが個人的にはRxJSのプログラミングスタイルが好きなのでWeRxを使っています。興味のある方は [RxJSと組み合わせるMVVMフレームワークWebRx - Qiita](http://qiita.com/erukiti/items/22c38f245f35fb7956f3) という記事を以前書いたので参照してみてください。

すみません、途中で力尽きてというか時間が尽きて中途半端な状態で公開してます。本当は、アップデートとかも組み込んでちゃんとしたアプリとして公開したかったんですが。

## まとめ

[erukiti/rize-filer](https://github.com/erukiti/rize-filer) を作りました。今後もアップデートしていく予定です。

ポニーテールリゼ先輩もツインテールリゼ先輩もどっちも魅力的です。

僕は休職してしまいましたが、療養しながらのんびりプログラミングを楽しんでいます。ご安心ください(?)。色々とElectronで作りたいネタがあるので、ゆっくり色々なアプリをリリースしていくつもりです。そんなElectronを探す日常をポイせず大切にしたいです。ElectronとNodeとウェブでisomorphicな世の中になればいいな。Electronアプリ開発でメシ食えればベストですね。

[前回](http://qiita.com/erukiti/items/0cf9741bbbff26446c1e) の記事と合わせて、皆様がElectronアプリを作成できるようになれば幸いです。疑問や「こう書いた方がもっとわかりやすい」みたいなものがあれば是非コメントなどをいただけたら記事を更新していきたいと思います。

それでは、ごきげんよう。