2016/11/04

ブログを引っ越しました。

去る2016年1月にブログを引っ越しました。

アナウンスがすっかり遅くなってしまいした。もう一年近く経つのですね・・・ このブログを定期的に見てくれている方がいらっしゃったらば、申し訳ございません。

新しいブログは、 shimar's blog がそれになります。 これまでは、Google Bloggerを使っていましたが、jekyll + Github Pagesに乗り換えました。

相変わらずの体でやっております。 よろしくお願いいたします。

2015/07/31

[Electron] ElectronでHello,Worldアプリを書いてみる。

はじめに

ElectronっていうAtomエディタのベースを切り離してプラットフォーム化したものがある。コレを使うと、クラスプラットフォームなデスクトップアプリケーションが、HTML、CSS、Javacriptで書けるっていうんで、使っていこうかしらと思案しています。まずは、環境を作って、Hello,Worldでも。

ちなみに、環境は、

  • ubuntu 14.04 LTS(32bit)
  • node.js v0.12.6
です。

Electronのインストール

npmでインストールする。

npm i -g electron-prebuilt

プロジェクトの初期化

プロジェクトっていうか、アプリのルートディレクリを掘って、npmコマンドで初期化する。

mkdir hello-electron && cd hello-electron
npm init

コード書く

browser(main)プロセス用のjavascriptコードと、rendererプロセスによって画面に表示されるhtmlを書く。 本家にあるコードそのままを試す。

動かしてみる

electronコマンドで起動する。

electron .
すると、Developer Toolsが有効になったChromeっぽいものが起動します。
Developer Toolsを隠すと↓みたいな。

終わりに

とりあえずウチのへっぽこPCでも動いたので、いろいろ作ってみます。

2015/03/31

Epochでリアルタイムなチャートを表示する - Gauge編

前回の記事「Epochでリアルタイムなチャートを表示する。」の続き。前回は、D3.jsベースのチャート表示ライブラリ「Epoch」を使って、時系列なラインチャート、エリアチャート、バーチャートをリアルタイムに描画する方法について書いた。今回は、Gaugeと呼ばれるチャート?を描画する方法について。

Gauge?

gaugeは、カタカナで書くなら「ゲージ」で、「計器」を意味するらしい。実際、

みたいなのが描画される。

使い方

gaugeを表示するには、

var gauge = $('#gauge-chart').epoch({
    type: 'time.gauge',
    value: 0.1
  });
のように、epochのtypeにtime.gaugeを指定する。ライン、エリア、バーと異なり、gaugeは時系列なチャートではないので、時間を指定せず、値のみ指定する。

そして、値を更新するには、

gauge.push(値);
とするだけ。これで、gauge上の針がアニメーションしながら指定した値に落ち着いていく。ここにデモがあります。にしても簡単だわ〜。

終わりに

今回はEpochでGaugeチャートの書き方を書きました。後はHeatmapが残っているので、次回あるいはその次あたりに書きたいが・・・。

2015/03/19

Epochでリアルタイムなチャートを表示する。

html上のチャート、ラインチャートとかバーチャートとか、リアルタイムに更新したい、と思って調べていたら、Epochなるライブラリを発見したので、使い方を纏めておく。

Epoch

A general purpose real-time charting library for building beautiful, smooth, and high performance visualizations.
とのこと。 で、
  • javascriptライブラリ
  • d3.jsベース
  • Line, Area, Bar, Gauge, Heatmapの各チャートをリアルタイムに表示できる
  • リアルタイムでない通常のチャートもいける
という特徴がある。

導入

Epochはjavascriptファイルとcssファイルを提供しているので、そいつらをページに引きこむ。Epoch - Getting Startedにあるようにd3.jsも忘れずに。

LineChart, AreaChart, BarChart

これらはx軸が時間、y軸は任意というチャートを描画する。見た目が違うだけでデータの構造は同じ。データの構造は、

var data = [
  { label: 'Series1',
    values: [{ time: time, y: 100 }, { time: time + 1, y: 1000 }] },
  { label: 'Series2',
    values: [{ time: time, y: 200 }, { time: time + 1, y: 200 }] }
];
という形になる。系列ごとにlabelとデータを定義して、それを配列にする。で、valuesの中のtimeは、a unix timestampである必要がある(故にEpochなのかなぁ)。で、これをEpochに与えるとチャートを表示してくれる。
<div id="line-chart" class="epoch" style="height:160px"></div>
のようなhtmlを書き、
var lineChart = $('#line-chart').epoch({
    type: 'time.line',
    data: data,
    axes: ['left', 'bottom', 'right']
  });
とすると、ラインチャートが表示される。エリアチャート、バーチャートの場合もデータの構造は同じなので、epoch関数のパラメータのtypeを、エリアチャートの場合はtime.area、バーチャートの場合はtime.barとしてやればよい。

で、初期データを食わせたチャートに、後から時々刻々とデータを追加していくことでリアルタイムなチャートになるんだけど、追加するデータの形式は、

var current = [
  { time: time, y: Math.random() * 1000 },
  { time: time, y: Math.random() * 1000 }
];
という形式になる。初期データに比べてlabelが要らない。この追加データを、
lineChart.push(current);
とするだけで、チャートが更新される。なんて楽ちんな。タイマーを使って、周期的にバックエンドからデータを取得してpushしてやるとか、WebSocketを使って、バックエンドから送りつけられたデータをpushする、といった方法でリアルタイムなチャートが表示できる。

実際に表示すると↓のようなチャートが表示される。

で、このチャート表示が動いているのがこちらになります。

終わりに

Epochを使うと、他にもGaugeとかHeatmapもリアルタイムに描画できるけど、力尽きたので次回にまわします。それにしても、Epoch便利だわ。

2015/03/05

Animate.cssで簡単にDOM要素のアニメーションを実現する。

DOM要素にfadeとかslideとかのアニメーションをつけるのに、jQueryで書くのはまあ簡単ではあるけど、Animate.cssなるcss3のアニメーションライブラリを知ったので、使い方をメモっておく。シンプルなアニメーションがすごく簡単に実現できる。

Animate.css

githubのREADME.mdには、

animate.css is a bunch of cool, fun, and cross-browser animations for you to use in your projects.
とある、つまり、クロスブラウザに対応したクールで楽しいアニメーションの束、ってことっぽい。実際、cssを取り込むだけで、いろんな種類のアニメーションが簡単に実現できちゃう。

使い方

cssをダウンロードするなり、bower install animate.cssするなりして、HTMLにcssを埋め込む。

<head>
  <link rel="stylesheet" href="path/to/animate.min.css">
</head>

で、アニメーションさせたいDOM要素にclass属性を与えてやる。と。例えば、とあるh2要素に対して、rubberBand(ビヨーーンプルンプルンプン)みたいなアニメーションをつけるには、

