DartでElectronサンプル書いてみた(メインプロセス編)

メインプロセス側もDartで書いたElectronのサンプルコードです。
[レンダラ編](http://qiita.com/takutaro/items/9c382b264d837a94752d)の続編となります。

## メインプロセスでもDartしたい

今回、メインプロセス側のDartコードのJSへのトランスパイルはDDC(Dart Dev Compiler)を使ってみます。
nodejs向けの(比較的)リーダブルなJSコードを吐いてくれます。

コードやインストール方法は以下に置きましたので、ご覧ください。
https://github.com/takutaro/electron-with-dart-sample2

## remote使ってダイアログ表示するよ

[本家Electronのチュートリアル](https://electron.atom.io/docs/tutorial/quick-start/)をベースに、以下の様な簡単なアプリを作成します。

* Polymerのpaper-inputを使用した入力欄にテキストを打ち、下のボタンを押すと、ダイアログが表示されます。
* ダイアログ表示はメインプロセスとのプロセス間通信によるものです。remoteモジュールを使用しています。
* (ダイアログ表示の際にレンダラープロセスがブロックする為か、paper-buttonのアニメーションが途中で止まってしまいますね・・)

![ss.png](https://qiita-image-store.s3.amazonaws.com/0/34690/940cdd3f-f83d-3168-affd-a3f43daf3295.png)

## メインプロセスでDartするには？

Electronのメインプロセス側をDartで書く場合、JSとの相互運用を考える必要があります。ざっと見てみましょう。

main.dartをdart2jsでのトランスパイル対象外にします(DDCするため)。

```yaml:pubspec.yaml
(抜粋)
transformers:
(中略)
- $dart2js:
    $exclude: "web/main.dart"
```

最初に起動するmainのJSでは、electronモジュールを読み込み、DDCが吐くJSコードから参照可能な位置にぶら下げます。その後、DDCが吐くメインプロセス用JSをキックします。

```json:package.json
(抜粋)
"main": "bootstrap.js",
```

```js:bootstrap.js
'use strict';
global.electron = require('electron');
require('./main.dart.js').main.main();
```

いよいよメインプロセス用のDartコードです。
イベントに対するコールバックを登録したり、BrowserWindowを生成たりしています。
ほぼ前回(レンダラ編のmain.js)と同じ感覚で書けているかと。

```dart:main.dart
import "package:electron_with_dart2/electron_main.dart" as electron;

electron.BrowserWindow mainWindow;

void onReady() {
  mainWindow = new electron.BrowserWindow(width: 800, height: 400);
  String url = 'file://' + electron.app.getAppPath() + '/index.html';
  if ((electron.process.argv as List).contains('--pubserve')) {
    url = 'http://localhost:8080';
  }
  mainWindow.loadURL(url);
  mainWindow.on('closed', () => mainWindow = null);
}

void main() {
  electron.app.on('ready', onReady);
  electron.app.on('window-all-closed', () {
    if (electron.process.platform != 'darwin') {
      electron.app.quit();
    }
  });
  electron.app.on('activate', () {
    if (mainWindow == null) {
      onReady();
    }
  });
}
```

さて、上記のmain.dartが比較的シンプルに書けているのは、JSとの相互運用を裏方に任せているからです。
ElectronのAPIすべてをサポートするのは面倒なので、超サブセット版です。

```dart:lib/electron_main.dart
@JS()
library electron;

export "main/app.dart";
export "main/browserWindow.dart";

import "package:js/js.dart";
import "main/app.dart";
import "main/browserWindow.dart";

@JS("electron.app")
external App get app;
@JS("electron.BrowserWindow")
external BrowserWindow get browserWindow;

@JS("process")
external dynamic get process;
```

```dart:lib/main/app.dart
@JS()
library electron.app;

import "package:js/js.dart";

@JS("electron.app")
class App {
  external String getAppPath();
  external void on(String evnet, Function callback);
  external void quit();
}
```

```dart:lib/main/browserWindow.dart
@JS()
library electron.browser_window;

import "package:js/js.dart";

@JS("electron.BrowserWindow")
class BrowserWindow {
  BrowserWindow({width = 800, height = 600});
  external void on(String evnet, Function callback);
  external void loadURL(String url);
}
```

次にレンダラープロセス用のDartコードです。ボタン押下時にDailogを出すコードが見えますね。

```dart:electron_app.dart
@HtmlImport('electron_app.html')
@JS()
library electron_app;

import 'dart:html';
import "package:js/js.dart";
import 'package:web_components/web_components.dart' show HtmlImport;
import 'package:polymer/polymer.dart';
import 'package:electron_with_dart2/rndr/remote.dart' as remote;
import 'package:electron_with_dart2/rndr/dialog.dart' as dialog;

@PolymerRegister('electron-app')
class ElectronApp extends PolymerElement {
  ElectronApp.created() : super.created();

  @property
  String inputvalue = '誰か';

  @reflectable
  sayHello(Event e, var detail) {
    var win = remote.getCurrentWindow();
    var opts = new dialog.DialogOpts(
        type: 'info', buttons: ['OK'], title: 'Hello',
        message: inputvalue + ' さん', detail: 'こんにちは');
    dialog.showMessageBox(win, opts);
  }
}
```

上記のDartコードが比較的シンプルに書けているのも、JSとの相互運用を裏方に任せているからです。
やはりElectronのAPIすべてをサポートするのは面倒なので、超サブセット版です。

```dart:lib/rndr/remote.dart
@JS()
library electron.remote;

import 'package:js/js.dart';

@JS("electron.remote.getCurrentWindow")
external dynamic getCurrentWindow();
```

```dart:lib/rndr/dialog.dart
@JS()
library electron.dialog;

import "package:js/js.dart";

class DialogOpts {
  String type;
  List<String> buttons;
  String title;
  String message;
  String detail;
  DialogOpts({this.type, this.buttons, this.title, this.message, this.detail});
}

@JS("electron.remote.dialog.showMessageBox")
external void showMessageBox(dynamic win, DialogOpts options);
```

最後になりましたが、main.dartをDDCでトランパイルするコマンドが以下です。
このコマンド指定の場合、main.dartがimportしているapp.dartやbrowserWindow.dartも込みで1つのmain.dart.jsにまとまります(コンパイラに渡すオプション指定により分割コンパイルも出来ます。詳しくは[dev_compiler/USAGE.md](https://github.com/dart-lang/sdk/blob/master/pkg/dev_compiler/USAGE.md)へ)。

```bash:
$ dart $DDC_PATH/bin/dartdevc.dart --modules node -o main.dart.js main.dart
```

## 補足

DDCを使用するにあたり、コンパイルのシンプル化やdart_sdk.jsの配布方法で悩みました。
結局日和りましたが。