ひ孫

犬のこととか書いていきたい

node-chromeを一日いじくり回してわかったこと

今朝方
http://www.moongift.jp/2013/02/20130216-2/
という記事を見つけた。
簡単にいえばnode.jsのUI部分をchromeに任せるようなものを作っちゃおう!って代物

過去にこういうことをやろうとしてchrome-extensionを作って挫折したりlocalhostの方をブクマしてたり色々やってた自分には朗報だったので早速node-chromeを触ってみた。

まずは普通にインストール

$mkdir node-chrome-sample
$cd node-chrome-sample
$npm init 
$npm install node-chrome

windowsだとインストール時にエラーが出る。ここについては後述するが、簡単に言うとエラー出ても問題なく動くっぽいのでこの場ではひとまず気にせず進める。

ソースを見てみる

https://github.com/hij1nx/node-chrome
メインとなるのはlib/index.jsだけでとても簡素。
肝になっているのは

  var args = [
    '--app=http://localhost:' + (opts.port || 8080) + opts.index,
    '--force-app-mode',
    '--app-window-size=' + (opts.width || 1024) + ',' + (opts.height || 760),
    '--enable-crxless-web-apps',
    '--user-data-dir=' + __dirname
  ];

このあたり。
なるほど。appモードとして起動するのか。その後のほうはサーバー起動をしてるみたい。
user-data-dirも別になっているのでchrome通常起動時とは独立した状態になっている。

デモを動かす

node-chrome/exampleにサンプルがあるのでとりあえずサンプルを起動

$cd node_modules/node-chrome/example
$node demo

ん?動かん。
とソースを見ると

       :
var opts = {
  runtime: "/Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome",
     :

あー・・・はい。すいません。macなんて高貴なもの持ってないんです。でも警告ぐらい出してくれても・・・
なので下記のように書き換え

      :
var opts = {
  runtime: process.env.LOCALAPPDATA+"\\Google\\Chrome\\Application\\chrome.exe",
}
    :

再度起動。
動く。うむ。問題ない。
runtimeを決めてくれないのはなかなか痛い。パッケージで配布するときに困りそう。

とりあえず自分の何かをおいてみる。

demoが問題なかったのでちょっと簡易に作ってみる

index.js

var opts = {
  runtime: process.env.LOCALAPPDATA+"\\Google\\Chrome\\Application\\chrome.exe",
  files: "./view",
  port: port,
  index:  "/index.html",
};
var fs = require("fs")
var client = require(".server.js");
cn(opts, function(websocket, chrome) {
  console.log("foo")
  chrome.on('exit', function (code) {
    process.exit(0);
  });
});

view/index.html

<html>
  <body>
   foo
  </body>
</html>

で、起動

$node index.js

・・・・あれ?console.log(foo)としているのでコンソール上になにかしら出力がありそうなのに黙りこくられる。なんで・・・?
コードをもっかい見てみる

    var wss = new WebSocketServer({ server: httpserver });

    wss.on('connection', function(ws) {
      callback.call(this, ws, chrome);
    });

ハッ!callbackがwebsocketコネクション確立後に来ている!なんてこった!*1 *2
確かにdemoもwebsocketコネクションやっとる。
なのでwebsocketへの接続を追加。

<html>
  <script>
  var ws = new WebSocket("ws://localhost:8080");
  </script>
  <body>
   foo
  </body>
</html>

これで再度起動してみたら動く。うん。動いた・・・
言いたいことはあるけどまとめにて後述。

小ネタとか。

色々やってみて気づいたこともあるのでメモ。

ウィンドウサイズ

ウィンドウ画面のサイズが起動時に固定されているので
画面をサイズを変えて閉じる→再起動 とすると元の大きさに戻ってしまう

'--app-window-size=' + (opts.width || 1024) + ',' + (opts.height || 760),

という部分をコメントアウトしてやると保存されるようになるのでこれはぜひオプションが欲しいところ。*3

ポート

きっとクライアントサイドで複数立ち上げたりすることもあるのでポートが固定だと困っちゃう。
こういうやり方で空きポート見つけて決めたい!
けどポートが変わったら変わったでclientサイドでwebsocketにどうやって渡せばいいの!?
ってなる。そんな時は

<script>
  var ws = new WebSocket("ws://"+location.host);
</script>

こうしてやればOK。わーい。
ちなみにhost名は今のところnode-chrome側でlocalhostに固定されているので変えられない

まとめ

  • 最終的に起動されたときの感じはすげえいい。素敵。
    • node.jsのコードをローカルで起動してブラウザでlocalhost:8080と入力せずその場で開いてくれる&chrome終了時にスクリプトも終了できるというのはとても素敵。
  • ただ結構不満がいろいろ・・・
    • モジュールの方針としてchromeをうまいこと起動するだけにしてはruntime必須とかでちょっと気が効いておらず、サーバー周りについてはwebsocket部分に関してはws強制で自由度効かなくて辛い感じがあってちょっとそのまま使うのは辛い。
      • どういうモジュールとして振りたいのか図りかねるので何かpull request投げづらい感強い。
        • 個人的にはchrome起動のモジュールとして特化してくれたらありがたい。
    • runtimeの指定をうっかり間違うと無言で黙りこくられて捗らない。
    • wsがwindowsでエラー出るしあんまり使いたくない。どうせローカルなんだしsocket.ioとかでも良いしなあ感。*4
    • callbackイベントがwebsocketのconnection確立後に発動してるのでchromeの起動に成功したけどサーバー起動に失敗した時はchrome終了するとかも出来ないの辛い。
  • コード量も大したことないので今のところprocess.spawnして自前するという選択肢もありそう。
  • 兎にも角にもchromeの起動部分は超有用なのでガンガン使って行きたい。

*1:これに気づくのに結構な時間を要した

*2:ちなみにコールバックの第一引数はerrorにすべきって偉い人が言ってた気がするけどどうなんだろう

*3:まあ固定したほうが良い事のほうが多そうだけど

*4:英語自信無いけどwsのエラーは多分この[https://github.com/einaros/ws/issues/155:title=ssue]にひっかかっている