<h2 class="animated rubberBand">rubberBand</h2>
のように書く。動的にアニメーションさせたい、例えばボタンクリック時とか、という場合は、
var target = $('h2');
var button = $('button#some-button');
button.on('click', function(ev) {
  target.addClass('animated').addClass('rubberBand');
});
となる。他にも無限にアニメーションさせるとか、アニメーション終了イベントをハンドリングするとかもできる。

アニメーションの種類

  • Attension Seekers
  • Bouncing
  • Fading
  • Flippers
  • Lightspeed
  • Rotating
  • Specials
  • Zoom
8つのカテゴリに分かれていて、それぞれいくつかのアニメーションを提供している。

当然本家のサイトでも試せるんだけど、ドロップダウンでいちいち選択するのが面倒に思って、これらを1つ1つ試すページをここに作ってみたので、よかったらどうぞ。

終わりに

animate.css、簡単に使えて効果でかいっていう感じがいいな、と思った。

2015/02/18

モックhtmlはyeoman+gulp-webappで。

2013年の8月に、「モックhtmlはJade+LESS+CoffeeScript+Grunt.jsで。」という記事をこのブログに書いた。あれから約1年半。Webアプリケーションの開発の仕方やツールなども変わってきて、当時やろうとしていたことが今では随分とスマートにできるようになったので、あらためてモックhtmlをさくさく作る環境について纏めておきます。

Yeoman

Yeomanを使う。Yeomanは「モダンなWebアプリのscaffoldingツール」。いろいろなWebアプリケーションの構成、例えばAngujarJS+Express.js+MongoDBとか、backborn.js+express.jsとか、に対するアプリケーションのscaffoldを作るツール。で、いろいろな構成に対するgeneratorが提供されていて、必要なgeneratorをインストールして使う、という寸法。

gulp-webapp

gulp-webappは、yeomanが提供する静的サイトのためのgenerator。モックHTMLを作るなら、ズバリこいつがよさげ。gulpのwatchタスクが動くから、コード書きながら結果を見ながらコード直しながら結果を見ながら、っていうのが簡単にできちゃう。

Jadeとless、あるいは。

gulp-webpp、ちょっとまって。Jadeとかlessを使いたい場合はどうすんのさ?って思ったんだけど、よくよくドキュメントを見ると、Recipesってのがあって、ここに色々書いてあった。Jadeもlessも使えるわ。他にもいろいろあるから読んでみたらいい。

終わりに

YeomanはGrunt+Bowerを全面に押し出してるはずなのに、gulp-webappって…と思わずにいられませんでした。とはいえ、便利ですね。

2015/01/22

AngularJSのDIについてDeveloper Guideを読んでみた。

を纏める時に、AngularJSのDeveloper Guideを読んだわけですが、AngularJSがどのようにDIを実現しているか、についても書かれています。興味あったんで纏めてみます(というか訳してみます、に近いか・・・)。

コンポーネント(objectやfunction)が依存するブツを得る方法は3つしかない。

  1. コンポーネントは依存するブツを生成することができ、一般的にはnew演算子を使う。
  2. コンポーネントはグローバル変数から依存するブツを探すことができる。
  3. コンポーネントはそれが必要となる場所で渡される。

最初の生成する、とか、探すっていう2つの選択肢は、コンポーネントに依存するブツをハードコードしなきゃいけなくなるので、最適とはいえない。依存するブツを変更することが面倒になってしまう。これは特にテスト用に分離したブツのモックを提供したいといった場面で問題になる。

3番目の選択肢は、コンポーネントから依存するブツの場所を知る責務を取り除くのでもっともよさそう。依存するブツは単純にそのコンポーネントに渡されるだけ。

function SomeClass(greeter) {
  this.greeter = greeter;
}

SomeClass.prototype.doSomething = function(name) {
  this.greeter.greet(name);
};

上の例のSomeClassはgreeterの生成や場所の特定などには関係していなくて、インスタンスが作成される際に、単純にgreeterを手渡されるだけ。 それはいいんだけど、SomeClassを生成するコード上に依存するブツを得るっていう責務を生んでる。

依存するブツを生成するっていう責務を管理するために、angularjsアプリケーションは”injector”を持ってる。injectorは、依存するブツの生成や検索を責務とするサービスロケータである。

injectorサービスを使った例を挙げる。

var myModule = angular.module('myModule', []);

injectorにどうやってgreeterサービスを組み立てるか教えてあげる。greeterは$windowサービスに依存していることに注目しよう。greeterサービスはgreetメソッドを持つオブジェクト。

myModule.factory('greeter', function($window) {
  return {
    greet: function(text) {
      $window.alert(text);
    }
  };
});

で、myModuleに定義されたコンポーネントを提供することができる新しいinjectorを生成して、 そいつにgreeterサービスをリクエストする。(これは通常angular bootstrapで自動的に行われる。)

var injector = angular.injector(['myModule', 'ng']);
var greeter = injector.get('greeter');

ハードコードの問題の解決を望んだら、今度はinjectorがアプリケーション全体を通じて存在する必要性が出ちゃった。injectorを渡すことが「デメテルの法則」を破ってしまう。これを救済するために、下の例のように、コンポーネントを生成する責務をinjectorに移譲するために、HTMLテンプレート上で宣言的な記法を使っている。

function MyController($scope, greeter) {
  $scope.sayHello = function() {
    greeter.greet('Hello World');
  };
}

AngularはHTMLをコンパイルする時、injectorに対してコントローラとそれが依存するブツのインスタンスを次々に生成するよう要求しながらng-controllerディレクティブを処理する。

injector.instantiate(MyController);

これは全て舞台裏で完了する。ng-controllerを使うってことは、これまでにinjectorを知っているコントローラなしで、injectorにそのクラスのインスタンスを生成するよう要求し、MyControllerの全ての依存性を満たすってことに注意しよう。

これがベストな結果。アプリケーションコードはinjectorを使ってどうこうするとかなしに、単純に必要な依存性を宣言するだけ。この仕組なら「デメテルの法則」を破らないですむ。

というようなことが書いてあって、訳しかたがうまくなくて、はっきりと理解できたわけではないが、とりあえずメモとして残しておきます。

2015/01/06

AngularJSのDIの定義の仕方

AngularJSはDI(Dependency Injection)の機構を提供してるっつーんで、AngularJS: Developer Guide: Dependency Injectionを読みつつ、使い方を纏めておくとしよう。

依存するコンポーネントをどう書くか

以下の3つの方法がある。

  • inline array annotation (最も好ましい方法)
  • $inject property annotation
  • 関数のパラメータ名から暗黙的に注入する方法(この方法は注意が必要)

inline array annotation

