フロントエンドのビルドツール、Grunt以外の選択肢

フロントエンドのビルドツールというとGruntが
デファクトスタンダードになっている感ありますが、
それ以外の選択肢って何があるかなという話です。

Gulp

Gulpはストリーミング式のビルドツールです。
設定はgulpfileに記述します。

gulp = require 'gulp'
coffee = require 'gulp-coffee'
concat = require 'gulp-concat'
uglify = require 'gulp-uglify'

gulp.task 'compile', ->
  gulp.src('./src/*.coffee')
    .pipe(coffee())
    .pipe(concat('all.js'))
    .pipe(uglify())
    .pipe(gulp.dest('./dist/'))

記述方法はGruntと違い、このようにgulp.src()が返すstreamオブジェクトから
処理をメソッドチェインで繋げていきます。
上の例だと

# ファイル読み込んで
gulp.src('./src/*.coffee')
  # coffeeからコンパイルして
  .pipe(coffee())
  # concatして
  .pipe(concat('all.js'))
  # 圧縮して
  .pipe(uglify())
  # ./dist/以下に出力
  .pipe(gulp.dest('./dist/'))

といった感じの流れになります。
pipeで生成物をそのまま次の処理に渡して行くため、
このようなケースではGruntよりシンプルにタスクを定義する事ができます。
またwatchタスクは標準機能として備えているためその為のプラグイン等は不要です。

gulp.task 'watch', ->
  gulp.watch './src/*.coffee', (event) ->
    console.log "#{event.path}: #{event.type}"
    gulp.run 'compile'

複数のタスクを実行するタスクはGruntと同じような書き方で定義できますが、

gulp.task 'default', ['coffee', 'concat', 'uglify']

Gruntと違いこの順番に同期的に実行される訳ではなく、
順序が保証されてないため注意が必要です。
そうしたい場合はcallbackやdeferred等で
ごにょごにょやらないといけないのが少し面倒ですね。

プラグインは現在約180ほどの登録があり、
各種コンパイラやlint、テストランナーは一通り揃っているようです。

Brunch

Brunchは

Brunch is an ultra-fast HTML5 build tool

とされていますが、ビルド以外にも
scaffoldやローカルサーバの立ち上げといった機能があり
Grunt + yoと言った方が近いかもしれません。

# 指定したBrunch skeltonからプロジェクトの雛形を作成
$ brunch new skelton-url
# wach + ローカルサーバの立ち上げ
$ brunch w -s
# ビルド
$ brunch build

多くのskeltonは一緒にテスト環境も用意してくれるので、
例えばBrunch with Marionetteならlocalhost:3333/testにアクセスすれば
そのままブラウザ上でmochaによるテストを実行する事ができます。

Gulp同様、プラグインやボイラープレートとなるskeltonは一通り揃っています。
skeltonはやはりChaplinやAngularなどMVCフレームワークのものが人気なようです。

まとめ

プラグインの充実度やコミュニティの活発さでは
Gruntがまだまだ強いと思いますが、
SPAなんかでフロントとサーバが疎結合になっていたりする場合は
ローカルサーバの立ち上げからビルドまでやってくれるBrunch、
小規模なプロジェクトで設定ファイルをシンプルに書きたいなら
Gulpと、それぞれビルドツールの選択肢に加えてみるのも良いんじゃないでしょうか。

2013年読んで良かった本

たのしいRuby 第4版

Rubyを始めたので、まずは定番と言われているものを。
章立てで非常に解りやすく、入門書としても最適だと思います。
Ruby2.0にも対応しています。

ジェネラティブ・アート -Processingによる実践ガイド

ジェネラティブ・アートとはプログラムによって生成されるアートであり、
そのパラダイムやアルゴリズムの解説+Processingの入門的な本です。
フラクタルの生成は再帰処理の練習にもなります。

ジェネラティブ・アート -Processingによる実践ガイド

ジェネラティブ・アート -Processingによる実践ガイド

思考する機械コンピュータ (サイエンス・マスターズ)

コンピュータサイエンスの知識が全くない事に危機感を感じて、
その入門として古本屋で見つけて読みました。
チューリングの万能機械、NP完全問題などコンピュータとアルゴリズムの
基礎的な概念を解りやすく学ぶ事ができます。

思考する機械コンピュータ (サイエンス・マスターズ)

