node.jsとRubyで作るWindows向けフロントエンド開発環境

自身はMacを使っているのだけど、Windows向けの開発環境を作る必要があったため作ってみた。つらかった。

## やりたいこと

- Jadeのコンパイル
- Sassのコンパイル
  - node-sass/libsass + compass
- 画像の最適化
- ローカルにHTTPサーバを立てて確認できる
- LiveReload

## node.js/npm

node.jsとnpmを以下から持ってきた。node.jsは最新安定版の0.10.36を、npmは2.0系を使いたかったのだけど1.4.12までしかなかったので、これを入れてupdateすることに。

- http://nodejs.org/dist/
- http://nodejs.org/dist/npm/

## 使ったモジュール

シェルスクリプトは当然動かない（Bashを入れれば別かもしれないけど）し、たまにはGruntでなくてgulpを使ってみるか、ということでgulpを使った。

- coffee-script
  - gulpfile.coffeeで書くため
- gulp
  - 本体、npm-scriptsから実行できるようにするため
- gulp-cached
  - 変更のあったファイルだけを対象にフィルターしてくれる
- gulp-imagemin
  - 画像の最適化
- gulp-jade
  - Jadeのコンパイル
- gulp-notify
  - 通知してくれる
- gulp-plumber
  - watchでコンパイルエラーが出てもwatchが続くようにしてくれる
- gulp-sass
  - Sassのコンパイル
- gulp-util
  - ユーティリティ
- gulp-watch
  - ファイル監視
- gulp-webserver
  - HTTPサーバ + LiveReload
- serve-static
  - ファイルをいい感じに返してくれるミドルウェア

## package.json

npm 2.0でnpm startから引数を渡して使いたいのでstartにgulpのコマンドを書いてる。

```json:package.json
{
  "private": true,
  "scripts": {
    "start": "gulp --require coffee-script/register"
  },
  "devDependencies": {
    "coffee-script": "^1.8.0",
    "gulp": "^3.8.10",
    "gulp-cached": "^1.0.2",
    "gulp-imagemin": "^2.1.0",
    "gulp-jade": "^0.11.0",
    "gulp-notify": "^2.2.0",
    "gulp-plumber": "^0.6.6",
    "gulp-sass": "^1.3.2",
    "gulp-util": "^3.0.2",
    "gulp-watch": "^4.1.0",
    "gulp-webserver": "^0.9.0",
    "serve-static": "^1.8.1"
  }
}
```

## gulpfile.coffee

なんかこんな感じ。serve-staticを使ったのは、ディレクトリが2つあって片方のディレクトリにファイルがなかったらもう片方を見に行って欲しかったため。

```coffee:gulpfile.coffee
# serve directories
viewDirectory    = '../views'
webrootDirectory = '../webroot'

# 出力先
jadeOutput  = '../views'
sassOutput  = '../webroot/css'
imageOutput = '../webroot/image'

# 先頭が_で始まるファイル（インクルードされるファイルを想定）は無視
# 一方で、インクルードファイルを更新してもwatchでコンパイルされないというデメリットがある
# ぐぬぬ
jadeTarget  = './jade/**/!(_)*.jade'
sassTarget  = './scss/**/!(_)*.scss'

# gif, jpg, jpeg, pngを最適化
imageTarget = './image/**/*.{gif,jpg,jpeg,png}'

exec = require('child_process').exec

gulp    = require 'gulp'
util    = require 'gulp-util'
jade    = require 'gulp-jade'
sass    = require 'gulp-sass'
image   = require 'gulp-imagemin'
watch   = require 'gulp-watch'
server  = require 'gulp-webserver'
cached  = require 'gulp-cached'
notify  = require 'gulp-notify'
plumber = require 'gulp-plumber'

serve = require 'serve-static'

if util.env.production
  jadeOption =
    debug: false
    pretty: true
  sassOption =
    outputStyle: 'compressed'
    sourceComments: false
else
  jadeOption =
    debug: true
    pretty: true
  sassOption =
    outputStyle: 'nested'
    sourceComments: true

gulp.task 'jade', ->
  gulp
    .src(jadeTarget)
    .pipe(plumber(errorHandler: notify.onError('<%= error.message %>')))
    .pipe(cached('jade'))
    .pipe(jade(jadeOption))
    .pipe(gulp.dest(jadeOutput))

gulp.task 'sass', ->
  if util.env.compass
    command =
      if util.env.production
      then 'compass compile --config config.rb --environment production --force'
      else 'compass compile --config config.rb'
    p = exec(command, (err, stdout, stderr) ->
      util.log(err) if err
      util.log(stdout)
      util.log(stderr)
    )
    p.on('error', (err) -> util.log(err))
  else
    gulp
      .src(sassTarget)
      .pipe(plumber(errorHandler: notify.onError('<%= error.message %>')))
      .pipe(cached('sass'))
      .pipe(sass(sassOption))
      .pipe(gulp.dest(sassOutput))

gulp.task 'image', ->
  gulp
    .src(imageTarget)
    .pipe(plumber(errorHandler: notify.onError('<%= error.message %>')))
    .pipe(cached('image'))
    .pipe(image(progressive: false))
    .pipe(gulp.dest(imageOutput))

gulp.task 'watch', ->
  if util.env.compass
    p = exec('compass watch --config config.rb', (err, stdout, stderr) ->
      util.log(err) if err
      util.log(stdout)
      util.log(stderr)
    )
    p.on('error', (err) -> util.log(err))
    process.on('exit', (code) -> p.kill('SIGINT'))
  else
    watch(sassTarget, -> gulp.start('sass'))

  watch(jadeTarget,  -> gulp.start('jade'))
  watch(imageTarget, -> gulp.start('image'))

  return

gulp.task 'server', ->
  gulp
    .src('.')
    .pipe(server(
      livereload: true
      middleware: [
        serve(webrootDirectory)
        serve(viewDirectory)
      ]
    ))

gulp.task 'compile', ['jade', 'sass', 'image']
gulp.task 'develop', ['server', 'watch']
gulp.task 'default', ['server', 'watch']
```

