Real World Electron Development


~ Case of the Kobito, Markdown Editor
for YAPC Hackathon!

@mizchi / Koutaro Chikuba, Increments Inc

## About

- Node.js / Frontend Engineer
    - Single Page Application Specialist
- Kobito for Windows Developper
- Increments Inc (Providing qiita.com / Qiita:Team)
- Sorry, my English is not so good.

------

[YAPC::Asia 2015 Hackathon | Peatix](http://peatix.com/event/108475 "YAPC::Asia 2015 Hackathon | Peatix") の発表資料

ここで喋ることは一昨日急に決まったので(YAPC回るし)スライド作る時間なかった。ゆるして。

発表中に @benogle 氏に何度か質問しながら進行します。

## 前提知識

- 基本は @benogle 氏が喋ってくれたでしょう
- Electronには沢山の拡張があります。迷ったら[electron/docs at master · atom/electron](https://github.com/atom/electron/tree/master/docs "electron/docs at master · atom/electron") を読むこと

## Kobito とは

- Electron で作った Markdown Editor
- ![](https://i.gyazo.com/1998027ead22ac38468baaa8eab47821.png)
- デモする

## Electron採用理由

- Web系の会社がWPFに習熟したエンジニア雇うのは難しい
- nodeの豊富なライブラリ資産
- 環境固定でエッジなAPIでコードを書ける
    - IndexedDb
    - WebComponents
    - ServiceWorker
    - (WebAssembly)
- Chrome以外の動作確認をサボれて最高

## Electronでの開発に必要な足腰

- **発生した問題がnode/ブラウザのどちらかの問題か特定するだけの知識**
    - Electronのバグ！っていう前にChromeのバグを確認
    - [Issues - chromium - An open-source project to help move the web forward. - Google Project Hosting](https://code.google.com/p/chromium/issues/list "Issues - chromium - An open-source project to help move the web forward. - Google Project Hosting")
    - Electronのバグ！っていう前に各npmパッケージのIssueを確認
    - Electronの質問です！って言いながらブラウザ/nodeの質問しにくる人多すぎて、関係者はイライラしていると思う！！！！！
- あるとよい: nodeでの開発経験
    - nodeできることならなんでもできる
    - nodeで足りない機能はC++とV8 BridgeでAPI書く必要
        - だいたいある
        - node-libusbでusbを直接読んでIoTっぽいことをしてる人もいる
- あるとよい: ブラウザフロントエンドの経験
    - Single Page Applicationのノウハウ
        - ユーザーはElectronだからUIがHTMLでレスポンスが悪い、なんてのを許容しない
        - => 必然的にSPA
        - (業務アプリだと気にしないかも)
    - **HTML = GUI Toolkit**
- 確かにElectronの環境に依存したバグは存在する
    - node/browser対応のjsで `if (typeof global === 'object') {...} `  としてしまっているケース
        - 適当にforkしたりパッチ当てたりして対応(これは難しくない)
    - 一部のnativeモジュールのビルド(一時期sqlite3落ちてて困った)
    - webview tag is buggy

(今どんなバグがあります？)

- @benogle「AutoUpdaterが不穏」

## Electronを使う場合、同じ目的に対して複数の回答がありうる

- これどうやって実現するの？
    - HTTP Request の例
        - XMLHTTPRequest / Fetch API
        - `require('http')`
        -  ※ クロスドメイン制約は起動オプションで変更できる
    - modal window
        - Electron組み込みdialog モジュール
        - DOMでcssでposition: absolute
        - 場合によっては `input<type='file' .../>`
            - nodeのbuffer と browser の file オブジェクトの変換はちょっと面倒
    - ストレージ
        - IndexedDb / localStorage
        - HTML5 File API
        - `require('fs')` や sqlite3のORM
            - ネイティブモジュールを使うと各環境でnpm install しなおす必要があったので自分はIndexedDbを使った。楽だし。

## Electron本体の構成を読む

- コミットログみると開発者はzcbenz氏ほぼ一人
    - ドキュメンテーションは複数人
- ほとんど C++(chromium-srcへのパッチ)
- node v8 とのバインディングで少しだけJavaScript(coffee)
- Electron では node v8 と Chromium のメインループの透過的な統合処理が主
    - ChromiumのStandalone化だけなら atom/brightray 側を読む

[祝: Atom の Web Components 導入、ついでに Atom Shell の話。 - steps to phantasien](http://steps.dodgson.org/b/2014/12/21/atom-from-the-shell-side/ "祝: Atom の Web Components 導入、ついでに Atom Shell の話。 - steps to phantasien")

> そこで Atom Shell は Node.js のイベントループを二つのフェーズにわけ、 一部の処理だけを別スレッドに移すことで二つのイベントループを単一スレッドに押し込んだ。 具体的には libuv でファイルハンドルを待ちブロックする部分だけを専用のスレッドに追い出す。 そしてハンドルがシグナルされブロックから抜けた直後にメインスレッドへ処理を戻して 続きの処理、つまり JavaScript のコードを動かす。
> 要するに別スレッドで epoll_wait() して続きはメインスレッドで引き取る。 そしてメインスレッドの処理が終わったらふたたび別のスレッドでハンドルを待つ。

何が起こるかというと、実は透過的に見えててもクライアントからブラウザプロセスを使っててエラーを起こした場合ハングする。

- 「今度zcbenzが東京に引っ越してくるよ」
- 「Electron開発チームが一人しかいないんで、3~4人に増やしたい」(雇用の気配がする)

## Electronと似たような構成の環境

- nwjs/nw.js
    - セキュリティサンドボックスモデルが違う
    - あるインスタンスがnode由来のオブジェクトか、ブラウザ由来のオブジェクトかを厳密に区別する
        - Electronは区別せずシンプルで楽観的
    - 構成的に複数ウィンドウは出せない
- adobe/brackets-shell (no node.js) 
    - bracketsというエディタのシェル
    - `CEF3-based application shell for Brackets.` 
    - ソース公開されてないけど、たぶんVivaldi も似たような構成なんじゃないか？

## リリースしての辛み

### App Store問題

- App Storeに出せない
    - [Mac App Store · Issue #249 · atom/electron](https://github.com/atom/electron/issues/249 "Mac App Store · Issue #249 · atom/electron")
    - nodeの一部がMacOSXのサンドボックス違反があるっぽい
    - nw.js と共通の問題
 - パッチはあるけど大変 [Electron for the Mac App Store](http://www.saschawise.com/blog/2015/08/12/electron-for-the-mac-app-store.html "Electron for the Mac App Store")

![](https://i.gyazo.com/e7b0e67e6f37df4f4643c2b96fc653c9.png)

`There will probably be a 2nd version of Electron OS X posted for future releases that is MAS compatible`

とのこと。(質問: future っていつ頃？)

- @benogle「現状手が足りてない中、いろいろなプライオリティがある。いつとは言えないがやる」

### ウイルス誤検知があった

- リリース後、最初一部のウイルス検知ソフトウェアで引っかかった(symantec)
    - [SONAR.SuspBeh!gen3 Removal - Removing Help | Symantec](http://www.symantec.com/security_response/writeup.jsp?docid=2013-111415-1843-99&tabid=3 "SONAR.SuspBeh!gen3 Removal - Removing Help | Symantec")
- 問い合わせて問題がないことを確認して解除してもらった

(この件きいたことある？)

-  @benogle「自分は携わってないが、たしかそんな話があったと聞いている」

## Electron を自前でビルドする必要があるか？

- resedit.exeでプロセス名やアイコンを変えれる、とあったが自分はできなかった
- ついでにいろいろイジって回りたかった(IndexedDbの最大サイズとか)
- 適当に作るだけなら不要

- @benogle「electron-packagerなら大丈夫だよ」

## Electronのビルドでの苦悩

- 定期的にHEADが落ちてる
    - テスト通ってても特定プラットフォーム(Windowsでだけ)壊れてたりする
    - node / io.js のゴタゴタでリポジトリのurl変わって落ちたりもした
- Issue建てて壊れてんぞーって言うと直る(こともある)
- webview tag まわりは壊れがち
    - [electron/web-view-tag.md at master · atom/electron](https://github.com/atom/electron/blob/master/docs/api/web-view-tag.md "electron/web-view-tag.md at master · atom/electron")
    - webview tag 自体は Chrome拡張用の機能で、Electron側で機能を追加している [webview Tag - Google Chrome](https://developer.chrome.com/apps/tags/webview "webview Tag - Google Chrome")
    - **webviewタグを使った場合、必ずビルドごとに挙動を確認すること!**
    - 他にも秘孔あるかも

(怪しいモジュールどこかありますか。たとえばElectronじゃなくてChromeの問題だけどshadowRootが頻繁に描画に失敗するのは知ってる)


## Electronアプリ開発において参考になる実装

- atom/atom
    - bootstrap ~ window生成 ~ apmのplugin読み込み までは難しくない(コード量は多いが)
    - 編集バッファのコードを読むのは難しい(コードハイライター + DOM職人芸的な最適化)
    - 困ったらwindow.atomインスタンスのAPI探す [atom/atom.coffee at master · atom/atom](https://github.com/atom/atom/blob/master/src/atom.coffee "atom/atom.coffee at master · atom/atom")
    
- benogle/curve-app
    - 一昨日の発表で知った
    - 昨日というよりAtom開発者ならではのビルド環境
        - package.json の devDependencies を真似たい

(他におすすめのビルド構成ある？)
-  @benogle「エコシステムとして細かいタスクに分割しようという動きがある。今だとelectron-packager とか electron-rebuild が良い」

## Atom の編集はStandaloneで動かないのか？

- テキストオブジェクトのモデルはpure jsで独立している [atom/text-buffer](https://github.com/atom/text-buffer "atom/text-buffer")
- 質問した
    - エディタを抜き出すためにリファクタしていいか質問した [Standalone TextEditor · Issue #5001 · atom/atom](https://github.com/atom/atom/issues/5001 "Standalone TextEditor · Issue #5001 · atom/atom")
    - ブラウザで動かないモジュールを使ってる(Highlighting の正規表現に Oniguruma) からそのままじゃ動かない
    - 今はまだやるな時期が悪い
- 自分は結局CodeMirrorを拡張した

(これ予定ありますか)

- @benogle 「やりたいが手が足りない」

## Isomorphic(Universal) JavaScript

- どんな環境でも動くJavaScript、という考え方
    - Pure EcmaScript Implementation
    - 強制的にView触れないので、DDDにおけるdomain層の設計ギプスと考えればよい
- 環境ごとの制約を満たせばいつでもいろんなプラットフォームで展開できる
    - 手元に複数の実装がある
        - Electron - Mac / Win / Linux(Debian)
        - npm版(electron-prebuilt)
        - ブラウザ版 (Chrome / Firefox / Edge で動作確認)
        - Chrome Packaged Apps
    - 必要になったタイミングで必要な物をリリース可能
    - E2Eテストでselenium組み込みが面倒なときにブラウザ版で走らせたりした

# TIPS

## IndexedDb

- IndexedDbの普通のブラウザの上限は5MB程度だが、Electronでは増やしている
- Electron
    - `~/Library/Application\ Support/<app-name>/IndexedDb` に実体
- サーバーサイドのDBと違っていつアップデートされるか不明でクライアントマイグレーションをしないといけない
- mizchi/stone-skin
    - 自作のIndexedDbWrapper
    - IndexedDbマイグレーション機能含む

## remote

レンダラプロセスからブラウザプロセスを操作するには、たぶん正しいやり方は require('ipc') してpubsubする。ただ結構面倒臭い。

main.jsの

```
var BrowserWindow = require('browser-window')
```

は、クライントでの

```
var BrowserWindow = require('remote').require('browser-window')
```

と同じ。その気になればブラウザ側から別ウィンドウの生成まで可能。

## プロダクションでbrowserify

- kobito は production では browserify を使うようにしている
    - 小サイズ化(87MB -> 1.8MB)
    - (ちょっとした難読化)
- browserify できないモジュールは無視する
    - ポリフィルできない fs, http 等
    - Electron プリインの app, browser-window, ipc ...
    - global.require('...') と書いて解決(BK気味)
- 開発時はbrowserifyの時間をスキップしたかったんでそのまま
    - いまは 差分ビルドできる watchify あるんで問題ないかも

## asar

- (たぶん) atom shell archiver
    - tar のようにファイルを連結する(だけ)
    - Electronのfsモジュールは中からは foo.asar/index.html と中のパスを透過的に扱える
    - fsにはその為のパッチが当たっており、 `require('original-fs')` で元のfsを使うことも可能

## KobitoのUpdaterの実装

[electron/auto-updater.md at master · atom/electron](https://github.com/atom/electron/blob/master/docs/api/auto-updater.md "electron/auto-updater.md at master · atom/electron")

```
This module has only been implemented for OS X.
```

とのことなので自分で作った。

```
main.js
versions/
    - 1.1.0.asar
        - index.html
        - etc...
    - 1.0.0.asar
        - index.html
        - etc...
```

起動時は semverが一番大きい物を起動。起動後にバージョンサーバーを確認して、存在しなければ asar をダウンロードして再起動。

Electron本体の更新を伴うバージョンアップはメジャーアップデートでやるとする(年1~2回の予定)

(質問したが諸事情で省略)

## その他諸々

atom/Electron/docs 以下を穴があくほど読め！以上！
知見はQiitaのElectronタグにでも投稿してくれ！