僕なりのフロントエンド開発環境2016年版

ちょっとバージョンアップ
http://qiita.com/koh110/items/f6ecbdc03093675a9527

---

自分なりに使いやすいフロントエンドの開発環境を整えて使っていたけれど、だんだん時代の流れにそぐわない箇所が出てきたのでその部分を書き換えてアップデートしてみた。
http://qiita.com/koh110/items/9c750fb67e40481e52cd

昔のバージョンはv1.0.0というブランチに切り出し
https://github.com/koh110/minjsapp

# やめたもの
* gulp-load-plugins
* run-sequence
* bower

モジュールやライブラリへの依存度を下げたかった

# 変更したもの
* gulp-babel -> webpack + babel-loader

requireしたかった

# 追加したもの
* sassコンパル

生cssだけじゃなくてsassも使いたかった

# gulp-load-pluginsをやめた
作った当初は

「少ないコード量で出来ることが沢山増えた方がプログラムという本質的な作業に打ち込めて素晴らしいじゃん」

と思っていたけど、requireした以外のものが読み込まれていると、メンテナンスする時に何に依存しているのか分からなくなるのでやめた。
依存をはっきりさせるためにも使うものは全部gulpファイルの先頭でrequireします。

# run-sequenceをやめた
taskの同期実行はgulp4.0に組み込まれるから、それまでのつなぎと考えていたが、taskを細かく分割してrun-sequenceを使わなくてもいいレベルまで分解していった方が、gulp4.0が出た時にも移植しやすい気がする、という事でrun-sequenceに依存しないように修正。

run-sequenceに依存していたのは「watch -> build -> サーバーリロード」みたいなタスクだったので、「watch -> build」「build成果物のwatch -> サーバーリロード」のように2つに切り出して、お互いのタスクを疎結合にした。

```js
// jsのwatchタスク
gulp.task('watch-webpack', () => {
...
});
// cssのwatchタスク
gulp.task('watch-styles', () => {
...
});
// 
gulp.task('watch', ['watch-webpack', 'watch-styles'], () => {
  // html, js, cssの成果物どれかに変更があったらサーバをリロード
  gulp.watch([
    './app/index.html',
    './app/dist/app.js',
    './app/dist/style.css'
  ], ['reloadServer']);
});
```

# bowerをやめてnpmに統一
なんか最近はやりじゃなくなってきた気がするし、昔に比べてフロントのライブラリもほぼnpmにそろったからいらなそうという事でnpmに統一。bowerコマンド叩かなくても「npm install」一発で全てが揃うようになった。
ついでに「gulp-inject」「main-bower-files」からも依存がなくなったので削除。

node_modulesディレクトリをそのまま配信するのがなんか嫌だったので、必要なファイルだけvendor.jsという名前で全部concatしてhtmlで読み込んで使う方式

```js
// npmで入れたフロントエンドライブラリのconcat処理
gulp.task('vendor', () => {
  return gulp.src([
      'node_modules/jquery/dist/jquery.min.js',
      'node_modules/angular/angular.js'
    ])
    .pipe(plumber({
      errorHandler: (error) => {
        notify('vendor', error);
      }
    }))
    .pipe(concat('vendor.js'))
    .pipe(plumber.stop())
    .pipe(gulp.dest('./app/dist'));
});
```


# gulp-babel -> webpack + babel-loaderにかえた
「フロントのjsなんてファイル連結するくらいで十分でしょ、webpackとかBrowserifyとかビルドしたらデバッグしにくいし、angularとか使ってれば別に依存とか問題ないし」

と思っていたけど、Node.jsを書き続けていたら、

「なんでフロントエンドでrequire使えないの。どこのファイル同士が関係性強いのかファイル名だけで判断するの無理。デバッグも.mapファイル吐いてくれるからビルドしても全然問題ないじゃん」

という状態になったのでwebpackに宗旨替え。
webpackを選んだのはBrowserifyは1ファイルしか吐き出せない、webpackの方が高速、webpackのがナウいという記述を見たからです。そこまで検証してないので真偽の程は分かりません。
とりあえず今のところ不便はしてないのでこのままの予定。


