Edited at

Real World Electron Development

More than 3 years have passed since last update.

~ 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 の発表資料

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

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


前提知識


Kobito とは


  • Electron で作った Markdown Editor


  • デモする


Electron採用理由


  • Web系の会社がWPFに習熟したエンジニア雇うのは難しい

  • nodeの豊富なライブラリ資産

  • 環境固定でエッジなAPIでコードを書ける


    • IndexedDb

    • WebComponents

    • ServiceWorker

    • (WebAssembly)



  • Chrome以外の動作確認をサボれて最高


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



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


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


そこで 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問題

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

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



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


ウイルス誤検知があった

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


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


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


  • resedit.exeでプロセス名やアイコンを変えれる、とあったが自分はできなかった

  • ついでにいろいろイジって回りたかった(IndexedDbの最大サイズとか)

  • 適当に作るだけなら不要


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



Electronのビルドでの苦悩


  • 定期的にHEADが落ちてる


    • テスト通ってても特定プラットフォーム(Windowsでだけ)壊れてたりする

    • node / io.js のゴタゴタでリポジトリのurl変わって落ちたりもした



  • Issue建てて壊れてんぞーって言うと直る(こともある)

  • webview tag まわりは壊れがち



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


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



  • atom/atom


    • bootstrap ~ window生成 ~ apmのplugin読み込み までは難しくない(コード量は多いが)

    • 編集バッファのコードを読むのは難しい(コードハイライター + DOM職人芸的な最適化)

    • 困ったらwindow.atomインスタンスのAPI探す 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

  • 質問した


    • エディタを抜き出すためにリファクタしていいか質問した 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

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タグにでも投稿してくれ!