Node + Reactプロジェクトでの最高のgulp環境構築

# はじめに
この記事は[Gizumoエンジニア Advent Calendar 2015](http://www.adventar.org/calendars/919)の1日目の記事です。

株式会社Gizumoという今年できたばかりの会社でフロントエンドエンジニアをやってる[@nabeliwo](https://github.com/nabeliwo)です。僕らもアドベンドカレンダーに参加することで会社盛り上げていきましょうって感じでやってみました。
きっと明日以降もうちの会社のエンジニアだったりデザイナーだったりディレクターだったりな人たちが面白い記事をあげてくれるでしょう。

そんなわけで記念すべき初日を受け持つ僕は初日らしからぬとっても地味な環境構築の話をしたいと思います！

# NodeとReactをひたすら書いてるので
そういうわけで最近はNodeとReactでSPAのブログを作りながら勉強している僕なのです。そんな中で良い感じのgulp環境が作れないかと思って試行錯誤した結果わりと納得いく状態になったので載せてみることにしました。

# とりあえずソース貼る
## gulpfile.babel.js
```js
import gulp from 'gulp';
import browserify from 'browserify';
import babelify from 'babelify';
import source from 'vinyl-source-stream';
import nodemon from 'gulp-nodemon';
import BrowserSync from 'browser-sync';

const browserSync = BrowserSync.create();
const reload = browserSync.reload;


gulp.task('browserify', () => {
    browserify('./js/app/app.js', { debug: true })
        .transform(babelify.configure({
            presets: ["react"]
        }))
        .transform('browserify-shim', { global: true })
        .bundle()
        .on('error', err => { console.log('Error : ' + err.message); })
        .pipe(source('app.js'))
        .pipe(gulp.dest('./js/bundle/'))
        .pipe(reload({ stream: true }));
});

gulp.task('nodemon', cb => {
    let called = false;

    return nodemon({
        script: './app.js',
        ext: 'js html',
        ignore: ['./public', 'node_modules'],
        nodeArgs: ['--harmony']
    })
        .on('start', () => {
            if (!called) {
                called = true;
                cb();
            }
        })
        .on('restart', () => {
            setTimeout(() => {
                reload();
            }, 500);
        });
});

gulp.task('browser-sync', ['nodemon'], () => {
    browserSync.init(null, {
        proxy: {
            target: 'http://localhost:3000',
            ws: true
        },
        port: 35729
    });
});


gulp.task('default', ['browser-sync'], () => {
    gulp.watch('./index.html').on('change', reload);
    gulp.watch(['./js/app/*.js', './js/app/**/*.js'], ['browserify']);
});
```
## package.json
```json
{
  "name": "nabeliwo_test",
  "version": "1.0.0",
  "description": "",
  "main": "app.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "nabeliwo",
  "license": "ISC",
  "dependencies": {
    "history": "^1.9.0",
    "humps": "^0.6.0",
    "isomorphic-fetch": "^2.1.1",
    "lodash": "^3.10.1",
    "normalizr": "^1.0.0",
    "react": "^0.14.2",
    "react-dom": "^0.14.2",
    "react-redux": "^4.0.0",
    "react-router": "^1.0.0-rc1",
    "redux": "^3.0.4",
    "redux-logger": "^2.0.4",
    "redux-thunk": "^1.0.0"
  },
  "devDependencies": {
    "babel-core": "^6.1.2",
    "babel-preset-es2015": "^6.1.2",
    "babel-preset-react": "^6.1.2",
    "babelify": "^7.2.0",
    "browser-sync": "^2.10.0",
    "browserify": "^12.0.1",
    "browserify-shim": "^3.8.11",
    "gulp": "^3.9.0",
    "gulp-nodemon": "^2.0.3",
    "vinyl-source-stream": "^1.1.0"
  },
  "browserify-shim": {
    "react": "global:React"
  },
  "babel": {
    "presets": [
      "es2015"
    ]
  }
}

```

とりあえずgulpfile.babel.jsとpackage.jsonをあげましたが特筆すべきものだけ解説します。

- nodemon
- Browsersync
- browserify
- babelify
- browserify-shim

順番に説明していきます。

# gulpはbabelを使ってES6記法で
まず前提としてJSはもう全てES6で書いています。gulpfileをES6対応させるためには[Babel](https://babeljs.io/)を使います。
まずファイル名をgulpfile.babel.jsにします。これだけだとタスク実行時にエラーが出てしまうのでnpmで`babel-preset-es2015`を入れた上でpackage.jsonに以下を記述します。

```json
"babel": {
  "presets": [
    "es2015"
  ]
}
```
これでgulpfileをES6で書くことができるようになります。

# nodemonとBrowsersync
この二つはライブリロードのためのツールです。
Browsersyncでフロント側のファイル変更時に自動でブラウザ更新、nodemonでサーバー側のファイル変更時にサーバーを再起動してブラウザを更新です。

書き方はgulpfile.babel.jsonを見てもらえればいいとして、便利なのはnodemonでサーバーの再起動をしたタイミングで`restart`イベントが起きるのでそれを受け取ってBrowsersyncでブラウザ更新させることができるっていうところなのですが、なぜかタイミングがうまくいかず`restart`イベントのコールバックの中で`setTimeout`で500ミリ秒待って再起動っていうかっこ悪いことをしています…。
ここらへん解決できる人いたら教えていただきたいです。

# browserifyとbabelify、そしてReact対応
言わずもがなのbrowserifyでJSファイルの分割を行っています。
フロント側でJSをES6で書くにはBabelを入れる必要がありますがbrowserifyと同時に使うときはbaberifyを使うことで実現できます。
browserifyをタスクの中で実行し、そこにtransformメソッドを繋げて引数にbaberifyを入れることでES6で書いたJSをbrowserifyを使ってくっつけることができます。

さらに、このときReactを使ってる場合は`babelify.configure({presets:["react"]})`と記述する必要があります。
そしてこれを適応させるには事前にnpmで`babel-preset-react`を入れておく必要があります。

# browserify-shim
browserifyを使ってモジュール呼び出しをするわけですが、Reactとか使ってて闇雲にインポートしてると結合されたファイルがどんどん重たくなっていきます。そういうときにbrowserify-shimを使います。

使い方としては、
browserify実行時にtransformを繋げて引数で`('browserify-shim', { global: true })`と繋げます。
package.json側では例えば

```json
"browserify-shim": {
  "react": "global:React"
}
```
って感じに記述します。
このようにすると

```js
var React = require('react'); 
```
これを

```js
var React = window.React;  
```
こんな風に置き換えてくれる。

HTTPでは多分6つまで並列にリクエストを送ることができるのでbrowserifyではなく別のscriptで読み込んだ方が良いパターンもあって、そういうときに使える。
また、こうすることでブラウザのキャッシュが効いたりする。

ちなみに`global: true`のオプションをつけておくことで、`react-router`みたいな内部でReactを使うライブラリを使ってるときもwindowに紐付いたReactを使ってくれるようになるので結構便利。

# まとめ
そんなわけで今の僕のgulp環境を書いてみました。
本当はまだまだ全然あるんだけど記事として書けそうなのがこれくらいだったのでわりと省略してみた。そのうちもっと色々理解したらしっかり記事にしたいな。
nodemon・browsersyncに関しては微妙だけど結構モダンな環境にできた気がする。

なんか間違ってることとかあったら教えてくれると嬉しいです。以上です。

明日は[@suzumi](https://github.com/suzumi)さん素晴らしい記事を期待しているよ。
では！