gulpからwebpack使うためにgulp-webpack使ってみたりwebpack公式からリンクされてるwebpack-streamとかを試してみたけど、watchタスクでエラーになるとgulpのstreamは生きてるのに、webpackは死んでて再起動しないとビルドしてくれないとか、webpack-dev-serverおすすめだよってかいてあるけど僕はbrowser-sync使いたいんじゃい！という事で、結局webpackをNode.jsから使う感じのtaskにした。

ここまで頑張ったけど、watchタスクを成果物だけをwatchするようにしたのでビルドしたらリロードの流れもできるし、素直にwebpackコマンドとwatchタスクを同時にnpm startで呼べばよかったかも。
gulpから呼ぶことで「plumber + node-notifier」でエラーになった時にデスクトップ通知されます、くらいはメリットかもしれない。

https://github.com/shama/webpack-stream/issues/67
> 「plumberとか使ってるとwebpackがゾンビになるんだけど！」

webpackのconfigはgulpファイルの中に書いてしまう主義
https://github.com/koh110/minjsapp/blob/master/gulpfile.js#L37

```js
config = {
  webpack: {
    entry: './app/js/app.js',
    devtool: '#source-map',
    output: {
      path: './app/dist',
      filename: 'app.js'
    },
    externals: {
      document: 'document',
      jquery: '$' // jqueryという名前でrequireできるように
    },
    resolve: {
      // requireする時にrequire('../hoge/fuga')とか相対パス書くのが辛いので
      // ./app/jsをroot pathに指定してrequire('hoge/fuga')と出来るように
      root: './app/js', 
      extensions: ['', '.js']
    },
    module: {
      loaders: [
        {
          test: /\.js$/,
          // es2015で書きたいからbabelでコンパイル
          loader: 'babel-loader',
          query: {
            presets: ['es2015']
          }
        }
      ]
    }
  }
};

const webpackBuild = (conf, cb) => {
  webpack(conf, (err) => {
    if (err) {
      console.error(err);
      throw err;
    }
    if (!cb.called) {
      cb.called = true;
      return cb();
    }
  });
};
gulp.task('webpack', ['lint'], (cb) => {
  const conf = config.webpack;
  webpackBuild(conf, cb);
});
gulp.task('watch-webpack', ['lint'], (cb) => {
  const conf = Object.assign(config.webpack, { watch: true });
  webpackBuild(conf, cb);
});
```

# sassコンパルを追加
「新しく書く部分だけでもsassで綺麗にかきたい」
cssファイルを連結した後、sassをコンパイルしてその2つのファイルを連結するtaskを追加。
sassはpleeeseでコンパイルして、cssとsassの連結したファイルに対してpleeeseでベンダープレフィックスを付与する

```js
// cssをconcatして一旦tmpディレクトリに吐き出す
gulp.task('css', () => {
  return gulp.src(['./app/styles/css/*.css'])
    .pipe(plumber({
      errorHandler: (error) => {
        notify('css', error);
      }
    }))
    .pipe(concat('concat.css'))
    .pipe(plumber.stop())
    .pipe(gulp.dest('./tmp'));
});
// sassをコンパイルして一旦tmpディレクトリに吐き出す
gulp.task('sass', () => {
  return gulp.src('./app/styles/sass/*.scss')
    .pipe(plumber({
      errorHandler: (error) => {
        notify('sass', error);
      }
    }))
    .pipe(pleeease({
      sass: true,
      minifier: false,
      out: 'compile.css'
    }))
    .pipe(plumber.stop())
    .pipe(gulp.dest('./tmp'));
});
gulp.task('styles', ['css', 'sass'], () => {
  return gulp.src([
    './tmp/concat.css',
    './tmp/compile.css'
  ])
    .pipe(plumber({
      errorHandler: (error) => {
        notify('style', error);
      }
    }))
    .pipe(concat(config.style.output.filename))
    .pipe(pleeease({
      autoprefixer: {
        browsers: [
          'last 1 versions',
          'ie >= 10',
          'safari >= 8',
          'ios >= 8',
          'android >= 4'
        ]
      },
      minifier: false
    }))
    .pipe(plumber.stop())
    .pipe(gulp.dest('./app/dist'));
});
```