someModule.controller('MyController', ['$scope', 'greeter', function($scope, greeter) {
  // do something.
}]);
と書くのがinline array annotationだと。この例で言うなら、'$scope'と'greeter'という名前で生成&登録されたオブジェクトが存在する前提で、MyControllerは$scopeとgreeterオブジェクトに依存している、ということを表現している。依存するコンポーネントの名称を文字列で並べつつ、関数のパラメータとして定義する。このとき、配列の要素の順番と関数の引数の順番が一致するよう注意する必要がある。

$inject array annotation

この方法は、仮にミニファイツールなどでコードがミニファイされて、関数のパラメータ名がツールに変更されてしまったとしても、正しく依存するオブジェクトが注入されるようにする方法、だと。

var MyController = function($scope, greeter) {
  // ...
}
MyController.$inject = ['$scope', 'greeter'];
someModule.controller('MyController', MyController);
この例は、inline array annotationとよく似ているからわかりやすい。$injectに依存するオブジェクトの名前を設定する、ってことね。これもinline array annotationと同じく、$injectに指定する配列の要素と、関数のパラメータの順序を一致させないとダメよ。

関数のパラメータ名から暗黙的に注入する方法

で、この暗黙の注入が一番簡単なんだけど、問題も有る、と。 コードを書くとすると、

someModule.controller('MyController', function($scope, greeter) {
  // ...
});
みたいになって、配列の順序と関数の引数の順序を一致させるとかも気にしなくていい、というメリットはある。だけど、ミニファイツールで関数の引数名が変えられちゃったら、もうAngularは正しく注入できないから、この方法は使わない方がいいみたい。
However this method will not work with JavaScript minifiers/obfuscators because of how they rename parameters. Tools like ng-annotate let you use implicit dependency annotations in your app and automatically add inline array annotations prior to minifying. If you decide to take this approach, you probably want to use ng-strict-di. Because of these caveats, we recommend avoiding this style of annotation.

AngularJSのConceptual Overview

AngularJSの機能やコンセプトなどよくわかってないので、公式サイトのガイドを読んでいる。そこには、Conceptual Overviewなる一覧があり、例えばAngularJSのFilterとかわかっていない僕には嬉しい一覧であった。ので、自分の備忘録としてここに訳しつつ纏めておく。

Template
追加のマークアップを持つHTML
Directives
カスタム属性、カスタム要素を持つHTMLの拡張
Model
ユーザがView上で見たり操作するデータ
Scope
モデルが格納されるコンテキスト。controller、directive、expressionからアクセスできる。
Expressions
Scopeからアクセスする変数や関数を記述する式
Compiler
Templateを解析し、directiveやexpressionsをインスタンス化する。
Filter
式の値をユーザに見せるために整形する。
View
ユーザが目にするもの。
Data Binding
Model-View間でデータを同期する。
Controller
Viewの裏側のビジネスロジック。
Dependency Injection
オブジェクトと関数を生成し、結び付ける。
Injector
DIコンテナ
Module
Injectorを構成するcontroller、service、filter、directiveなどアプリの異なるパーツのためのコンテナ。
Service
Viewから独立した再利用可能なビジネスロジック

2015/01/04

YeomanのAngularJS Full-Stack generatorが提供するジェネレータ

AngularJS Full-Stack generatorは、アプリケーションのひな形だけでなく、アプリケーションの中身を開発する上で使えるジェネレータをいくつか提供しているってことに気づいたので纏めてみます。

angular-fullstack

アプリケーションそのものを作るジェネレータ。angular-fullstack:appのエイリアス。

$ yo angular-fullstack

これ以外のジェネレータには、サーバサイドの機能を作るためのジェネレータと、クライアントサイドの機能を作るためのジェネレータ、そしてデプロイのためのジェネレータがある。

サーバサイド

angular-fullstack:endpoint

REST APIのエンドポイントを生成するジェネレータ。例えば、issueというリソースに対するREST APIのエンドポイントを定義する場合は、

$ yo angular-fullstack:endpoint issue
を実行する。すると、
? What will the url of your endpoint to be? /api/v1/issues
   create server/api/issue/index.js
   create server/api/issue/issue.controller.js
   create server/api/issue/issue.model.js
   create server/api/issue/issue.socket.js
   create server/api/issue/issue.spec.js
となる。URLをどうするか聞かれるので、/api/v1/issuesと入力した。モデルやコントローラ、specファイルが生成される。

クライアントサイド

angular-fullstack:route

あるURLに対するクライアントサイドのリソース一式を生成するジェネレータ。例えば、/issuesというURLを有効にするために、

$ yo angular-fullstack:route issues
を実行すると、
? Where would you like to create this route? client/app/
? What will the url of your route be? /issues
   create client/app/issues/issues.js
   create client/app/issues/issues.controller.js
   create client/app/issues/issues.controller.spec.js
   create client/app/issues/issues.jade
   create client/app/issues/issues.less
が得られる。

angular-fullstack:controller

クライアントサイドのコントローラを生成する。

$ yo angular-fullstack:controller chat
を実行すると、
? Where would you like to create this controller? client/app/
   create client/app/chat/chat.controller.js
   create client/app/chat/chat.controller.spec.js
が得られる。

angular-fullstack:directive

AngularJSのdirectiveを生成する。何らかのチャートを表示するためのディレクティブを作るとして、

$ yo angular-fullstack:directive chart
を実行すると、
? Where would you like to create this directive? client/app/
? Does this directive need an external html file? Yes
   create client/app/chart/chart.directive.js
   create client/app/chart/chart.directive.spec.js
   create client/app/chart/chart.jade
   create client/app/chart/chart.less
が得られる。

angular-fullstack:filter

filterって何だろう。AngularJS初心者なのでまだ知りません。 でも、

$ yo angular-fullstack:filter someFilter
を実行すると、filterのためのファイルを生成してくれる、と。

angular-fullstack:service

AngularJSのserviceを生成する。 例えば、issueというリソースに関するAPIを提供するサービスを作るとして、

$ yo angular-fullstack:service issue
を実行すると、
? Where would you like to create this service? client/components
   create client/components/issue/issue.service.js
   create client/components/issue/issue.service.spec.js
が得られる。同様に、angular-fullstack:factoryangular-fullstack:providerというgeneratorも存在していて、それぞれ、
// yo angular-fullstack:service issue
'use strict';

angular.module('teamApp')
  .service('issue', function () {
    // AngularJS will instantiate a singleton by calling "new" on this function
  });
// yo angular-fullstack:factory issue
'use strict';

angular.module('teamApp')
  .factory('issue', function () {
    // Service logic
    // ...

    var meaningOfLife = 42;

    // Public API here
    return {
      someMethod: function () {
        return meaningOfLife;
      }
    };
  });