思考する機械コンピュータ (サイエンス・マスターズ)

コーディングを支える技術 ~成り立ちから学ぶプログラミング作法 (WEB+DB PRESS plus)

プログラミング言語の歴史、
なぜその機能が生まれたのかといった事を解説しています。
各言語の特色や独自のパラダイムをざっくりとつかむには非常に良い読み物であると思います。

97 Things Every Programmer Should Know: Collective Wisdom From The Experts

超定番ですが、kindle英語版がセールだったときに買ってみました。
読み物として面白いのはもちろん、どこからでも読める上に1章が短いので
ちょっとした空き時間に英語の学習をするのにも具合が良かったです。
(といいつつまだ半分しか読んでない)

97 Things Every Programmer Should Know: Collective Wisdom From The Experts

97 Things Every Programmer Should Know: Collective Wisdom From The Experts

Marionette.jsについてあれこれ

Marionette.jsBackbone.jsのラッパー的なライブラリです。

Backbone.jsの問題点

Backbone.jsは割と自由度が高いフレームワークなのですが、
それ故に中規模以上のプロジェクトで使おうとすると

  • 設計が難しい(ベストプラクティスがわからずオレオレ実装になる)
  • 初期化やインスタンスの管理が面倒
  • Viewは同じような処理が増える(で基底クラスにまとめたりする)

といったデメリットがあります。
実際僕もよくわからないまま初期設計をした結果
大きな技術的負債を作ってしまった事がありました。

Marionette.js

Marionette.jsはBackbone.jsと比べて抽象度が高く、
モジュール機構、CollectionView、Viewのデストラクタなど
独自実装になりがちな機能を提供してくれます。
これらを使う事で上に挙げたようなデメリットを回避し
Backbone.jsだけを使用した場合と比較して
シンプルで明確な実装を行うことができます。

機能とかAPIとか

まとめてみました

Backbone.jsは最近Angular.jsに押され気味な感もありますが、
こうしたライブラリと合わせて使ってみるとまた印象が違ってくるかと思います。

似たような感じの

全部試した訳じゃないんですが、色々あるようです。

後から書くフロントエンドのテスト

今業務でやってるプロジェクト、ジョインした時点では

  • 普通のソーシャルゲーム
  • 規模はそこそこ大きい
  • MVCフレームワークなどは使ってない
  • テスト0
  • 運用中

フロントはこんな感じでした。
これはヤバいと思い開発の合間合間にテストを書いていったのですが、
その時にやったことの記録です。

使ったもの

  • Mocha
  • chai
  • Sinon.js
  • testem
  • PhantomJS
  • CasperJS

DOMのテストもしたかったので実行環境はブラウザにしています。
Jasmineでも良かったのですがアサーションも自分で選びたかったので
Mochaを使いました。

どのくらい書いたか

結論から言うと共通のメソッドやコンポーネントだけしか書いてません。
そもそもテストを書く事を全く考慮されていないコードで、
DOMと密結合しまくりだったり

if (result) {
    resultDom += '<div class="result"...';
}
wrapper.innerHTML = resultDom;

長過ぎる関数だったり

function buildHogeList = function() {
    // 200行くらいの処理
}

外部から完全に遮断されていたり