## Ruby/RubyGems

Ruby 2.1.5を以下から持ってきた。7zじゃなくてzipだったらもっと楽だったんだけどな……

それとpemを入れてあげないとrubygemsからgemがインストールできないので入れておく。

- http://rubyinstaller.org/downloads/
- https://gist.github.com/luislavena/f064211759ee0f806c88

## バッチファイル

手順書なんて書いても誰も実行してくれないのは目に見えているので、ダブルクリックで全て済むようにバッチファイルをいくらか書いた。

```batch:env.bat
@echo off

set PATH="%~dp0\bin\node";%PATH%
set NODE_PATH="%~dp0\bin\node\node_modules\npm\node_modules;%~dp0\bin\node\node_modules\npm";%NODE_PATH%

set PATH="%~dp0\bin\ruby\bin";"%~dp0\bin\ruby\gem\bin";%PATH%
set FULLPATH=%~dp0
set GEM_HOME=%FULLPATH:\=/%/bin/ruby/gem
set GEM_PATH=%FULLPATH:\=/%/bin/ruby/gem

rem vim:ff=dos:
```

環境変数を設定するバッチファイル。ポータブルな感じにしたかったので、%~dp0祭りをしている。`GEM_HOME`などはバックスラッシュだと駄目みたいなのでスラッシュに変えてある。

ん、ダブルクォートで囲んでいたのが駄目だっただけで、スラッシュでなくバックスラッシュでも行けるのかもしれない。面倒なので試さない。

```batch:setup.bat
call env.bat

call npm.cmd install -g npm
call npm.cmd install

call gem.bat update --no-document --system
call gem.bat install compass --no-document --version 1.0.3

rem vim:ff=dos:
```

アップデートしたりgemを入れたりとか。

```batch:develop.bat
call env.bat

call npm.cmd start -- develop

rem vim:ff=dos:
```

watchするやつ。

```batch:develop-compass.bat
call env.bat

call npm.cmd start -- develop --compass

rem vim:ff=dos:
```

上のcompass版。

## 環境構築

setup.batをダブルクリックするだけ。

---

一応、動くっぽいということは確認できた。ただ、LiveReloadは動作しなかったと思う。

---

というのを[build-env](https://github.com/tsukurite/build-env)に作って置いておいた。

バッチファイルはほとんど忘れていた。あとMacを使って欲しいと思った。

WindowsでRubyものすごいつらそう、と思ったけどこの程度の用途なら普通に使えるっぽいことがわかってよかった。

Bundlerをいつも使うので`GEM_HOME`を切り替えればRubyGemsだけでもGemのインストール先を切り替えられるのを知れてよかった（？）

---

作ったのはいいけど使ってくれるんだろうか。