Electron を試す 10 – Main プロセスのデバッグ

2017年8月23日 0 開発 ,

このシリーズ第一回node-inspector を利用した Main プロセスのステップ実行を紹介した。しかし node-inspector は現在、非推奨である。Node v6 でサポートされた --inspect を使用することになっている。

Node 同様に Electron も v1.7.2--inspect が追加された。つまり Node/Electron 共に単体でステップ実行を利用できる。現時点の latest version は v1.7.5 なので開発者としても正式にこの機能を採用する時がきた。

というわけで、Electron が提供するデバッグ機能についてまとめる。

サンプル プロジェクトは以下。

Chrome

Electron 公式サイトに Main プロセスをデバッグするための方法が掲載されている。

Debugging the Main Process | Electron

Electron CLI を実行する際に --inspect=[port] オプションを指定することで、Electron の Main プロセスと外部デバッガーを連携できる。例えば

electron --inspect=5858 your/app

のようにする。package.json の npm-scripts に定義する場合は

{
  "scripts": {
    "app": "electron --inspect=5858 src/"
  }
}

こんな感じになる。実際に Terminal から実行すると

$ npm run app

> electron-audio-player@1.4.0 app .../examples-electron/audio-player
> electron --inspect=5858 src/

Debugger listening on port 5858.
Warning: This is an experimental feature and could change at any time.
To start debugging, open the following URL in Chrome:
    chrome-devtools://devtools/bundled/inspector.html?experiments=true&v8only=true&ws=127.0.0.1:5858/ee7b65f0-dc0d-46f7-9d9e-997441981f52

外部デバッガーとして使用する Chrome 用の URL が出力される。これを Chrome のアドレス バーに入力してページを表示すれば DevTools が割り当てられる。

--inspect と Chrome

あとは普段 DevTools を利用しているように Sources タブからブレーク ポイントを貼ってステップ実行したり、変数の内容を参照するなどのデバッグが可能となる。

なお --inspect=[port]=[port] は省略可能。その場合は自動的にポート 5858 が選択されたものとして扱われる。省略するかはお好みで。開発環境によってはポート番号が競合するかもしれないので、変更可能であることを明示するため規定値でもそのまま指定しておくのもよいだろう。私はそうしている。

Visual Studio Code

Atom と並んで Web アプリ開発者に人気のテキスト エディター Visual Studio Code (以下、vscode) にはデバッグ機能が実装されている。これは vscode の大きな特徴であり、エディターにも関わらず IDE のようなデバッグを可能とする。

これは汎用的に設計してあり、様々な言語へ対応できるようになっている。Microsoft 謹製の TypeScript や C# は当然として Python、Ruby、PHP といった言語から Node (JavaScript) まで幅広くサポート。もちろん Electron からも利用可能。

Main プロセスを素の Node で実装している場合は上記の公式資料にある設定だけで使い始められる。しかし Babel などの Transpiler、Browserifywebpack といった Bundler を利用して JavaScript を変換しているならば対象となるコードを指定するために工夫が要る。

vscode のデバッグ機能と設定ファイルの詳細は以下を参照のこと。

launch.json

はじめに vscode 用の設定ファイルを定義する。vscode で読み込むプロジェクトのルートに .vscode フォルダーを作成し、その中へ launch.json というファイルを作成。内容は以下。

{
  // Use IntelliSense to learn about possible Node.js debug attributes.
  // Hover to view descriptions of existing attributes.
  // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
  "version": "0.2.0",
  "configurations": [
    {
      "type": "node",
      "request": "launch",
      "name": "Debug Main Process",
      "runtimeExecutable": "${workspaceRoot}/node_modules/.bin/electron",
      "windows": {
        "runtimeExecutable": "${workspaceRoot}/node_modules/.bin/electron.cmd"
      },
      "program": "${workspaceRoot}/src/js/main/Main.js",
      "outFiles": [
        "${workspaceRoot}/src/assets/main.js"
      ]
    }
  ]
}

configurations 内に設定を定義してゆく。

type はデバッグ実行のプラットフォームを表す。Electron 用の設定ではなく Node として実行する。request は新たにプロセスを起動する launch と起動されているものを利用する attach のいずれかを指定する。基本、launch でいい。name は役割で命名しておく。

${workspaceRoot} は特別な変数で、プロジェクトのルート フォルダーをあらわす。基本的に設定はこのフォルダー内で完結するだろうから、パスはこれで開始しておけばよい。

runtimeExecutable はデバッグ実行で使用する Electron のパスになる。Windows だけ起動方法が変わる (cmd.exe 経由になる) ため windows 内へ個別に定義が必要。