(function() {
    var HogeController = function() {...
})();

といった有様でした。
最初は頑張ってこれらに合わせて書いていたのですが、

  • .hogeButtonという要素をクリックするとhogeController.hogeClickHandlerが呼び出される」
  • 「buildHogeTitleは<div class="hoge">massage</div>というDOMを生成する」

みたいなテストケースをひたすら書いているのはあまりにも効率が悪いと感じたので
それらを補完する意味でも後述のCasperJSなどを使用したテストに注力しました。

CasperJSが便利

単体テストに加えて、CasperJSでスクレイピングをするようにしました。
CasperJSはテストやスクレイピング用のユーティリティを備えたPhantomJSのラッパーです。
動作としては

  • 全ページのチェック
  • チュートリアルの進行
  • 基本ゲームループ

合わせてjsエラーのチェック、DOMの状態などのアサーション
スクリーンショットを取ってレイアウトをざっくり確認、といった事をしています。

Jenkinsとの連携

Jenkins上に専用のジョブを作成して、ビルド後にトリガーで実行されるようにしました。
ビルド前に単体テスト、ビルド後にそのサーバー上でCasperJSでのテストを実行しています。

これはまだやってないんですが、 HARファイルや画面キャプチャなどの成果物を
アップロードしてどこかで確認できるようになるとかなり捗りそうだなーと考えています。

思ったこと

テストファーストとかカバレッジとか完全に無視してしまってますが
仕様やDOMが頻繁に変わるソーシャルゲームの場合
このくらいが落としどころなのかもしれません。

とはいえ、ここまでやってきて結構つらい感じはあったし
最初からテスト書きながら開発するのに越した事は無いと思います。

最近の○○.jsをざっくりまとめてみた

一口に○○.jsと言っても、単純にクライアント側で読み込むライブラリではないものが増えてきています。
有名なものをいくつかまとめてみました。

Node.js

JavaScriptの実行環境です。実行にはGoogleのV8エンジンが使われています。
主にサーバーサイドの実装に使われることが多いですが
後述するGrunt.jsなどのツールが普及するにつれ、フロントエンドの開発でも重要性を増してきています。

Grunt.js

Node.js上で動くタスク実行ツールです。
プラグインをインストールすることで

  • ファイルのコンパイル
  • 変更の監視
  • テストの実行
  • サーバーを立てる
  • 画像のサイズ最適化

など様々なタスクを実行することができます。

CommonJS

ブラウザ上だけではなく、サーバーサイドやGUIアプリケーションでも
使用されるようになってきたJavaScriptの実装に
汎用性を持たせるために策定されたAPIの標準仕様です。
CommonJSという名前のライブラリが存在するわけではありませんが
例えばNode.jsの一部、モジュール機構などはこのCommonJSに準拠しています

CreateJS

JavaScriptでのインタラクティブコンテンツ開発をサポートするライブラリ群です。
Easel.js, Tween.js, Preload.js, Sound.jsの4つのライブラリから成ります。
Flash CS6以降はこれを使用したToolkit for CreateJSという拡張機能により
HTML5へコンテンツをエクスポートすることが可能となっています。

altJS

JavaScriptに変換される言語の総称です。
有名なものにCoffeeScript, TypeScript, JSX, Haxeなどがあります。
それぞれの言語ごとに特色があり、いずれも
クラスの実装や型付けの強化、実行パフォーマンスの向上など
素のJavaScriptを補完するようなものとなっています。

PhantomJS

ヘッドレスなWebkitブラウザです。
操作にはJavaScriptを使用し、スクレイピングや画面キャプチャをとるといったことが可能です。
似たものにSlimerJSがあり、こちらはGeckoベースとなっています。

CasperJS

スクレイピングやテストに便利なユーティリティを揃えたPhantomJS(or SlimerJS)のラッパーです。
Node.js上で動かすための更なるラッパーであるSpookyJSもあります。

VanilaJS

高速で軽量かつクロスプラットフォームフレームワークです。
非常に高いシェアを誇り、これを使用していないJavaScriptのコードは存在しないとまで言われています。


と、ざっくりした感じですがまとめてみました。
実際にここに列挙したものは全て僕がやっているプロジェクトでも使っていたりします。

どれもデファクトスタンダードになりつつある感じなので、一度触っておいて損は無いかと思います。

Vagrant + chef + Berkshelfでフロントエンドの環境構築を自動化

最近のフロントエンド

GitやNode.jsやSassやaltJSのコンパイラなど、使うものが増えて
環境構築の手順も複雑になってきています。
そこでVagrantとchef、それからcookbookの依存関係を管理するBerkshelfを使って
環境構築の手順を自動化かつ共有できるようにしました。

開発環境の中身

最近自分がよく使っているものを入れてみます。

  • Git
  • Node.js
  • grunt-cli
  • Bower
  • testem
  • PhantomJS
  • CasperJS

セットアップ

スケルトンの作成

berks cookbook chef-frontend

実行するとchefリポジトリのセットアップから
Vagrantfileの生成、.gitの追加までやってくれます。

Berksfileの編集

使用するサードパーティのcookbookを記述します。
要するにGemfileとかpackage.jsonのようなものです。

Berksfile
site :opscode

metadata
# Git, Node.js, PhantomJSはOpscodeにホスティングされているcookbookを使用してインストール
cookbook 'git'
cookbook 'nodejs'
cookbook 'phantomjs'

自前recipeの編集

サードパーティのcookbookを使わずにセットアップするものは
こちらに記述していきます。

recipe/default.rb
extract_path = "/usr/local/bin"

# githubからCasperJSのリポジトリをクローン
git "/vagrant/casperjs" do
    repository "git://github.com/n1k0/casperjs.git"
    user "root"
    revision "1.1-beta1"
    action :checkout
end

# シンボリックリンクを作成
bash "install casperjs" do
  cwd "/vagrant/casperjs"
  user "root"
  code <<-EOH
    ln -sf `pwd`/bin/casperjs #{extract_path}/casperjs
  EOH
end

# Node.jsのパッケージで必要なものをグローバルインストール
bash "install npm packages" do
  cwd "/home/vagrant"
  user "root"
  code <<-EOH
    npm install -g grunt-cli bower testem
  EOH
end

Vagrantfileの編集

使用するBoxの設定*1
# 今回はUbuntuを使用
# Every Vagrant virtual environment requires a box to build off of.
config.vm.box = "Ubuntu precise 64 VirtualBox"

# The url from where the 'config.vm.box' box will be fetched if it
# doesn't already exist on the user's system.
config.vm.box_url = "http://files.vagrantup.com/precise64.box"
起動時に実行するレシピを追記
chef.run_list = [
     "recipe[git]",
     "recipe[nodejs]",
     "recipe[phantomjs]",
     "recipe[chef-frontend::default]"
]

実行してみる

# cookbookのインストール
berks install --path=cookbooks
# Vagrantマシンの起動、レシピの実行
vagrant up

これだけで環境構築完了です。
仮想マシンにログインして各コマンドが使用できることを確認してみます。

vagrant ssh
# -> git, node, grunt, bower, testem, phantomjs, casperjsコマンドが使える!

まとめ

リポジトリをクローンしてコマンド2発で環境を構築できるのはお手軽で良いですね。
実際の開発で使う場合、OS別の構築手順の差異を吸収する、
あまり黒い画面を使わないデザイナーやマークアップの人向け
といったメリットが期待できるんじゃないかなと。*2

Berkshelfによるcookbookの管理や、スケルトンの作成はknifeを使うより簡単でした。

t93/chef-frontend

*1:このあたりのデフォルト値はberks configureで設定可能です。

*2:環境構築だけなら普通にVMイメージ配布した方が手っ取り早いかも

NodeListのイテレーション比較

document.querySelectorAllなどで取得するNodeListはArrayのインスタンスではないため
forEachやmapといったメソッドを持っていません。

そのためループで展開したいときは普通にfor文使うとか、
Array.prototype.forEachをcallで呼び出すとか
ライブラリの関数を利用するといった選択肢がありますが
それらのパフォーマンスを計測・比較してみました。

結果はこんな感じ、平均して遅いのはfor inや$.each、
速いのは通常のfor文やwhileでした。
NodeList Iteration · jsPerf

通常のArrayだとこのように、極端なパフォーマンスの差は見られません。
Array Iteration · jsPerf

結論としては、普通にfor文にしましょうとなるんですが、
lodashはfor, whileに次ぐ実行速度で、記述量も少なくて済むので
既にlodashを入れているor他にも使いたいメソッドがあるのであれば
選択肢としては十分にありではないかと思います。

内部的には渡されたリストがArrayのインスタンスでなければ
while文を使ったイテレータ関数の文字列を生成して、
Functionコンストラクタに突っ込んで実行するという、結構泥臭いことをしているようです。
やはりパフォーマンスを最優先にしている印象ですね。

あと、for文書くときに

for (var i = 0, len = list.length; i < len; i++) {

のようにlengthをキャッシュした方が速い、というのは割と定説になっている思うのですが、
最近のJavaScriptエンジンでは自動的に最適化されるので必要ないという話も
耳に挟んでいたのでその比較も先ほどのjsperfに入っています。

for (var i = 0; i < list.length; i++) {

lengthをキャッシュした場合と比較してもほぼ遜色ないどころか、
Firefoxではむしろ速くなっています。
モダンブラウザ・スマートフォン向けの開発なら、lengthのキャッシュはもう必要ないですね。

※この他にインクリメントの前置・後置も比較してみましたが、特に差は確認できませんでした。