// yo angular-fullstack:provider issue
'use strict';

angular.module('teamApp')
  .provider('issue', function () {

    // Private variables
    var salutation = 'Hello';

    // Private constructor
    function Greeter() {
      this.greet = function () {
        return salutation;
      };
    }

    // Public API for configuration
    this.setSalutation = function (s) {
      salutation = s;
    };

    // Method for instantiating
    this.$get = function () {
      return new Greeter();
    };
  });
を生成する。

angular-fullstack:decorator

公式サイトには

Generates an AngularJS service decorator.
とあるが、これもよくわかっていない。が、
yo angular-fullstack:decorator issue
を実行すると、
? Where would you like to create this decorator? client/components
   create client/components/issue/issue.decorator.js
が得られ、
'use strict';

angular.module('teamApp')
  .config(function ($provide) {
    $provide.decorator('issue', function ($delegate) {
      // decorate the $delegate
      return $delegate;
    });
  });
が生成された。

デプロイ

デプロイのためのgeneratorとしては、

  • yo angular-fullstack:openshift
  • yo angular-fullstack:heroku
があって、それぞれOpenShift、Herokuへのデプロイを簡単に行うためのコマンドのようだが、今回は割愛。

終わりに

filterや、service decoratorなどAngularJSを理解していないと使い方がわからないものがあり、使いこなすにはもっと勉強しないと、と感じた。あとは、クライアントサイドのディレクトリ構成を意識するというか、戦略を持たないといけないな。

2015/01/01

YeomanでAngularJS、Express、MongoDB、Node.jsなアプリのひな形を作る。

Yeomanを使ってMEAN(MongoDB、Express、AngularJS、Node.js)なWebアプリケーションのひな形を作ってみます。

AngularJS Full-Stack generator

AngularJS Full-Stack generatorというgeneratorを使用する。

$ npm install -g generator-angular-fullstack
でインストールする。-gオプションを付けてglobalなパッケージとしてインストールする。

AngularJS Full-Stackを実行する。

$ yo angular-fullstack
を実行すると、generatorが次のような質問をしてくるので、適宜答えてあげる。
  • JavaScript、CoffeeScriptのどっちを使う?
  • HTMLとJade、どっちでマークアップする?
  • css、sass、stylus、lessのどれでスタイル書く?
  • AngularのRouterは、ngRouterとuiRouterのどっちを使う?
  • Twitter Bootstrap使う?
  • ui-bootstrapを使う?
  • MongoDBのモデリングにはmongooseを使う?
  • 認証処理のboilerplateを生成する?
  • Google、Facebook、TwitterのOAuthストラテジを有効にする?
  • socket.ioを使う?
質問に答えていくと↑のような画面になる。ここから先は、npm installとかbower installとかYoermanが自動で進めてくれる。

生成したプロジェクト

このgeneratorによって生成したプロジェクトは↓のようなディレクトリ構造になる。

.
├── Gruntfile.js
├── bower.json
├── client
├── e2e
├── karma.conf.js
├── node_modules
├── package.json
├── protractor.conf.js
└── server
clientディレクトリにはクライアントサイドのコードを、serverディレクトリにはサーバサイドのコードを格納するっていうのが、以前のバージョンから変わったところか。e2eディレクトリの目的は詳細を調べる必要がある。

アプリケーションを起動する。

$ grunt serve
でアプリケーションが起動する。

2014/12/30

mean-cliでアプリケーションのひな形を作る。

mean-cliを使ってアプリケーションのひな形を作るまでの手順を纏めてみます。

準備

NodeJS、MongoDB、Git

前提として、NodeJS、MongoDB、Gitが必要。それぞれインストールしておく。 ここでは詳細は割愛。

npmのバージョンを上げておく。

npmのバージョンが2.xでないとWARNINGが出るので、上げておく。

$ npm update -g npm
を実行する。

Grunt.jsあるいはGulp.jsをインストールしておく

meanで作成するプロジェクトは、タスクマネージャとしてGruntかGlupを選択できる。デフォルトはGrunt。両方入れておく。

$ npm install -g grunt-cli gulp

mean-cliをインストールする。

インストールするのは、mean-cli。なので、

$ npm install -g mean-cli
を実行する。

mean-cliを使ってプロジェクトを作る。

インストールしたmean-cliを使って、Webアプリケーションプロジェクトを作成する。例えば、teamというプロジェクトを作るには、

$ mean init team
を実行する。すると、アプリケーション名と利用するタスクマネージャ(GruntかGulpか、僕はGulpを選択)を問われ、答えるとプロジェクトディレクトリを生成してくれる。

npmパッケージのインストール

生成されたプロジェクトのルートに移動し、npm installを実行する。

$ cd /path/to/team
$ npm install

アプリケーションの起動

アプリケーションを起動してみる。Gulpを使うよう指示したので、gulpのデフォルトタスクを起動するとアプリケーションが起動する。

$ gulp
そして、http://localhost:3000にアクセスすると、↓のような画面が表示される。

ヘッダの「Join」リンクをつつくと、ユーザ登録ページに遷移する。

また、「Log in」リンクをつつくとログインページに遷移する。

初めてmean-cliを使って、アプリケーションのひな形を作るところまでやってみました。 これから実際に何か作ってみます。ちなみに、あのキャラクターは、Daphneっちゅー忍者らしいですね。JenkinsとかYeomanとかキャラがおっさんだったから、ちょっと可愛いなと思いました。どうでもいいですけど。

2014/11/20

PM2でnodeなWebアプリをデプロイしてみた

npmにPM2というプロセスマネージャツールがあって、プロセスの起動、終了、監視などを便利に行ってくれる。実はPM2にはアプリのデプロイもできちゃうことを知ったので試してみました。

デプロイのフロー

基本的なフローは、PM2/ADVANCED_README.md at development · Unitech/PM2に書いてあるとおりで行けましたんで、流れを纏めておこうと思います。

ecosystem

PM2でデプロイしたいアプリケーションや、デプロイ先の情報は、jsonファイルに記述します。そのファイルのひな形を作るコマンドが、

$ pm2 ecosystem
です。このコマンドを叩くと、↓のようなファイルが生成されます。こいつを自分の環境に合わせて編集します。
{
  "apps" : [{
    "name"      : "API",
    "script"    : "app.js",
    "env": {
      "COMMON_VARIABLE": "true"
    },
    "env_production" : {
      "NODE_ENV": "production"
    }
  },{
    "name"      : "WEB",
    "script"    : "web.js"
  }],
  "deploy" : {
    "production" : {
      "user" : "node",
      "host" : "212.83.163.1",
      "ref"  : "origin/master",
      "repo" : "git@github.com:repo.git",
      "path" : "/var/www/production",
      "post-deploy" : "pm2 startOrRestart ecosystem.json --env production"
    },
    "dev" : {
      "user" : "node",
      "host" : "212.83.163.1",
      "ref"  : "origin/master",
      "repo" : "git@github.com:repo.git",
      "path" : "/var/www/development",
      "post-deploy" : "pm2 startOrRestart ecosystem.json --env dev"
    }
  }
}