program はデバッグ実行のエントリー ポイントになるコードを指定する。Transpiler を利用しているなら変換元のエントリー ポイントになるファイルを指定する。変換先を指定しても動作するが、Bundler で複数ファイルを単一化しているとブレーク ポイントを貼りにくくなる。

outFilesprogram と関連付けるファイル一覧を定義。Transpiler を利用しているなら変換先のエントリー ポイントになるファイルを指定する。以前は sourceMapstrue に指定する必要もあったが、現在はデフォルトで有効になっている。Transpiler/Bundler と Source Maps を利用した最小の設定としては今回のもので十分。

他にもデフォルトが変更されて省略可能なものがあったり、outDir が deprecated で outFiles が代替になっているなどの変化がある。この記事ですら古びるかもしれないので、自分で設定する際は前述の公式資料を必ず確認すること。

Source Maps の注意点

私の環境は Babel + Browserify になる。元のコードは ES.next (例えば ES2015 の Modules など) で書いて、Babel により Electron 向けに変換している。これらのタスクは package.json の npm-scripts で組んでおり、デバッグに関連する定義は以下となる。

{
  "babel": {
    "presets": [
      ["env", {"targets": {"electron": "1.7"}}],
      "react"
    ]
  },
  "scripts": {
    "watch:js-main": "watchify -v -t [ babelify ] ./src/js/main/Main.js --exclude electron --im --no-detect-globals --node -o \"exorcist --base ./src/assets ./src/assets/main.js.map > ./src/assets/main.js\" -d"
  },
  "devDependencies": {
    "babel-preset-env": "^1.6.0",
    "babel-preset-power-assert": "^1.0.0",
    "babel-preset-react": "^6.24.1",
    "babelify": "^7.3.0",
    "browserify": "^14.4.0",
    "electron": "^1.7.5",
    "exorcist": "^0.4.0",
    "watchify": "^3.9.0"
  },
}

watch:js-main が browserify (watchify) によるファイル監視つきの JavaScript 変換になる。

browserify により変換された内容を exorcist に渡して Source Maps となる main.js.map を生成し、その後に変換結果となる main.js を出力している。

これらのうち、vscode のデバッグで重要なのは Source Maps にあたる map ファイルである。map ファイルは変換元ファイルのパスとコード行などが定義されている。vscode のデバッグ機能で map ファイルを使用する場合、変換元ファイルのパスは map からの相対にしなければならない。

exorcist の README によれば、オプションを何も指定しないと変換元ファイルのパスは absolute path (絶対パス) になる。実際には exorcist を実行したフォルダーをルートにした相対パスになるようだが、どちらにせよ map ファイルから見た変換元ファイルへのパスにはならない。

この状態で vscode のデバッグを開始すると変換元を見つけられず program に指定されたファイルをそのまま解釈してしまう。デバッガーは Node として動作するため、現時点で未対応の import 構文などを見つけると Syntax Error 例外でプロセスが中断される。

これを防ぐため exorcist の --base オプションに map と変換されたファイルの出力先フォルダーを指定。変換元ファイルへのパスがそこから見た相対となるようにした。

exorcist --base ./src/assets ./src/assets/main.js.map > ./src/assets/main.js

--base [PATH] の有無で map ファイルの sources が変化することを確認できる。

デバッグ実行

すべての設定が正しく定義されていれば vscode のデバッグ機能を利用できる。JavaScript 変換 & map ファイル生成された状態で vscode のメニューから「デバッグ」、「デバッグの開始」を選ぶとアプリが起動されるはず。

次に vscode でブレーク ポイントを貼りたいファイルを開き、対象行の行番号の左側をクリックすると赤い丸印アイコンが表示される。

ブレーク ポイント

この状態でその場所を通るような操作をアプリから実行すると、その箇所でプロセスが一時停止 (ブレーク) する。このとき vscode のサイドバーからデバッグを開いていれば、変数なども確認できる。

デバッグ実行

コード編集に使用しているエディターとデバッガーがひとつになっていることの便利さ (IDE 感) を実感。これはいい。

userData のパス問題

Electron でアプリ固有のデータを保存する場合は app.getPath('userData') で得られたフォルダーを利用することが多いだろう。このパスは Electron の Web Storage や IndexedDB などを保存する場所で、OS のユーザー単位かつアプリ単位で作成される。

userData には

The directory for storing your app’s configuration files, which by default it is the appData directory appended with your app’s name.

