Electron + React.js でちょっとした Markdown Viewer を作成して少し知見が溜まったので宣伝とハマりどころなどまとめた (Electron編)


色々ネタがあるので分けて作成していきます。今回は Electron 編です。  
前回は gulpfile でのポイントを以下で紹介しています。
* [Electron + React.js でちょっとした Markdown Viewer を作成して少し知見が溜まったので宣伝とハマりどころなどまとめた (gulpfile編)](http://qiita.com/ma-tu/items/9daf81c0b34817a675bc)

よろしければこちらも見ていただけると幸いです。  
再び宣伝するのは、くどい気もしたのですが 前回の記事を見ていない人のほうが圧倒的に多いと思いますので再び少し宣伝。

# 作成したもの

**markcat**  
Electron製の Markdown Viewer

## 機能的な特徴

* **Intellij IDEA の Darcula 風の表示テーマも用意**
* 表示テーマの変更も可能
* Github Flavored Markdown
* コードハイライト表示
* 編集時の自動更新
* ドラッグ＆ドロップからの Markdown 表示
* (Windows) SendTo に配置することにより、エクスプローラーの「送る」からの Markdown 表示
* (Windows)ファイル関連付けを行うと、ダブルクリックからの Markdown 表示
* (Mac)このアプリケーションで開く からの起動。
* (Mac)ファイル拡張子で関連付けを行うと、ダブルクリックからの Markdown 表示

## 開発としての特徴

* Typescript + gulp + react + electron と今風の技術を利用しています。
* 実装はそれなりにシンプルなので学習にも最適
* とはいえ Markdown としての基本機能に追加して上記のこだわり機能を実装しています。

## 開発動機

Markdown は普段は Atom などのエディタを利用して作成しています。  
Markdown を見る時も同じく Atom を利用したり、Chrome の拡張機能を利用して表示したりしていたのですが、md ファイルをダブルクリックしたり、エクスプローラーのコンテキストメニューからサクッと見ることができないかと考えていました。  
そんな時 WEB+DB Press の React の記事の中で marked という markdown parser が紹介されていたのをきっかけに、また [rhysd/Shiba](https://github.com/rhysd/Shiba) を拝見して、自分用の Markdown Viewer を作ってみたいと思いました。

## ソースファイル

以下で公開しています。  
https://github.com/ma-tu/markcat

## 利用方法

GitHub の [Release](https://github.com/ma-tu/markcat/releases) にコンパイル済みのファイルを配置しているので、環境に合わせた zip ファイルをダウンロードして、適当なフォルダに解凍後 markcat を実行するだけです。
詳細は [こちらの README](https://github.com/ma-tu/markcat) を参照ください。

## 知見

#### Mac で「このアプリケーションで開く」で Electron モジュールを指定した場合のファイルパス取得方法

個人的にこの方法がなかなか分からず、かなりハマりました。  
Mac では「このアプリケーションで開く」で指定したファイルのパスは引数では渡ってきません。

[GitHub の electron/docs/api/app.md](https://github.com/atom/electron/blob/master/docs/api/app.md) の [Event: 'open-file' OS X] にちゃんと書いてあるのですが、なかなかここに辿りつけませんでした。  
詳細は上記のほうが確実かもしれませんが、MarkCat では以下のようにファイルパスを取得しています。

「このアプリケーションで開く」で Electronモジュールが実行されると、electron.app に 'open-file' eventが発火されます。その第２引数にファイルのパスが入ってくるのでそれを利用します。

```js:src/app.js
const app = electron.app;  // Module to control application life.

var openFilePath = null;
var openFileHandler = function(event, path) {
  event.preventDefault();
  openFilePath = path;
};
app.on('open-file', openFileHandler);

```

#### Windows で 「送る」を利用して Electronモジュールを開いた場合、および ファイルの関連付けを行って開いた場合のファイルパス取得方法

こちらはシンプルに 引数でファイルパスが渡されてくるので、それを取得します。  
ただしこのファイルパスは RendererProcess側 からは取得できません。  MainProcess側から取得する必要があります。  

MarkCat では以下のように RendererProcess側から remote を利用して MainProcess経由で取得するようにしています。

```js:src/renderer/remoteService.ts

const remote = require('remote');
const argv = remote.process.argv
const initPage = argv[argv.length-1]
```

#### MainProcess側のオブジェクトを RendererProcess側から参照する方法

これはもっと正しい方法があるのかもしれませんが MarkCat では以下のように対応しました。  

* mainProcess側から global オブジェクトに共有したい情報をセットします。
* rendererProcess側から remoteオブジェクトを利用して getGlobal 関数を利用して取得できます。

```js:src/app.js

//mainProcess側

var cfg = config.readConfig(process.execPath)
global.cfg = cfg
```

```js:src/renderer/services/thema.ts

//RendererProcess側

const remote = require('remote');
var cfg: config.Config = remote.getGlobal('cfg')
```

#### Electron のメニュー を動的に制御する方法

MarkCat で 現在選択されている thema を Menu の チェック で表現しています。そのため thema の変更を行った時にチェックの表示を制御する必要があります。

以下の方法で実現できました。
* mainProcess側の appオブジェクトから getApplicationMenu() でメニューを取得できるので、そこから対象の MenuItem を探して checked プロパティを変更しました。

```js:src/renderer/services/thema.ts

function changeThemaMenu(themaId: string) {
  const themaThemaMenu = remote.app.getApplicationMenu().items.find((item: Electron.MenuItemOptions) => item.id == 'thema')
  const themaNormalItem = themaThemaMenu.submenu.items.find((item: Electron.MenuItemOptions) => item.id == 'thema-normal')
  const themaDarkItem = themaThemaMenu.submenu.items.find((item: Electron.MenuItemOptions) => item.id == 'thema-dark')

  switch(themaId) {
    case 'thema-normal':
      themaNormalItem.checked = true
      themaDarkItem.checked = false
      break
    case 'thema-dark':
      themaNormalItem.checked = false
      themaDarkItem.checked = true
      break
  }
}
```

#### Electron 用アイコンファイル

正直以下の記事が完璧にまとまっているので、この記事を参照するのがわかりやすいです。  
[アカベコマイリ-Electron を試す 2](http://akabeko.me/blog/2015/10/electron-2/) ※ありがとうございました。

以下の手順で作成します。  
1. 1024×1024 のサイズのアイコンを png ファイルで準備します。  
デザインセンスに対する知見は私にはありません。頑張ります。  

2. 上記のアイコンを利用して、以下のサイズの png ファイルを作成していきます。  
512×512 / 256×256 / 128×128 / 64×64 / 32×32 / 16×16  

3. Windows向けアイコン作成  
手順は上記記事参照。  
[@icon変換](http://www.towofu.net/soft/aicon.php) を利用して 「マルチicon」を作成します。  

4. Mac向けアイコン作成  
手順は上記記事参照。  
準備しておいた png ファイルの名前を変更して icon_16x16.png 〜 icon_512x512@2x.png の合計10ファイルを用意して markcat.iconset というフォルダに保存したうえで Mac の Terminal から ```iconutil -c icns markcat.iconset``` を実行します。  

5. gulp での electron-packager の実行時に Windows の場合は .ico ファイルを Mac の場合は .icns を指定します。  

6. 各環境で build を実行して アイコンが登録されていることを確認します。  

## 終わりに

いかがだったでしょうか？  
同じところでハマっている人に、一人でもいいので参考になると幸いなのですが。。。  
できましたら MarkCat 使ってみてください。  
または、もっとすごい markdown viewer を作成して教えていただけると幸いです。

また MainProcess側のオブジェクトを RendererProcess側から参照する方法のより良い方法をご存知の方は是非コメントまたは Twitter の DM で教えてほしいです。