読者です 読者をやめる 読者になる 読者になる

GeekFactory

int128.hatenablog.com

npm + gulp + bower でビルド自動化

JavaScriptCSSのビルドを自動化することで、手作業による無駄な時間やミスを削減できます。また、誰でもビルドできるようになるため、リリースのボトルネックを解消できます。

改善したいこと

  • JavaScriptフレームワークCSSフレームワークの依存関係を自動的に管理したい。ダウンロードして配置を手作業でやりたくない。
  • CoffeeScriptやLESSのコンパイルを自動的に実行したい。手順書を見ながらコマンドを叩くとかやりたくない。
  • ダウンロードしたライブラリをリポジトリに入れたくない。
  • 出荷対象のリソースを明確にしたい。ゴミファイルをリリースしたくない。

どうやって実現する?

  • bowerでライブラリの依存関係を管理する。
  • gulpでビルドを自動化する。
  • ライブラリのフォルダは .gitignore で除外する。
  • ビルド時に別のフォルダにリソースを出力する。
  • npmでビルドに必要なツール(bowerやgulp)の依存関係を管理する。

具体的な方法を説明します。

下記を前提とします。

  • node.js がインストールされていること
  • npm が使えること

新しいフォルダを作って git init します。.gitignore に下記を入れておきます。

/build/
/bower_components/
/node_modules/

package.json を生成します。

npm init

最小限の package.json を自分で書いても構いません。

{
  "name": "example",
  "description": "an example with npm, gulp and bower",
  "dependencies": {},
  "devDependencies": {}
}
bower でライブラリの依存関係を管理

bower をインストールします。

npm install -g bower

bower でライブラリを追加してみましょう。

bower init
bower install angular --save

上記を実行すると bower_components/angular/ に AngularJS が格納されます。さらに bower.json に依存関係が保存されます。

ついでに Bootstrap も入れておきましょう。

bower install bootstrap --save
gulp でビルドを自動化

gulp はローカルにインストールする必要があります。ただし、ローカルだけでは node_modules/gulp/bin/gulp.js を実行する必要があるためグローバルにも入れておきます*1

npm install -g gulp
npm install gulp

gulp のスクリプトJavaScriptでもCoffeeScriptでも書けますが、今回はCoffeeScriptを使いたいのでインストールしておきます。

npm install coffee-script --save-dev

gulpfile.js に下記を書いておくことで、スクリプト本体をCoffeeScriptで書けるようになります。

require('coffee-script/register');
require('./gulpfile.coffee');

gulpfile.coffee にタスクを書きます。

(2014/11/4) 思ったよりブクマが続いているので、スクリプトを差し替えました。gulp-rimrafやgulp-ngminはdeprecatedになっています。

# gulpfile.coffee: build script for front assets
#
# gulp        - build assets
# gulp watch  - build assets continuously
# gulp server - start a server with assets and mocked APIs

sources =
  bower:  'bower.json'
  coffee: 'app/**/*.coffee'
  less:   'app/**/*.less'
  static: 'public/**/*'

libs =
  js: [
    'angular/angular.min.js'
    'angular-route/angular-route.min.js'
  ]
  css:    ['bootstrap/dist/**/*.min.css']
  static: ['bootstrap/dist/**/*']


bower       = require 'bower'
del         = require 'del'
gulp        = require 'gulp'
coffee      = require 'gulp-coffee'
concat      = require 'gulp-concat'
less        = require 'gulp-less'
ngAnnotate  = require 'gulp-ng-annotate'
nodemon     = require 'gulp-nodemon'
uglify      = require 'gulp-uglify'


gulp.task 'default', ['clean'], ->
  gulp.start 'compile:lib', 'compile:coffee', 'compile:less', 'compile:static'

gulp.task 'clean', (cb) ->
  del 'target/webapp/', cb

gulp.task 'watch', ->
  gulp.watch sources.bower,  ['compile:lib']
  gulp.watch sources.coffee, ['compile:coffee']
  gulp.watch sources.less,   ['compile:less']
  gulp.watch sources.static, ['compile:static']


gulp.task 'compile:lib', ->
  bower.commands.install().on 'end', ->
    gulp.src libs.js.map (e) -> "bower_components/#{e}"
      .pipe concat 'lib.js'
      .pipe gulp.dest 'target/webapp/'
    gulp.src libs.css.map (e) -> "bower_components/#{e}"
      .pipe concat 'lib.css'
      .pipe gulp.dest 'target/webapp/'
    gulp.src libs.static.map (e) -> "bower_components/#{e}"
      .pipe gulp.dest 'target/webapp/'

gulp.task 'compile:coffee', ->
  gulp.src sources.coffee
    .pipe coffee()
    .pipe ngAnnotate()
    .pipe uglify()
    .pipe concat 'app.js'
    .pipe gulp.dest 'target/webapp/'

gulp.task 'compile:less', ->
  gulp.src sources.less
    .pipe less()
    .pipe concat 'app.css'
    .pipe gulp.dest 'target/webapp/'

gulp.task 'compile:static', ->
  gulp.src sources.static
    .pipe gulp.dest 'target/webapp/'


gulp.task 'server', ['compile:apimock'], ->
  gulp.start 'watch', 'watch:apimock'
  nodemon
    script: 'target/apimock.js'
    watch: ['target/apimock.js', 'target/webapp/']
    env:
      port: 8888
      webapp: "#{__dirname}/target/webapp/"

gulp.task 'watch:apimock', ->
  gulp.watch 'apimock.coffee', ['compile:apimock']

gulp.task 'compile:apimock', ->
  gulp.src 'apimock.coffee'
    .pipe coffee()
    .pipe gulp.dest 'target/'

このスクリプトは下記を実行します。

  • bowerの依存関係をインストールします。
  • CoffeeScriptコンパイルしてminifyします。minifyする時にAngularJSに必要な情報が失われないようにng-annotateを適用します。
  • LESSをコンパイルします。
  • すべてのリソースを build フォルダに保存します。

必要なプラグインをインストールしておきます。

npm install gulp-coffee --save-dev
npm install gulp-concat --save-dev
npm install gulp-less --save-dev
npm install gulp-ng-annotate --save-dev
npm install gulp-nodemon --save-dev
npm install gulp-uglify --save-dev

gulp コマンドでビルドを実行すると build フォルダにリソースが出力されます。

gulp

ファイルの変更を監視してビルドを実行するには watch タスクを実行します。

gulp watch
ローカルサーバでプレビュー

先ほどのタスクはファイルを生成するだけでしたが、gulpからローカルサーバを立ち上げてプレビューすることも可能です。expressで簡単なコードを書くことで、バックエンドAPIのスタブと組み合わせてプレビューできます。

以下の内容で apimock.coffee というスクリプトを配置します。

express = require 'express'
app = express()

app.use express.static process.env.webapp

app.get '/api/v1/hoge', (req, res) ->
  res.set 'Content-Type', 'application/json'
  res.send JSON.stringify
    price: 100

app.post '/api/v1/hoge', (req, res) ->
  res.set 'Content-Type', 'application/json'
  res.send JSON.stringify
    price: 200

app.listen process.env.port

必要なパッケージをインストールしておきます。

npm install express --save-dev

serverタスクを実行することで、ローカルサーバがポート8888で起動します。

gulp server
別の環境で git clone した場合の対応

下記を実行するだけでビルドが完了します。

npm install
gulp

ビルドも簡単になるし、リポジトリもすっきりするし、便利ですね。間違いがあったら指摘ください。

*1:正しいお作法を知っていたら教えてください