とある。この説明なら package.jsonname が参照されることを期待するけど、実際にはアプリの実行ファイル名が使用されるようだ。例えば AudioPlayer.appAudioPlayer.exe
であれば AudioPlayer というフォルダーになる。

Chrome によるデバッグでは Electron プロセスを単体実行して Chrome と関連付けているだけなので、userData は通常のアプリと同じ場所になる。しかし vscode のデバッグ実行だとフォルダー名は Electron 一択となる。

そのため複数のアプリでデバッグ実行している場合、データが競合するかもしれない。これに気付いたのは、これまで --inspect を利用して IndexedDB などに保存していたデータが vscode のデバッグだと読み込めていなかったから。userData のパスを調べたら、どのプロジェクトでも Electron になっていた。app.getName()Electron を返す。

かなり致命的な問題と思うのだけど、ざっと調べたかぎり vscode 公式のデバッグ関連資料や GitHub issues にはこれについての言及が見つからなかった。後で issue あげるかも。

いちおう app.setPath('userData', path) でパスの上書きは可能だが app.getName()Electron なのでアプリ名を抽象化しきれず、ハード コードが発生する。そもそも開発環境の都合でアプリ側のコードが左右されるのも好ましくない。

この件について情報をお持ちの方は、コメント欄や Twitter などで知らせていただきたい。

Atom v1.19.0 の IME 問題とダウングレード方法

2017年8月9日 0 開発

Atom v1.19.0 の更新があったので反映したら、IME 入力がおかしい。例えば「にほんご」と入力した場合、変換中は一文字目だけしか表示されず、順に「に」、「ほ」、「ん」、「ご」と切り替わってゆく。変換前の文字がひとつしか見えないので、まともに入力できない状態。あまりに致命的な問題なので issue があるだろうと探したら以下をみつけた (ついでにコメントしておいた)。

beta2 で見つかっていた問題だが、残念ながら安定版でも再現する。コメントを読むと影響があるようならダウングレードしてほしいとのこと。Atom の標準機能としてはダウングレードを提供していないので手動実行する必要あり。手順は以下。

  1. Atom が起動しているなら終了しておく
  2. Web ブラウザーでダウングレードしたい旧バージョンのリリースページにアクセスする
    • リリースされたバージョンは Releases に一覧あり
    • 例えば Release 1.18.0 にアクセスする
  3. リリース用イメージを入手する
    • Windows は AtomSetup.exe
    • macOS は atom-mac.zip
  4. 旧バージョンをインストールする
    • Windows は AtomSetup.exe を実行
    • macOS は atom-mac.zip を展開した中身の Atom.app をアプリケーション フォルダーに上書き
  5. Atom を起動してバージョンを確認

この手順で v1.18.0 にダウングレードして IME 問題が再現しないことを確認できた。

ダウングレードにおける懸念としてプラグインの互換性がある。例えばプラグインが最新の Atom に依存している場合、ダウングレードによって不具合を生じるかもしれない。今回のように minor レベルの更新なら大丈夫そうだけど、major の場合は要注意である。

安定版とはいえ致命的なデグレードが発生することは今後もありえるだろうから、今回の件はダウングレード手順を調べるよい契機だと考ることにした。

  • 2017/8/15: Atom v1.19.1 にて本記事の問題が修正されていることを確認

Redmine テーマ minimalflat2 v1.3.0 リリース

2017年7月16日 0 開発 , ,

Redmine 3.4 がリリースされたので minimalflat2 も対応した。

以下、開発メモ。

Stylus 定義を標準 application.css にあわせる

minimalflat2 の CSS は Stylus で記述して application.cssresponsive.css へコンパイルしている。Stylus の代表的な機能には透過的な変数参照、Mix-In、クラスのネストがあってこれまで便利に利用してきたのだけど、本バージョンからネストは控え目にした。

ネストによってクラス定義の冗長さは軽減される。例えば

#top-menu {background: #3E5B76; color: #fff; height:1.8em; font-size: 0.8em; padding: 2px 2px 0px 6px;}
#top-menu ul {margin: 0;  padding: 0;}
#top-menu li {
  float:left;
  list-style-type:none;
  margin: 0px 0px 0px 0px;
  padding: 0px 0px 0px 0px;
  white-space:nowrap;
}

のように同一 id やクラスを親としているものは

#top-menu {
  background: #3E5B76; color: #fff; height:1.8em; font-size: 0.8em; padding: 2px 2px 0px 6px;

  ul {
    margin: 0;  padding: 0;
  }

  li {
    float:left;
    list-style-type:none;
    margin: 0px 0px 0px 0px;
    padding: 0px 0px 0px 0px;
    white-space:nowrap;
  }
}