setup

デプロイ先にsshで接続し、アプリケーション格納ディレクトリを掘ったり何やらかんやらするコマンドが、

$ pm2 deploy ecosystem.json production setup
です。上述のecosystem.jsonを指定してdeployのsetupを行います。このままだと、デプロイ先のサーバに対して、22ポートを指定してsshで接続にいきます。sshのポート番号を指定するには、ecosystem.jsonに追記します。
{
  "apps" : [{
    "name"      : "API",
    "script"    : "app.js",
    "env": {
      "COMMON_VARIABLE": "true"
    },
    "env_production" : {
      "NODE_ENV": "production"
    }
  },{
    "name"      : "WEB",
    "script"    : "web.js"
  }],
  "deploy" : {
    "production" : {
      "user" : "node",
      "host" : "212.83.163.1",
      "ref"  : "origin/master",
      "repo" : "git@github.com:repo.git",
      "path" : "/var/www/production",
      "port" : 99999, // sshポート番号
      "post-deploy" : "pm2 startOrRestart ecosystem.json --env production"
    },
    "dev" : {
      "user" : "node",
      "host" : "212.83.163.1",
      "ref"  : "origin/master",
      "repo" : "git@github.com:repo.git",
      "path" : "/var/www/development",
      "post-deploy" : "pm2 startOrRestart ecosystem.json --env dev"
    }
  }
}
実際にsetupが成功すると、ecosystem.jsonで指定したpath配下に、次のようなディレクトリが作成されます。
foo@bar:/var/www/production$ ls -la
total 20
drwxrwxr-x  4 root root 4096 Nov 20 00:48 .
drwxrwxr-x  5 root root 4096 Nov 20 00:48 ..
-rw-rw-r--  1 node node   48 Nov 20 00:12 .deploys
lrwxrwxrwx  1 node node   20 Nov 20 00:12 current -> /var/www/production/source
drwxrwxr-x  4 node node 4096 Nov 14 00:57 shared
drwxrwxr-x 12 node node 4096 Nov 19 00:03 source

deploy

実際にデプロイするのは、

$ pm2 deploy ecosystem.json production
のコマンドです。これにより、リポジトリからのチェックアウトがなされ、ecosystem.jsonのpost-deployに指定したコマンドを実行します。デフォルトのecosystem.jsonではpm2によるプロセスの起動or再起動を行うコマンドが指定されています。例えば、npmやbowerのパッケージのインストール後に、nodeプロセスの再起動を、という場合は、↓のように書いてできました。
{
  : (省略)
  "post-deploy" : "npm install && bower install && pm2 startOrRestart ecosystem.json --env production"
  : (省略)
}

その他

pm2のデプロイ関連のコマンドを実行すると、/tmp/pm2-deploy.log にログが出力されます。ssh経由で発行したコマンドなども記録されますんで、デバッグやら何やらに参照するとよさそうです。

2014/06/03

express.jsでconnect-assetsを使う。

Railsには、asset pipelineという仕組みがあって、クライアントサイドのcoffeescriptやsassのソースを結合・圧縮してクライアントに配信することができるけど、node.js + express.jsのアプリケーションでは、同じようなことができないかな、と思って調べてみた。

connect-assets

すると、connect-assetsというライブラリを発見した。connect-assetsのREADMEに、"Transparent file compilation and dependency management for Node’s connect framework in the spirit of the Rails 3.1 asset pipeline."とあるように、Rails3.1で導入されたasset pipelineの仕組みをnode.jsのconnectフレームワーク上で実現するライブラリのようで、asset pipelineのように、分割されたjavascriptファイルやcssファイルを結合・圧縮してくれる。ソースがcoffeescriptやless、stylus、sassなどを使って書かれている場合でも使える。

mincer

connect-assetsは、内部でmincerを使っている。 mincerは、Railsのasset pipelineで使われているsprocketsのjavascriptポーティングライブラリ。 mincerの提供する構文に従って依存するライブラリを記述すると、それらをすべて結合したファイルを生成してくれる仕組みを提供する。 mincerは、内部でjavascriptの圧縮、cssの圧縮を行う。 ソースがcoffeescriptや、less、stylus、sassなどで書かれている場合は、コンパイルした上で圧縮することもできる。 connect-assetsのデフォルトだと、mincerに対して、Javascriptの圧縮器としてuguify-jsを、cssの圧縮機としてcssoを指定している。

使ってみる。

というわけで、connect-assetsを使ってみた。環境は、

  • node.js 0.10.28
  • express.js 4.2.0
  • connect-assets 3.0.1

インストール

npmでインストール。

$ npm install connect-assets --save
$ npm install less --save

cssにlessを使っているので、lessもインストールした。(less-middlewareは使わない。)

app.jsの記述

app.jsにconnect-assetsの設定を書く。 ↑のpathsには、ソースファイルのパスを指定する。 assets/jsはjavascriptのソースディレクトリ、assets/cssはCSSのソースディレクトリ(lessファイルもココに置いている)、componentsはbowerでインストールしたパッケージ群が入っている前提。 buildDirは、コンパイル・結合・圧縮後のファイルの出力ディレクトリ。

依存関係の定義

connect-assetsを使う上にあたり、mincerが理解できる形式で依存関係を定義したファイルを作成する必要がある。 mincerに対する依存関係の指定は、次のようなファイルで行う。

HTML(jade)側はどうするか?

HTML(jade)側で、connect-assetsが出力するファイルを参照するために、jadeファイルで次のコードを書く必要がある。

buildの有無

app.jsのconnect-assetsに対する設定で、buildオプションを指定することで、build(=結合・圧縮)の有無を変更できる。
デフォルトだと、

  • 開発環境では、build無し
  • 本番環境では、build有り
というオプションとなっている。この開発環境や本番環境は、NODE_ENVで切り替える。

2014/05/06

express.jsなプロジェクトでbowerを使ってパッケージを管理する。

bowerというパッケージ管理ツールがある。これは、Webサイト構築時に利用する様々なJavascriptやらcssといった外部のリソースを、パッケージとして管理するためのオープンソース。例えば、jQueryのバージョンが上がったからダウンロードしなくちゃとか、このライブラリはどこから持ってくればいいのか、とか、どこのディレクトリに配置すればいいの?とか、一度決めてしまえばそれでいいのだろうけど、bowerを使ってbowerの振る舞いに乗っかることで、外部のリソースに関する管理の手間が省けそう。

