こんにちは。Ameba事業本部の杉本と申します。

業務では「天下統一クロニクル」というチームでフロントエンドのディベロッパをしています。

今回は、「node-webkit」という一風変わったアプリケーションを紹介させていただきます。

私達は普段、gruntといったnode.js製のツールを使ってJavaScriptの結合や圧縮、画像の減色といった処理を自動化していますが、node-webkitはこれらnodeのモジュールを使ってGUIアプリケーションを作れるツールです。nodeの資産をそのまま利用できるので、私のようなディベロッパには嬉しいですね。

今日は、node-webkitの概要と仕組みからアプリケーションの作り方までを簡単に紹介させていただきます。
node-webkit

node-webkitとは

node-webkitは、Chromiumの機能を内包したGUIアプリケーションを実行します。インターフェースもHTML5/CSS3、クライアントロジックもJavaScriptで記述することができます。イメージとしては、Adobe AirのHTML5バージョンといったところでしょうか。まずは、以下のコードを見てください。node-webkitで実行するサンプルコードです。

<!doctype html>
<html lang="ja">
<head>
<title>Sample node-webkit app</title>
<meta charset="UTF-8">
<script>
var fs = require('fs');

document.addEventListener('DOMContentLoaded', function() {
var contents = fs.readFileSync('./sample.txt');

document.body.innerHTML = contents;
});
</script>
</head>
<body></body>
</html>


えっ、と思われた方もいるかもしれません。上のコードにはDOMのコードとnodeのコードが混在していますね。nodeのFileSystemモジュールをロードし、ローカルファイルの内容を読み込み、そのままinnerHTMLに代入しています(もちろん、本来はエスケープ処理が必要です)。

node-webkitでは、DOMとnodeの機能を併用することができるんです。もちろん、HTMLの装飾もタグでCSSへのリンクをつければスタイリングされますし、GUIのクリックイベントも従来どおりのJavaScriptでイベントハンドリングを行うことができます。

さらに、Chromiumのを内包したWebkitベースのアプリケーションとなるので、Windows/GNU Linux/Mac OSのどのプラットフォームで動作させることが可能で(それぞれのプラットフォーム用のビルドは必要です)、Webkitの機能をほぼ全て使うことができます。WebWorker、WebGL、localStorage、IndexedDB、SQLDataBase、WebSocketなど、互換性を気にする必要もありません。また、HTML側にもファイル操作関連の機能拡張がなされています。

少し興味が出てきたでしょうか?このコードをGUIアプリケーションとして動作させるのもとても簡単です。以下では、一番手軽で簡単なMac OS用のアプリケーションを例に上げます。また、node.jsが実行できる環境も用意しておきましょう。

node-webkit本体のインストール

npmからインストールできます。ターミナルから適当なディレクトリ内でnpmモジュールとしてインストールしましょう。

$ npm install nodewebkit


少し時間がかかりますが、これでインストール完了です。
(npmでインストールされるnode-webkitはやや古い場合があります。最新版を使用したい場合は、githubのページから最新のパッケージがダウンロードできます。)

アプリケーションの記述

node-webkitでは、メインで実行されるhtmlファイルと、package.jsonだけで動作可能です。サンプルのアプリケーションは以下のようになります。よくあるnodeのプロジェクトのようですね。

app - index.html
|- package.json
|- sample.txt
|- node_modules


index.htmlは先に示したコードを記述し、"Hello, node-webkit!"という文字列を書いたテキストファイルをsample.txtとして配置します。続いて、pacakge.jsonを作成し、node-webkit用の設定を記述します。今回は以下のように記述しました。

// package.json
{
"name": "Sample-node-webkit-app",
"main": "index.html",
"scripts": {
"start" : "node_modules/nodewebkit/bin/nodewebkit"
}
}


package.jsonのフォーマットも、npmで生成されるものと互換性があります。
ここまでできたら、ターミナルから以下のコマンドを実行します。

$ npm start


GUIアプリケーションが起動し、"Hello, node-webkit!"という文字が表示されたら成功です!



HTMLファイル一枚だけでGUIアプリケーションが作れてしまいました。また、Chromiumベースなので、DevToolもChromeと同じく優秀です。GUI上の歯車のマークをクリックすると、Chromeでお馴染みのDevToolが起動します。
さらにさらに、Consoleのペインでは、通常のJavaScriptのデバッグに加えて、nodeのコードも動かすことができます。ブレークポイントを使ってnodeのスクリプトのデバッグが行えるのも嬉しいですね!



