去る2016年1月にブログを引っ越しました。
アナウンスがすっかり遅くなってしまいした。もう一年近く経つのですね・・・ このブログを定期的に見てくれている方がいらっしゃったらば、申し訳ございません。
新しいブログは、 shimar's blog がそれになります。 これまでは、Google Bloggerを使っていましたが、jekyll + Github Pagesに乗り換えました。
相変わらずの体でやっております。 よろしくお願いいたします。
去る2016年1月にブログを引っ越しました。
アナウンスがすっかり遅くなってしまいした。もう一年近く経つのですね・・・ このブログを定期的に見てくれている方がいらっしゃったらば、申し訳ございません。
新しいブログは、 shimar's blog がそれになります。 これまでは、Google Bloggerを使っていましたが、jekyll + Github Pagesに乗り換えました。
相変わらずの体でやっております。 よろしくお願いいたします。
ElectronっていうAtomエディタのベースを切り離してプラットフォーム化したものがある。コレを使うと、クラスプラットフォームなデスクトップアプリケーションが、HTML、CSS、Javacriptで書けるっていうんで、使っていこうかしらと思案しています。まずは、環境を作って、Hello,Worldでも。
ちなみに、環境は、
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でも動いたので、いろいろ作ってみます。
前回の記事「Epochでリアルタイムなチャートを表示する。」の続き。前回は、D3.jsベースのチャート表示ライブラリ「Epoch」を使って、時系列なラインチャート、エリアチャート、バーチャートをリアルタイムに描画する方法について書いた。今回は、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が残っているので、次回あるいはその次あたりに書きたいが・・・。
html上のチャート、ラインチャートとかバーチャートとか、リアルタイムに更新したい、と思って調べていたら、Epochなるライブラリを発見したので、使い方を纏めておく。
A general purpose real-time charting library for building beautiful, smooth, and high performance visualizations.とのこと。 で、
Epochはjavascriptファイルとcssファイルを提供しているので、そいつらをページに引きこむ。Epoch - Getting Startedにあるようにd3.jsも忘れずに。
これらは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便利だわ。
DOM要素にfadeとかslideとかのアニメーションをつけるのに、jQueryで書くのはまあ簡単ではあるけど、Animate.cssなるcss3のアニメーションライブラリを知ったので、使い方をメモっておく。シンプルなアニメーションがすごく簡単に実現できる。
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');
});
となる。他にも無限にアニメーションさせるとか、アニメーション終了イベントをハンドリングするとかもできる。
当然本家のサイトでも試せるんだけど、ドロップダウンでいちいち選択するのが面倒に思って、これらを1つ1つ試すページをここに作ってみたので、よかったらどうぞ。
animate.css、簡単に使えて効果でかいっていう感じがいいな、と思った。
2013年の8月に、「モックhtmlはJade+LESS+CoffeeScript+Grunt.jsで。」という記事をこのブログに書いた。あれから約1年半。Webアプリケーションの開発の仕方やツールなども変わってきて、当時やろうとしていたことが今では随分とスマートにできるようになったので、あらためてモックhtmlをさくさく作る環境について纏めておきます。
Yeomanを使う。Yeomanは「モダンなWebアプリのscaffoldingツール」。いろいろなWebアプリケーションの構成、例えばAngujarJS+Express.js+MongoDBとか、backborn.js+express.jsとか、に対するアプリケーションのscaffoldを作るツール。で、いろいろな構成に対するgeneratorが提供されていて、必要なgeneratorをインストールして使う、という寸法。
gulp-webappは、yeomanが提供する静的サイトのためのgenerator。モックHTMLを作るなら、ズバリこいつがよさげ。gulpのwatchタスクが動くから、コード書きながら結果を見ながらコード直しながら結果を見ながら、っていうのが簡単にできちゃう。
gulp-webpp、ちょっとまって。Jadeとかlessを使いたい場合はどうすんのさ?って思ったんだけど、よくよくドキュメントを見ると、Recipesってのがあって、ここに色々書いてあった。Jadeもlessも使えるわ。他にもいろいろあるから読んでみたらいい。
YeomanはGrunt+Bowerを全面に押し出してるはずなのに、gulp-webappって…と思わずにいられませんでした。とはいえ、便利ですね。
を纏める時に、AngularJSのDeveloper Guideを読んだわけですが、AngularJSがどのようにDIを実現しているか、についても書かれています。興味あったんで纏めてみます(というか訳してみます、に近いか・・・)。
コンポーネント(objectやfunction)が依存するブツを得る方法は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を使ってどうこうするとかなしに、単純に必要な依存性を宣言するだけ。この仕組なら「デメテルの法則」を破らないですむ。
というようなことが書いてあって、訳しかたがうまくなくて、はっきりと理解できたわけではないが、とりあえずメモとして残しておきます。
以下の3つの方法がある。
someModule.controller('MyController', ['$scope', 'greeter', function($scope, greeter) {
// do something.
}]);
と書くのがinline array annotationだと。この例で言うなら、'$scope'と'greeter'という名前で生成&登録されたオブジェクトが存在する前提で、MyControllerは$scopeとgreeterオブジェクトに依存している、ということを表現している。依存するコンポーネントの名称を文字列で並べつつ、関数のパラメータとして定義する。このとき、配列の要素の順番と関数の引数の順番が一致するよう注意する必要がある。
この方法は、仮にミニファイツールなどでコードがミニファイされて、関数のパラメータ名がツールに変更されてしまったとしても、正しく依存するオブジェクトが注入されるようにする方法、だと。
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のFilterとかわかっていない僕には嬉しい一覧であった。ので、自分の備忘録としてここに訳しつつ纏めておく。
AngularJS Full-Stack generatorは、アプリケーションのひな形だけでなく、アプリケーションの中身を開発する上で使えるジェネレータをいくつか提供しているってことに気づいたので纏めてみます。
アプリケーションそのものを作るジェネレータ。angular-fullstack:appのエイリアス。
$ yo angular-fullstack
これ以外のジェネレータには、サーバサイドの機能を作るためのジェネレータと、クライアントサイドの機能を作るためのジェネレータ、そしてデプロイのためのジェネレータがある。
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ファイルが生成される。
ある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
が得られる。
クライアントサイドのコントローラを生成する。
$ 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
が得られる。
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
が得られる。
filterって何だろう。AngularJS初心者なのでまだ知りません。 でも、
$ yo angular-fullstack:filter someFilter
を実行すると、filterのためのファイルを生成してくれる、と。
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:factory、angular-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();
};
});
を生成する。
公式サイトには
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としては、
filterや、service decoratorなどAngularJSを理解していないと使い方がわからないものがあり、使いこなすにはもっと勉強しないと、と感じた。あとは、クライアントサイドのディレクトリ構成を意識するというか、戦略を持たないといけないな。
Yeomanを使ってMEAN(MongoDB、Express、AngularJS、Node.js)なWebアプリケーションのひな形を作ってみます。
AngularJS Full-Stack generatorというgeneratorを使用する。
$ npm install -g generator-angular-fullstack
でインストールする。-gオプションを付けてglobalなパッケージとしてインストールする。
$ yo angular-fullstack
を実行すると、generatorが次のような質問をしてくるので、適宜答えてあげる。
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
でアプリケーションが起動する。
mean-cliを使ってアプリケーションのひな形を作るまでの手順を纏めてみます。
前提として、NodeJS、MongoDB、Gitが必要。それぞれインストールしておく。 ここでは詳細は割愛。
npmのバージョンが2.xでないとWARNINGが出るので、上げておく。
$ npm update -g npm
を実行する。
meanで作成するプロジェクトは、タスクマネージャとしてGruntかGlupを選択できる。デフォルトはGrunt。両方入れておく。
$ npm install -g grunt-cli gulp
インストールするのは、mean-cli。なので、
$ npm install -g mean-cli
を実行する。
インストールしたmean-cliを使って、Webアプリケーションプロジェクトを作成する。例えば、teamというプロジェクトを作るには、
$ mean init team
を実行する。すると、アプリケーション名と利用するタスクマネージャ(GruntかGulpか、僕はGulpを選択)を問われ、答えるとプロジェクトディレクトリを生成してくれる。
生成されたプロジェクトのルートに移動し、npm installを実行する。
$ cd /path/to/team
$ npm install
アプリケーションを起動してみる。Gulpを使うよう指示したので、gulpのデフォルトタスクを起動するとアプリケーションが起動する。
$ gulp
そして、http://localhost:3000にアクセスすると、↓のような画面が表示される。
ヘッダの「Join」リンクをつつくと、ユーザ登録ページに遷移する。
また、「Log in」リンクをつつくとログインページに遷移する。
初めてmean-cliを使って、アプリケーションのひな形を作るところまでやってみました。 これから実際に何か作ってみます。ちなみに、あのキャラクターは、Daphneっちゅー忍者らしいですね。JenkinsとかYeomanとかキャラがおっさんだったから、ちょっと可愛いなと思いました。どうでもいいですけど。
npmにPM2というプロセスマネージャツールがあって、プロセスの起動、終了、監視などを便利に行ってくれる。実はPM2にはアプリのデプロイもできちゃうことを知ったので試してみました。
基本的なフローは、PM2/ADVANCED_README.md at development · Unitech/PM2に書いてあるとおりで行けましたんで、流れを纏めておこうと思います。
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"
}
}
}
デプロイ先に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
実際にデプロイするのは、
$ 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経由で発行したコマンドなども記録されますんで、デバッグやら何やらに参照するとよさそうです。
すると、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などを使って書かれている場合でも使える。
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を使ってみた。環境は、
npmでインストール。
$ npm install connect-assets --save
$ npm install less --save
cssにlessを使っているので、lessもインストールした。(less-middlewareは使わない。)
app.jsにconnect-assetsの設定を書く。 ↑のpathsには、ソースファイルのパスを指定する。 assets/jsはjavascriptのソースディレクトリ、assets/cssはCSSのソースディレクトリ(lessファイルもココに置いている)、componentsはbowerでインストールしたパッケージ群が入っている前提。 buildDirは、コンパイル・結合・圧縮後のファイルの出力ディレクトリ。
connect-assetsを使う上にあたり、mincerが理解できる形式で依存関係を定義したファイルを作成する必要がある。 mincerに対する依存関係の指定は、次のようなファイルで行う。
HTML(jade)側で、connect-assetsが出力するファイルを参照するために、jadeファイルで次のコードを書く必要がある。
app.jsのconnect-assetsに対する設定で、buildオプションを指定することで、build(=結合・圧縮)の有無を変更できる。
デフォルトだと、
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の基本的な使い方は簡単で、npmとかgemとか使ったことのある人なら、すぐに使えると思った。具体的な使い方は、↓のリンクが参考になります。
express.jsだとサーバーサイドで使うライブラリは、packages.jsonに書いてnpmで管理するのが基本的なパッケージ管理手法と思ってる。で、bowerはクライアントサイドで使うJavascriptやらを管理するために使う、ってことになる。
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はデフォルトで、./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'))); // <= コレを追記する。
three.jsの勉強中、単純なオブジェクトを円運動させる方法を知りたくて、それなら調べながら、太陽系の惑星たちの公転運動を描いてみようと思い至った。
惑星なのでほぼ球だろう、ということでthree.jsが提供してくれているオブジェクトを使って、太陽と水星〜海王星までを球として表現してみた。3Dのオブジェクトを作るには、Geometry、Material、Meshの3つを使うのが基本だと思っていて、今回各天体を球として表現するのに、
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;
}
};
のようなオブジェクトになった。コンストラクタの引数radiusRateとorbitalRadiusRateは、それぞれ地球の半径を1として何倍か、地球の公転軌道半径を1として何倍か、を指定するようにした。それと、omegaRateっていうのは、地球の公転の角速度(rad/s)を1として何倍か、を指定するもので、公転運動の処理で使ってる。さらには、一応公転軌道(=円)を3D空間に表示しようと思って、circleっていう属性を定義した。円を描くのも、例のGeometry,Material,Meshの3点セットが必要だけど、three.jsは円に特化したオブジェクトを定義していて、実際に使ったのは、
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で公開しました。マウスでグリグリ、コロコロできたり。とりあえず、当初の目的であった、オブジェクトを円運動させる方法はわかったので良かった。
node(express.js)でcsrfに対する対策を施す方法について書いてみる。といっても、express.jsはcsrf対策のためのミドルウェアを提供しているので、その使い方の話。(ここで使っているexpress.jsのバージョンは3.4.8です。)
express.jsが提供するミドルウェアに、csrf っていうのがあってコレを使うと簡単にcsrf対策ができる。
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()よりも下に書けってあるので、注意が必要。
csrfミドルウェアが生成してくれるトークンをformに埋める必要があるので、viewでformを書くときには、
form(action='/some_action', method='form')
input(type='hidden', name='_csrf', value='#{_csrf}')
:
のように、hiddenに_csrfのトークンを埋めておく。(これはテンプレートエンジンにjadeを使った場合の例)
これで、postなリクエストにcsrfトークンが含まれていなかったり、不正なトークンが含まれていると、expressが検出してエラーを上げてくれるようになる。
失礼なほどざっくり言ってしまえば、capistranoはrubyで書かれたデプロイツールで、gitやsvnなどのリポジトリからモジュールを取得、デプロイ先のしかるべきディレクトリに配置し、dbの変更、assetsの生成、プロセスの再起動なんかを自動でやってくれるすぐれものです。 capistrano2っていうのが長く広く使われていたところに、capistrano3が登場していろいろとスマートになったようです。
nodeなアプリを対象とするにあたり、リモート(デプロイ先)でのnodeのパスを正しく設定したり、npmでライブラリ群をインストールしたり、っていうところをどうやるのか、というのが肝になるのかなぁ、と思っていたら、
っていうのが既にあったので、使わせて頂きました。capistrano上で動作するnvmタスクを提供してくれる。 ローカルなnvmにも、システムワイドなnvmにも対応している。僕のサーバはnvmをシステムワイドで入れているので助かった。([node] nvmをシステムワイドに適用する。)
capistrano上で動作するnpmタスクを提供してくれる。 Railsアプリのデプロイプロセスでbundle installを実行するように、npm installを走らせる。 もともとcapistrano-npmだけ追加してやったらnpmのパス(、つまりnode関連のパス)が解決できずどうしたものか、とはまっていたところに、capistrano-nvmと併せて使ったらば解決できたので、セットで使うのがいいのかと。
実際にデプロイするには、
リモートで作業するユーザとSSHの設定が基本になります。 ユーザ作って、ディレクトリ掘って、SSHでフォワードできれば良さそう。 capistranoの本家サイトにある、Authentication & Authorisationのページのとおりにやりました。
gemコマンドでも、bundle経由でもいいと思います。今回はgemコマンド叩いてインストールしてしまいました。
$ gem install capistrano $ gem install capistrano-nvm $ gem install capistrano-npm
ここが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
ここまで準備ができたところで、デプロイします。 capistrano2の頃のように、cap deploy:setupとかはもう要らない。いきなり、 cap deployでよい。
$ cap production deploy
今回使ってみたのは、
の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": "*",
:
}
}
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-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')] }
));
これを楽する方法がまだわかっていませんorz。
node_modules/twitter-bootsrap-3.0.0の中にあるjsファイルをpublic/javascripts配下にコピーして使っているけど、これは今後の課題です。