以前「node(express.js)でbootstrap3を使う、npmで簡単に。」を書いた際に、終わりの方に課題として、 ”bootstrapのJavascriptはどうすんのさ? これを楽する方法がまだわかっていませんorz。 node_modules/twitter-bootsrap-3.0.0の中にあるjsファイルをpublic/javascripts配下にコピーして使っているけど、これは今後の課題です。” と書いたけど、コレ対する解にもなる。

bowerの使い方。

bowerの基本的な使い方は簡単で、npmとかgemとか使ったことのある人なら、すぐに使えると思った。具体的な使い方は、↓のリンクが参考になります。

express.jsなプロジェクトでbowerを使う。

express.jsだとサーバーサイドで使うライブラリは、packages.jsonに書いてnpmで管理するのが基本的なパッケージ管理手法と思ってる。で、bowerはクライアントサイドで使うJavascriptやらを管理するために使う、ってことになる。

express.jsなプロジェクトにbowerを適用する。

bowerを適用するには、

$ cd express_project_root
$ bower init
を実行する。bower initは、最終的にbower.jsonを出力コマンドで、いくつかの設定を対話的に入力していく。実際のbower.jsonは、
{
  "name": "プロジェクト名",
  "version": "0.0.0",
  "homepage": "プロジェクトページヘのURL",
  "authors": [
    "名前 <メールアドレス>"
  ],
  "moduleType": [
    "node"
  ],
  "license": "MIT",
  "private": true,
  "ignore": [
    "**/.*",
    "node_modules",
    "bower_components",
    "components",
    "test",
    "tests"
  ],
}
のようになり、bowerでパッケージを管理する準備が出来た状態。

パッケージをインストールする。

例えば、jqueryを使うとする。bowerでインストールするには、

$ bower install jquery --save
とすると、プロジェクトルートにbower_componentsというディレクトリが生成され、その下にパッケージがインストールされる。また、--saveオプションを指定することで、bower.jsonに書き込まれる(この仕組みは、npmと同じですね。)。
.
├── app.js
├── bin
├── bower.json       # <= jqueryへの依存が定義される。
├── bower_components # <= この配下にパッケージがインストールされる。
├── node_modules
├── package.json
├── public
├── routes
└── views
したがって、npmやgemと同じ要領で、予め利用するパッケージがわかっているときは、bower.jsonにつらつらと書いてから、bower installすればいいし、後から利用するパッケージを増やす時は、bower install package_name --saveとすれば良さそう。

パッケージのインストール先をbower_componentsを以外にする。

bowerはデフォルトで、./bower_components配下にパッケージをインストールする。このままだと、ビューでJavascriptを読み込むのに、bower_components/jquery/dist/jquery.min.jsとかがscriptタグのsrc属性に登場することになるけど、bower云々がクライアント側のコードに出てくるのは避けたいので、インストール先のディレクトを変えてみる。具体的には、

$ less .bowerrc
{
  "directory": "components",
  "json": "bower.json"
}
というファイルを作って、"directory"を指定する(ここでは、componentsというディレクトリ名にしている)。そして、このディレクトをexpress.jsにスタティックなコンテンツを含むディレクトリであることを教えてあげれはよい。app.jsに↓を追記する。
app.use(express.static(path.join(__dirname, 'public')));
app.use(express.static(path.join(__dirname, 'components'))); // <= コレを追記する。

2014/04/24

three.jsで球を円運動させて太陽系の惑星を描いた。

three.jsの勉強中、単純なオブジェクトを円運動させる方法を知りたくて、それなら調べながら、太陽系の惑星たちの公転運動を描いてみようと思い至った。

球を描く

惑星なのでほぼ球だろう、ということでthree.jsが提供してくれているオブジェクトを使って、太陽と水星〜海王星までを球として表現してみた。3Dのオブジェクトを作るには、Geometry、Material、Meshの3つを使うのが基本だと思っていて、今回各天体を球として表現するのに、

  • THREE.MeshLambertMaterial
  • THREE.SphereGeometry
  • THREE.Mesh
の3つを使った。で、太陽をJavascriptのオブジェクトとして、↓のように書いてみた。
Sun = function() {
  this.material = new THREE.MeshLambertMaterial({ color: 0xff0000 });
  this.geometry = new THREE.SphereGeometry(109, 64, 64);
  this._mesh = new THREE.Mesh(this.geometry, this.material);
}

Sun.prototype = {
  constractor: Sun,
  get mesh() {
    return this._mesh;
  }
}
この太陽を3D空間に配置するには、
var sun = new Sun();
scene.add(sun.mesh);
とすればいい、と。

惑星は、Planetっていうオブジェクトをprototype継承させて個々の惑星を作ってやった。Planetは、

Planet = function(radiusRate, orbitalRadiusRate, color, omegaRate) {
  this.radiusBase = 4;
  this.orbitalRadiusBase = 400;
  this.radiusRate = radiusRate !== undefined ? radiusRate : 1.0
  this.orbitalRadiusRate = orbitalRadiusRate !== undefined ? orbitalRadiusRate : 1.0;
  this.color = color !== undefined ? color : 0x0000ff;
  this._omegaRate = omegaRate !== undefined ? omegaRate : 1;

  this._material = new THREE.MeshPhongMaterial({ color: this.color });
  this._geometry = new THREE.SphereGeometry(this.radiusBase * this.radiusRate, 64, 64);
  this._mesh = new THREE.Mesh(this._geometry, this._material);
  this._mesh.position.x = - this.orbitRadiusBase * this.orbitalRadiusRate;
  this._mesh.position.z = - this.orbitRadiusBase * this.orbitalRadiusRate;

  this._circleGeometry = new THREE.CircleGeometry(this.orbitalRadiusBase * this.orbitalRadiusRate, 2560);
  this._circleGeometry.vertices.shift();
  this._circleMaterial = new THREE.LineBasicMaterial({
    color: 0x111111
    // opacity: 0.1
    // linewidth: 0.01
  });
  this._circle = new THREE.Line(this._circleGeometry, this._circleMaterial);
  this._circle.rotation.x = 90 * Math.PI / 180;
};

Planet.prototype = {
  constractor: Planet,
  get material () {
    return this._material;
  },
  get geometry () {
    return this._geometry;
  },
  get mesh () {
    return this._mesh;
  },
  get circle() {
    return this._circle;
  },
  pivot: function(time) {
    var theta = 1.047 / this._omegaRate * time;
    this._mesh.position.x = Math.cos(theta) * this.orbitalRadiusBase * this.orbitalRadiusRate;
    this._mesh.position.z = Math.sin(theta) * this.orbitalRadiusBase * this.orbitalRadiusRate;
  }
};
のようなオブジェクトになった。コンストラクタの引数radiusRateorbitalRadiusRateは、それぞれ地球の半径を1として何倍か、地球の公転軌道半径を1として何倍か、を指定するようにした。それと、omegaRateっていうのは、地球の公転の角速度(rad/s)を1として何倍か、を指定するもので、公転運動の処理で使ってる。さらには、一応公転軌道(=円)を3D空間に表示しようと思って、circleっていう属性を定義した。円を描くのも、例のGeometry,Material,Meshの3点セットが必要だけど、three.jsは円に特化したオブジェクトを定義していて、実際に使ったのは、
  • THREE.CircleGeometry
  • THREE.LineBasicMaterial
  • THREE.Line