のようにネスト可能。ただしこれを徹底すると標準 application.css と定義位置が乖離してゆき、差分比較して追従するのが難しくなる。また、ときには位置の依存関係が崩れることで意図せぬ問題を引き起こす。

以上の理由からアイコン フォント用の疑似要素などを除き、標準 application.css の定義順へならうことにした。Redmine のバージョン更新があれば、最後に対応したものと最新版の application.css を差分比較して部分対応すればよい。従来もそうしていたのだが、今回の変更によりこれが更に容易となった。

Redmine v3.4 の Vagrant Box

テーマ開発における Redmine 上の動作確認は onozaty さん が提供している Vagrant Box を利用している。今回も Redmine v3.4 版が公開されたので、それを Vagrantfile へ指定するようにした。

Twitter 上で Vagrant Box リリースされないのかな?とつぶやいていたら迅速に対応していただけた。非常にありがたい。

Redmine v3.4.0 から間隔が空いたのは、これのすぐ後に致命的なバグを修正した v3.4.1 がリリースされたので、これを待っていたのかもしれない。

Redmine v3.4 プロジェクト一覧の謎

minimalflat2 では theme.js によりプロジェクト一覧をツリー上に開閉する機能がある。しかし Redmine v3.4 へ更新したら、これがうまく動作しない。そもそもプロジェクト名と説明文が横並びになったりする。

もしかして HTML の DOM 構造が大幅に変更された?標準テーマではどうだろう?と試したら、標準のほうでもそうなる。これは application.css にある

#projects-index {
  column-count: auto;
  column-width: 400px;
  -webkit-column-count: auto;
  -webkit-column-width: 400px;
  -webkit-column-gap : 0.5rem;
  -moz-column-count: auto;
  -moz-column-width: 400px;
  -moz-column-gap : 0.5rem;
}

という定義が原因だった。

複数カラムでグリッド状に表示するための定義らしいけど、responsive.css のほうは従来どおり縦一列である。表示幅が広ければグリッドで、ということなのだろう。しかしプロジェクト名に対して説明文が回り込んでしまうなど、好ましくない表示のされかたをする。また minimalflat2 としてはツリー表示によりプロジェクト一覧を整理する方針なのでグリッドにしなくても冗長さはおさえられる。

以上の理由から、この新しい定義は無効化することにした。プロジェクト一覧は表示幅にかかわらず、従来どおり常に縦一列になる。

モバイル用メニューのアイコン フォント対応強化

Redmine v3.4 ではアイコン画像を表示する DOM 要素に icon- を接頭辞とするクラスが統一的に指定されるようになった。指定されるだけで CSS に画像指定のないものも多数あるのだが、minimalflat2 としてはなるべくこれらにアイコン フォントを割り当てるようにした。

もっとも目立つのはモバイル用メニューのアイコンだろう。サイド メニューから移動されてきた項目以外はのきなみ icon- 接頭辞をもつため、かなり華やかになった。

モバイル用メニュー

簡易テスト用 HTML 更新

Vagrant なのか Redmine の設計なのか分からないが、VM のテーマ ディレクトリーと同期している場所で CSS が更新されても Redmine に反映されない。Web ブラウザーのリロード、スーパー リロードをしてもダメで、しかたなく vagrant reload している。

しかしこれは非常に時間がかかる。そのため Redmine の代表的な画面を静的 HTML として保存し、そこにコンパイルされた application.css などを読ませるようにして簡易テストできるようにしている。

今回も Redmine v3.4 用に HTML を保存し直して更新した。また従来のリポジトリー構成では

src/
├── debug_images/
├── favicon/
├── fonts/
├── images/
├── javascripts/
├── stylesheets/
├── stylus/
└── *.html

のように src/ 直下に全ファイルが並んでいて stylus のように開発で頻繁に書き換えるものと、静的でほとんど更新のないものが区別しにくかった。そこで

src/
├── assets/
│   ├── debug_images/
│   ├── favicon/
│   ├── fonts/
│   ├── images/
│   ├── stylesheets/
│   └── *.html
└── stylus/

のように静的リソースは assets/ へ置くようにした。最近の Web フロントエンドや Electron アプリ開発でもこのようにしている。Stylus がコンパイルした CSS と Source Maps は assets/stylesheets/ へ出力される。

assets/ がテーマとして動作するための構成をもったディレクトリーとなる。リリース用イメージ生成も、ここにあるものから必要なファイルをコピーすればよい。動的なファイルは Stylus のコンパイル結果ぐらいなので、cpx 用のフィルターも書きやすくなった。