続いて、GUIウインドウの設定を変更してみます。package.jsonに「window」というプロパティを追加し、設定を記述することで、ウインドウ制御を行うことができます。(アプリケーションの終了は、ターミナル上でCtrl+cを押してください)

// package.json
{
"name": "Sample-node-webkit app",
"main": "index.html",
"scripts": {
"start" : "node_modules/nodewebkit/bin/nodewebkit"
},
"window": {
"title" : "Sample-node-webkit-app",
"width": 800,
"height": 600,
"toolbar": false
}
}


この設定で再度起動すると、今度はサイズが大きくなり、ツールバーのないGUIアプリケーションっぽい感じになりました。他にも、フルスクリーンの設定やキオスクモードの設定もあるので、詳細な設定項目は、Manifest formatのページを参照してください。

アプリケーションのデプロイ

さて、このままではnpm startのコマンドからしかアプリケーションが起動できません。せっかくなのでアプリケーション化し、アイコンのダブルクリックで起動できるようにしてみましょう。
まずは、npmでインストールしたnode-webkit本体を取り出します。
npmでインストールしたnode-webkitの場合、本体は、node_modules/nodewebkit/nodewebkit/node-webkit.appにあります(時計のようなアイコンです)ので、これを適当な場所にコピーします。



そして、アプリケーションの中身を表示して、Contents/Resourcesディレクトリの中に「app.nw」という名前のディレクトリを作成します。この名前は規定なので注意してください。

node-webkit.app
|-Contents
|-Resources
|-app.nw ←ここに作成


あとはapp.nwの中に、先に作成したindex.htmlとpackage.json、sample.txtを入れるだけで完了です(別途npmでモジュールを追加している場合は、node_modulesもコピーします)。試しにアプリケーションをダブルクリックしてみましょう。ちゃんと起動できれば、GUIアプリケーションの完成です。

また、毎回このような作業を行うのはとても手間なので、node-webkitのインストールからデプロイまでをgruntのタスクで自動化してしまいます。

ysugimoto / grunt-node-webkit-builder

上のリポジトリをnpmでインストールし、以下のように設定を書けばgruntコマンドでデプロイできます。(現在はMac OS用に作ってあります)

module.exports = function(grunt) {

'use strict';

grunt.initConfig({
nodewebkit: {
src: ['package.json', 'index.html', 'sample.txt'],
name: 'Sample-node-webkit-app'
}
});

grunt.loadNpmTasks('grunt-node-webkit-builder');
grunt.registerTask('default', ['nodewebkit']);
};


nodeモジュールによる拡張性

node-webkitで作るGUIアプリケーションではnodeのモジュールが利用できると説明しました。これにより、シェルの実行などはchild_processを使えますし、httpモジュールを使えばビルトインサーバの起動やHTTP通信も可能です。また、Webkitだけでもできることは増えてきていますが、足りない機能をさらにnpmパッケージをインストールして使うことができます。

例えば、zipやtarの解凍などもnpmのモジュールを使えば簡単に実装できます。今までCLI上で実行されてきたnodeの機能がGUI上でも使えるとなれば、考えられるアプリケーションの幅も広がるのではないでしょうか。

シェルを実行するサンプルアプリケーション

例を考えてみましょう。Macをセットアップするとき、私はパッケージマネージャにHomeBrewを使いますが、ターミナルからインストールを行う作業はシェルに慣れていない方だと少々ハードルが高いかもしれません。そんなとき、インストールから実行までのシェルを実行するように書いたnode-webkitアプリを使ってもらえれば、ボタンひとつでインストールできるのではないでしょうか。

以下のようなHTMLファイルを用意して、GUIアプリにして配布すれば、どなたでもHomeBrewがインストールできます。

<!doctype html>
<html>
<head>
<title>BrewInstaller</title>
<meta charset="UTF-8">
<style>
#install {
display: inline-block;
padding: 5px 10px;
border-radius: 5px;
background: #28cccc;
border: none;
text-align: center;
width: 100px;
font-size: 20px;
}
p {
text-align: center;
}
</style>
</head>
<body>
<p>HomeBrew Installer<br>
<button id="install">Checking...</button>
</p>
<script>
var exec = require('child_process').exec,
fs = require('fs'),
btn = document.getElementById('install'),
installing = false;

