こんにちは、古見澤です。
 アルバイト時代以来となるので、実に7年ぶりの投稿となります。今後共よろしくお願いします。
 久しぶりに技術畑に帰ってきたためブランクを取り戻すのに苦労していますが、鉄板の技術から胡散臭い(褒め言葉)新技術まで、色々なことが体験できる環境は楽しいですね。
さて、今回はプライベートで少し node-webkit を触る機会があったので、その紹介記事となります。
node-webkitとは
 node-webkitはChromium と node.js ベースで作られた、GUIアプリを動作させるランタイムです。
 アプリはHTMLやJavascriptで記述を行い、作ったアプリはLinux、Mac OS X、Windowsで動作が可能です。
配布元:https://github.com/rogerwang/node-webkit
 node-webkitを利用したアプリケーションのリストを見ると、どんなものが作れるかが伝わると思います。
 他のWebアプリとのマッシュアップに利用したり、ローカルファイルを編集・管理するために利用したり、音楽プレイヤーを作ったりと、利用用途は様々です。
まずは動かしてみよう
 まずは配布元にもあるクイックスタートを動かしてみましょう。
 githubにアクセスし、少し下がった所にあるDownloadsを確認します。
 目的やご自分の環境に応じたファイルをダウンロードします。私は新機能を試す目的もあったので v0.10.3をダウンロードしました。
 ダウンロードしたら解凍して、出てきたフォルダを適当なパスに配置します。
 フォルダ構成は以下のようになっているはずです。(解凍してできたフォルダはnode-webkitにリネームしました)
node-webkit
│  credits.html
│  ffmpegsumo.dll
│  icudtl.dat
│  libEGL.dll
│  libGLESv2.dll
│  nw.exe
│  nw.pak
│  nwsnapshot.exe
│  
└─locales
        ○○.pak
        :
        :
(※0.8.6をダウンロードした場合、localesフォルダが無かったりicudtlがbatではなくdllだったりしますが、今回は気にしないで大丈夫です。)
 さて、アプリを動作させるために、これから2つのファイルを作成します。
 ですがその前に、それらを格納するためのフォルダを準備しておきましょう。
 node-webkit直下に「app」というフォルダを作ります。(フォルダ名は何でも構いません。)
 そしてそのフォルダの中に「index.html」と「package.json」の2ファイルを作成します。
 それぞれのファイルの中身は以下の通りです。
index.html
<!DOCTYPE html>
<html>
<head>
  <title>Hello World!</title>
</head>
<body>
  <h1>Hello World!</h1>
  We are using node.js <script>document.write(process.version)</script>.
</body>
</html>
package.json
{
  "name": "nw-demo",
  "main": "index.html"
}
ここまで終了すると、フォルダ構成は以下のようになっているはずです。
node-webkit
│  credits.html
│  ffmpegsumo.dll
│  icudtl.dat
│  libEGL.dll
│  libGLESv2.dll
│  nw.exe
│  nw.pak
│  nwsnapshot.exe
│  
├─app              ←追加したフォルダ
│      index.html   ←追加したファイル
│      package.json ←追加したファイル
│  
└─locales
        ○○.pak
        :
        :
これでアプリケーション実行の準備ができました。簡単ですね。
 さて次はアプリケーションを実行します。
 実行のやり方はいくつかあるのですが、わかりやすいやり方としては以下の通り。
 ・エクスプローラーでnode-webkit直下を開く
 ・「appフォルダ」をドラッグ&ドロップで「nw.exe」に落とす
 アプリケーションが起動し、HelloWorld の文字とバージョンが表示されると思います。
 これで基本サンプルが動作しました。
(補足)コマンドラインからappフォルダをパラメータにnw.exeを実行でも構いません。
~~\node-webkit>nw.exe app
node.jsが使える
index.htmlの中にある
<script>document.write(process.version)</script>
の記述に「おや?」と思われた方がいるかもしれませんが、node-webkitではこの process.version のように
 htmlのscript要素にnode.jsを利用したプログラムを書く事ができます。