の3つ。これもsceneに追加してあげないと表示されないから、例えば、地球は、
Earth = function() {
  Planet.call(this, 1.0, 1.0, 0x0000ff, 1.0);
};
Earth.prototype = Object.create(Planet.prototype);
というオブジェクトを書いて、
var earth = new Earth();
scene.add(earth.mesh);
scene.add(earth.circle);
としてあげた。

円運動

円運動は、単位時間あたり何ラジアン進むか、が重要。で、今回は試しに1年を1分に見立てて色々と計算してみた。つまり、地球は1分かけて太陽の周りを1周する。その場合の地球の角速度は1.047(rad/sec)になった(あってます?)ので、これを使って瞬間瞬間の座標を計算してオブジェクトの位置を変えていけばよさそうだ、と。

で、今回のは、y=0平面上で公転させるってことにすると、xとzの値を時間と角速度と半径から求めることができるなぁ、と思い、結果出来たのが上述のPlanetオブジェクトのpivotというメソッド(↓再掲)。

pivot: function(time) {
    var theta = 1.047 / this._omegaRate * time;
    this._mesh.position.x = Math.cos(theta) * this.orbitalRadiusBase * this.orbitalRadiusRate;
    this._mesh.position.z = Math.sin(theta) * this.orbitalRadiusBase * this.orbitalRadiusRate;
  }

実際に動かしてみる

実際に動くページをThe Solar System 3Dで公開しました。マウスでグリグリ、コロコロできたり。とりあえず、当初の目的であった、オブジェクトを円運動させる方法はわかったので良かった。

2014/03/30

node(express.js)でcsrf対策する。

node(express.js)でcsrfに対する対策を施す方法について書いてみる。といっても、express.jsはcsrf対策のためのミドルウェアを提供しているので、その使い方の話。(ここで使っているexpress.jsのバージョンは3.4.8です。)

express.jsのcsrfミドルウェア

express.jsが提供するミドルウェアに、csrf っていうのがあってコレを使うと簡単にcsrf対策ができる。

使い方 - app.js

expressコマンドでアプリケーションを作ると、app.jsというファイルが出来て、この中にアプリケーションやサーバの設定を書くんだけど、その中で次のコードを書く。

// use session support.
app.use(express.session());

// use csrf middleware.
app.use(express.csrf());
app.use(function(req, res, next) {
  res.locals._csrf = req.csrfToken();
  next();
});
前述のこのページにあるとおり、csrfミドルウェアを使うには、session()よりも下に書けってあるので、注意が必要。

使い方 - view

csrfミドルウェアが生成してくれるトークンをformに埋める必要があるので、viewでformを書くときには、

form(action='/some_action', method='form')
  input(type='hidden', name='_csrf', value='#{_csrf}')
  :
のように、hiddenに_csrfのトークンを埋めておく。(これはテンプレートエンジンにjadeを使った場合の例)

これで、postなリクエストにcsrfトークンが含まれていなかったり、不正なトークンが含まれていると、expressが検出してエラーを上げてくれるようになる。

2014/03/23

twitter-bootstrapを使う時のベーシックなlayout.jade

twitter-bootstrapをexpressjsなアプリに適用するにあたって、jadeのレイアウト(layout.jade)にbootstrapのcssとjavascriptを埋めます。ベーシックなレイアウトをgistに上げました。

node(expressjs)なアプリをデプロイする、capistrano3で簡単に。

node(expressjs)なアプリを作って、capistrano2でデプロイしていたのだけど、capistrano3が登場してずいぶんスマートになったとのことで、capistrano3に載せ替えてみた、という話です。

capistranoって何?

失礼なほどざっくり言ってしまえば、capistranoはrubyで書かれたデプロイツールで、gitやsvnなどのリポジトリからモジュールを取得、デプロイ先のしかるべきディレクトリに配置し、dbの変更、assetsの生成、プロセスの再起動なんかを自動でやってくれるすぐれものです。 capistrano2っていうのが長く広く使われていたところに、capistrano3が登場していろいろとスマートになったようです。

capistrano + node

nodeなアプリを対象とするにあたり、リモート(デプロイ先)でのnodeのパスを正しく設定したり、npmでライブラリ群をインストールしたり、っていうところをどうやるのか、というのが肝になるのかなぁ、と思っていたら、

っていうのが既にあったので、使わせて頂きました。

capistrano-nvm

capistrano上で動作するnvmタスクを提供してくれる。 ローカルなnvmにも、システムワイドなnvmにも対応している。僕のサーバはnvmをシステムワイドで入れているので助かった。([node] nvmをシステムワイドに適用する。)

capistrano-npm

capistrano上で動作するnpmタスクを提供してくれる。 Railsアプリのデプロイプロセスでbundle installを実行するように、npm installを走らせる。 もともとcapistrano-npmだけ追加してやったらnpmのパス(、つまりnode関連のパス)が解決できずどうしたものか、とはまっていたところに、capistrano-nvmと併せて使ったらば解決できたので、セットで使うのがいいのかと。

deploy

実際にデプロイするには、

  1. リモートホスト(デプロイ先ホスト)の環境設定
  2. capistranoのインストール
  3. capistrano-nvmのインストール
  4. capistrano-npmのインストール
  5. Capfile, deploy.rbの作成
  6. deploy
という流れになりますね。

リモートホスト(デプロイ先ホスト)の環境設定

リモートで作業するユーザとSSHの設定が基本になります。 ユーザ作って、ディレクトリ掘って、SSHでフォワードできれば良さそう。 capistranoの本家サイトにある、Authentication & Authorisationのページのとおりにやりました。

capistrano,capistrano-nvm,capistrano-npmのインストール

gemコマンドでも、bundle経由でもいいと思います。今回はgemコマンド叩いてインストールしてしまいました。

$ gem install capistrano
$ gem install capistrano-nvm
$ gem install capistrano-npm

Capfile, deploy.rbの作成

ここがcapistrano2から変わったところの1つで、2の頃はcapifyっていうコマンドがあって、一番最初にcapifyしていたのが、 cap installに変わりました。 cap install すると、Capfileとdeploy.rbとかstaging.rbとかproduction.rbが作られます。環境別の設定ファイルのひな形が生成されます。いわゆるマルチステージに対応しています。stagingっていうのは、順productionというのか、本番の手前っていう位置づけの環境と考えるといいと思います。