// すでにインストール済みかどうかチェック
exec('which brew', function(err, stdout, stderr) {
if ( ! err ) {
if ( stdout !== '' ) {
// すでにインストールされている
btn.classList.add('installed');
btn.textContent = 'Installed';
return;
}

btn.textContent = 'Install';
btn.addEventListener('click', doInstall, false);
}
});

function doInstall(evt) {
if ( installing ) {
return;
}
installing = true;

var bashProfile= process.env.HOME + '/.bash_profile',
proc;

// .bash_profileが存在すればパス追記
if ( fs.existsSync(bashProfile) ) {
fs.appendFileSync(bashProfile, 'PATH=":/usr/local/bin"\n');
} else {
fs.writeFileSync(bashProfile, '#!/bin/sh\n\nPATH=":/usr/local/bin"');
}

// homebrewのインストール
proc = exec('/usr/bin/ruby -e "$(curl -fsSL https://raw.github.com/mxcl/homebrew/go/install)"', function(err, stdout, srderr) {
if ( ! err ) {
// .bash_profileを再読み込み
exec('source ' + bashProfile, function() {
btn.textContent = 'Installed';
btn.removeEventListener('click', doInstall);
);
}
});

proc.stdout.on('data', function(msg) {
// expectのように入力を促すstdoutに対してCRLFを送信
if ( /PRESS\sENTER/.test(msg) ) {
proc.write('\r\n');
}
});
}
</script>
</body>
</html>


アプリケーションを起動すると以下のような小さなGUIが起動します。Installのボタンを押すと、シェルが実行され、HomeBrewがインストールされます(環境によって上手くいかないケースもあります)


HomeBrewのインストール後にさらに"brew insall git"といったコマンドを実行するように設定しておけば、開発環境のセットアップもGUIからボタン一つで行うことができるでしょう。
このようなシンプルなシェルの実行をラップしたGUIアプリケーションを手早く作るのにnode-webkitは最適だと思います。

SPAの練習に

また、SPA(Single Page Application)の練習にも丁度よいと思います。一枚のHTMLファイルに対して、JavaScriptからWebSocketやAPI通信、ファイルI/Oから動的にデータを書き換えるような処理をnode-webkitで作ってみるのも面白いと思います。

公式サイトにもデモアプリが幾つか掲載されているので、是非試してみてください。

zcbenz / nw-sample-apps

また、SPAの習作としてnode-webkitでAWS S3のクライアントアプリを作ってみました。

ysugimoto / aws-s3-dav

node-webkitの今後

まだ登場してあまり日が経ってないツールですが、最新が0.8.4と、メジャーバージョンがリリースされるのもそれほど遠くないかも知れません。活発にissuesも投げられています。海外では採用実績は色々あるようですが、日本ではまだそれほど情報も多くないようです。

(参考:Prepros: node-webkitで書かれたSassなどのGUI上のビルドツール

もちろん本格的なGUIはC#のような言語には敵いませんが、スクリプトベースである利点を利用して、API監視ツールや、GUIでのアプリケーションインストーラなど、コンソールから実行していたものを視覚化するアプリの開発には有用ではないかと思っています。

最後に

簡単にnode-webkitの紹介とGUIアプリケーションの作り方を紹介させていただきました。

日常の開発の中で、自動化や効率化は重要な課題だと感じており、さらにこれらのツールをメンバーで共有することはさらに重要なことだと思います。
ミスを減らし、かつ効率を上げるため、冒頭に書いたようなgruntといったツールはもちろん、細かい作業も全てコマンド化したりすることで自動化しています。これらをnode-webkitのようなツールでGUI化し、開発環境のセットアップやメンテナンスを簡単にしていくのも効率を上げる手段の一つではないでしょうか。GUIなら「黒い画面」が苦手な方でも使える共通ツールになりえると思います。

node-webkitを使う上で、他のプラットフォームの対応や、DOMとnode実行環境での約束事、GUIの動的な制御など、本格的なGUIアプリケーションを作る際のもう少し深い話はまた機会があればご紹介させて頂きます。

それでは、最後まで読んでいただきありがとうございました!