試しに index.html を以下のように変更(文字コードはUTF-8)してから、アプリを起動してみましょう。
index.html
<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="utf-8">
    <title>node-webkitを試す</title>
  </head>
  <body>
  <script>
    var fs = require('fs');
    
    fs.readFile('package.json', function (err, data) {
      if (err) throw err;
      document.write(data);
    });
  </script>
  </body>
</html>
(起動したアプリケーションがまだ開いている場合は、一度閉じてから再度起動させてください。)
 起動は先ほどと同じく、「appフォルダ」を「nw.exe」にドラッグ&ドロップです。
 node.jsのFile Systemを利用して、同階層においてあるpackage.jsonの中身をそのまま表示しています。
 サーバサイドで書くJavascriptのような記述をhtmlのscript内に書くのは違和感があるかもしれませんね。
 またこのpackage.jsonですが、このファイルの記述を変えることで起動するアプリの外観やサイズなどを制御することもできます。
 どのような項目があるかはwikiの Manifest format をご参照ください。
さてもう一例、今度はHTTPサーバを立ててみましょう。index.htmlを以下のように変更します。
index.html
<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="utf-8">
    <title>node-webkitを試す</title>
  </head>
  <body>
  <script>
    var http = require('http');
    var server = http.createServer();
    server.on('request', function(req, res) {
      res.writeHead(200, { 'Content-Type': 'text/html' });
      res.end("connect");
      new Notification("リクエストがありました。");
    });
    server.listen(8888);
  </script>
  </body>
</html>
変更が終わったら、またいつものようにappフォルダをnw.exeにドラッグ&ドロップです。
 (ドラッグ&ドロップが面倒だったらコマンドを書いたbatを作成し、実行してもいいですね)
 アプリケーションが起動している間、8888ポートで要求を受け付けた状態となります。
 適当なブラウザからアクセスしてみましょう。
 ブラウザにはconnectという文字が表示され、「リクエストがありました。」というデスクトップ通知が現れると思います。
 ※デスクトップ通知は、node-webkit v0.10.1 からサポートされました
この他、npmでインストールした追加モジュールも使用することができます。
Native UI APIを試す
 node-webkitには、Native UI APIというものが用意されています。
 その名の通り、ネイティブに動作するUIコントロールを操作するためのもので、これらのAPIを通してアプリ上にメニューを作成したり、クリップボードを利用したり、シェルを実行できたりします。
 以下は、APIを使った一例です。
index.html
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="utf-8">
<title>node-webkitを試す</title>
</head>
<body style="background-color:#fa5;">
  <h1>Native UI APIを使う</h1>
  <script>
    var gui = require('nw.gui');
    var win = gui.Window.get();
    var menu = new gui.Menu();
    var mSubMenu = new gui.Menu();
    //サブメニュー1
    mSubMenu.append(
      new gui.MenuItem({
        label: "sample 1",
        click: function() {
          gui.Shell.openItem('C:/Users/Public/Music/Sample Music/Sleep Away.mp3');
        }
      })
    );
    //サブメニュー2
    mSubMenu.append(
      new gui.MenuItem({
        label: "sample 2",
        click: function() {
          gui.Shell.openItem('C:/Users/Public/Music/Sample Music/Kalimba.mp3');
        }
      })
    );
    //メインメニュー(大きく3つ。1個目と2個目の間にセパレータを挿入)
    menu.append(new gui.MenuItem({ label: '音楽', submenu: mSubMenu}));
    menu.append(new gui.MenuItem({ type: 'separator' }));
    menu.append(
      new gui.MenuItem({
        label: "キャプチャ(別窓)",
        click: function() {
          takeSnapshot();
        }
      })
    );
    menu.append(
      new gui.MenuItem({
        label: "キャプチャ(出力)",
        click: function() {
          saveSnapshot();
        }
      })
    );
    //メニュー表示
    function popMenu() {
        menu.popup(70, 80);
    }
    //スナップショットを別ウィンドウで表示
    function takeSnapshot() {
      win.capturePage(function(img) {
        var popWindow = gui.Window.open('popup.html', { width: 480, height: 270 });
        popWindow.on('loaded', function() {
          var image = popWindow.window.document.getElementById('image');
          image.src = img;
        });
      }, { format : 'png' });
    }
    //スナップショットをファイルとしてサーバ上に保存
    function saveSnapshot() {
      win.capturePage(function(img) {
        require("fs").writeFile("out.png", img, 'base64', function(err) {
          console.log(err);
        });
      }, { format : 'png', datatype : 'raw' });
    }
  </script>
  <button onclick="popMenu()">menu</button>