Capfileではそのプロジェクトにおけるcapistranoの環境を書きます。 deploy.rbとdeploy/*.rbには、デプロイに関する詳細やタスクを書きます。 自作でタスクを作る場合は、lib/capistrano/tasksに拡張子.capを作ります。

今回は、カスタムタスクはなしで、Capfile、deploy.rb、production.rbだけ弄って、それぞれ次のように書きました。

# Capfile
# Load DSL and Setup Up Stages
require 'capistrano/setup'

# Includes default deployment tasks
require 'capistrano/deploy'

# Includes tasks from other gems included in your Gemfile
#
# For documentation on these, see for example:
#
#   https://github.com/capistrano/rvm
#   https://github.com/capistrano/rbenv
#   https://github.com/capistrano/chruby
#   https://github.com/capistrano/bundler
#   https://github.com/capistrano/rails
#
# require 'capistrano/rvm'
# require 'capistrano/rbenv'
# require 'capistrano/chruby'
# require 'capistrano/bundler'
# require 'capistrano/rails/assets'
# require 'capistrano/rails/migrations'
require 'capistrano/nvm' # <= 追加
require 'capistrano/npm' # <= 追加

# Loads custom tasks from `lib/capistrano/tasks' if you have any defined.
Dir.glob('lib/capistrano/tasks/*.cap').each { |r| import r }
# config/deploy.rb
# config valid only for Capistrano 3.1
lock '3.1.0'

set :application, 'application_name'
set :repo_url,    'ssh://hostname.your.repo/path/to/your_repo.git'

# Default branch is :master
# ask :branch, proc { `git rev-parse --abbrev-ref HEAD`.chomp }

# Default deploy_to directory is /var/www/my_app
# set :deploy_to, '/var/www/my_app'
set :deploy_to, '/var/www/application_name'

# Default value for :scm is :git
# set :scm, :git

# Default value for :format is :pretty
# set :format, :pretty

# Default value for :log_level is :debug
# set :log_level, :debug

# Default value for :pty is false
# set :pty, true

# Default value for :linked_files is []
# set :linked_files, %w{config/database.yml}

# Default value for linked_dirs is []
# set :linked_dirs, %w{bin log tmp/pids tmp/cache tmp/sockets vendor/bundle public/system}
set :linked_dirs, %w{log node_modules}

# Default value for default_env is {}
# set :default_env, { path: "/opt/ruby/bin:$PATH" }

# Default value for keep_releases is 5
# set :keep_releases, 5

set :nvm_type, :system
set :nvm_node, 'v0.10.26'
set :nvm_map_bins, %w{node npm}

namespace :deploy do
  desc 'Restart application'
  task :restart do
    on roles(:app), in: :sequence, wait: 5 do
      # Your restart mechanism here, for example:
      # execute :touch, release_path.join('tmp/restart.txt')
      execute :sudo, :restart, 'name_of_upstart_script'
    end
  end

  after :publishing, :restart

  after :restart, :clear_cache do
    on roles(:web), in: :groups, limit: 3, wait: 10 do
      # Here we can do anything such as:
      # within release_path do
      #   execute :rake, 'cache:clear'
      # end
    end
  end

end

deploy

ここまで準備ができたところで、デプロイします。 capistrano2の頃のように、cap deploy:setupとかはもう要らない。いきなり、 cap deployでよい。

$ cap production deploy

2014/03/14

node(express.js)でbootstrap3を使う、npmで簡単に。

express.jsを使ったnodeなWebアプリを作っていて、twitter-bootstrap使いたいな、と思った。かつて、bootstrap2の頃、本家からダウンロードして、cssとjavascriptをpublicに置いてできたー、とか思ったこともあったけど、npmとかないのかな?って思って探したらあった、じゃあ使ってみよう、という話です。

less-middlewareとtwitter-bootstrap-3.0.0

今回使ってみたのは、

の2つです。 less-middlewareは、express.jsがデフォルトで使用するメタCSS。twitter-bootstrap-3.0.0は、自分でpackage.jsonに書いてインストールしましたよ。
{
  "name": "appname",
  "version": "0.0.1",
  "private": true,
  "scripts": {
    "start": "node app.js"
  },
  "dependencies": {
    "express": "3.4.8",
    "jade": "*",
    "less-middleware": "*",
    "twitter-bootstrap-3.0.0": "*",
    :
  }
}

app.jsでless-middlewareの設定を書く。

twitter-bootstrap-3.0.0がインストールできたらば、app.jsに手を入れます。
いろいろとmiddlewareの登録をしている部分で、 var bootstrapPath = path.join(__dirname, 'node_modules', 'twitter-bootstrap-3.0.0');

app.use(require('less-middleware')({
  src:    path.join(__dirname, 'assets', 'less'),
  paths:  [path.join(bootstrapPath, 'less')],
  dest:   path.join(__dirname, 'public', 'stylesheets'),
  prefix: '/stylesheets'
}));
のようにless-middlewareを登録します。この記述は、
  • lessのソースは、assets/less配下に置く。
  • bootstrapPathの下のlessもlessのコンパイル対象に含める。
  • コンパイルして出来たcssファイルは、public/stylesheets配下に置く。
  • prefixは、lessのページに"Path which should be stripped from the public pathname."とあるけど、イマイチよくわかっていません。
という具合になります。
これで、bootstrapのlessはnpmに任せられるようになります。bootstrapをカスタマイズするには、assets/lessの下の自前lessファイルに書いていけば、カスタマイズできる、と。

2014-03-16追記

less-middlewareのバージョンが上がると、上述のオプションの指定の仕方が変わるそうで。 Migration 0.1.x 0.2.0 · emberfeather/less.js-middleware Wikiに詳しい説明がありますね。0.2.1-betaっていうのが今の時点で使えて、上の指定をするとエラーとなるので、次のように直したら、うまくいった。

var less = require('less-middleware');
var bootstrapPath = path.join(__dirname, 'node_modules', 'twitter-bootstrap-3.0.0');

app.use(less(path.join(__dirname, 'assets', 'less'),
             { dest: path.join(__dirname, 'public'),
               preprocess: {
                 path: function(pathname, req) {
                   return pathname.replace('/stylesheets', '');
                 }
               }
             },
             { paths:  [path.join(bootstrapPath, 'less')] }
            ));

bootstrapのJavascriptはどうすんのさ?

これを楽する方法がまだわかっていませんorz。
node_modules/twitter-bootsrap-3.0.0の中にあるjsファイルをpublic/javascripts配下にコピーして使っているけど、これは今後の課題です。