</body>
</html>
そしてindex.htmlと同じ階層に、popup.htmlを以下の内容で作成します。
popup.html
<html>
  <head>
    <title>Popup window</title>
  </head>
  <body>
    <img id="image" alt="snapshot"/>
  </body>
</html>
一気にコード量が多くなりましたが、ほとんどがメニュー作成部分で中身は大した事はやってません。ゆっくり見て行きましょう。
この画面から使える機能は大きく2つ、「曲の再生」と「スクリーンショットの撮影」です。
 21行目と30行目、openItemは引数のファイルを開くShellのメソッドです。
 ここではWindowsに入っていたサンプルの音楽ファイルを指定しています。
 拡張子に紐付けられているプログラムで実行されるため、起動するアプリケーションは環境によって異なります。また、txtファイルを指定したらメモ帳やエディタが開いたり、xlsxファイルを指定したらExcelが起動したりします。
 62~68行目と72~78行目、capturePageはウィンドウ上の可視範囲をキャプチャするメソッドです。
 キャプチャした画像フォーマットはjpegかpngを選択でき、そのファイルをrawデータとして利用したり、保存場所のURIを使ってimg要素のsrcに利用が可能です。
 今回のサンプルプログラムでは、メニューから「キャプチャ(別窓)」を選ぶと別ウィンドウがポップアップし、そこにメインウィンドウのスクリーンショットが埋め込まれて表示されます。
 また「キャプチャ(出力)」を選ぶと、画面上では何も起こらないのでわかりにくいですが、メインウィンドウのスクリーンショットが、index.htmlと同じ階層に「out.png」というファイルが作成されます。
このサンプルでは、以下のAPIを利用しています。横の補足はこのプログラム内での用途です。
- Menu ボタン押下後に表示されるメニュー
 
- MenuItem メニューの見た目やクリック時の動作等
 
- Window 画面キャプチャー、別窓の表示
 
- Shell ファイル実行(音楽再生)
 
それぞれ色々な機能があるので、おもちゃの如く色々触りたくなってしまいますね。
アプリを配布する
 作ったアプリを配布する時は、フォルダ構成そのままを受け渡す方法もありますが、一つの実行ファイルにまとめてしまうやり方もあります。この場合は
 ・必要な資源をzip圧縮して1つにする。
 ・node-webkitフォルダの直下にあった「nw.exe」と、↑で圧縮したファイルを結合する。
 となります。
 今回の例で言うと、まず「app」フォルダの中身を1個のzipに固め、拡張子を「.nw」にします。
 (解凍時にpackage.jsonがルートに来るように圧縮する必要があるため、appフォルダ自体をzip化してはダメです。)
 
 次に、コマンドプロンプトからcopyコマンドを使い、node-webkitフォルダの直下にあった「nw.exe」と、今作った「.nwファイル」を、バイナリファイルとして(/bをつけて)結合します。
 これで完成です。
 OS環境や作成したプログラムの内容に応じて、実行ファイルと一緒にnode-webkitフォルダの中のファイルを同梱する必要があります。
 どのファイルを同梱する必要があるかは、配布元の解説を参照してください。
最後に
 いかがだったでしょうか。
 今回は紹介ということで動作は単純・見た目も地味なアプリでしたが、導入から動作、配布への流れくらいは伝えられたなら幸いです。
開発をやっているとわかるのですが、DevToolが普通に備えられているのはなかなか便利です。デバッグのやり方を重要視する方にとっては朗報かもしれません。
 jsファイルの呼び出しが、HTMLからなのかnode.jsからなのかがわかりにくい点が少し開発をやっていてつまづきましたが、Webサイトを作るような感じで自分好みのツールが作れるのはなかなか新鮮でした。
 特別に何かをインストールしたりすること無く動作するのも魅力的です。
ここに載せられなかった細かい決まり事や注意点、API情報などは配布元のwikiをご参照ください。