gulp v4とbabelでタスクを作ってみた

# 最初に
タスクランナーとしてgulpは使っているもののgulp用のコマンドだけ覚えて使っているだけなのでわかっている気がしている状態が続いていた。
それではダメだと思いgulp v4が出たのをきっかけに作ってみようと思った。
v4はv3までと書き方が大きく変化していた為新しい方法で覚えるのがいいと思った。
なるべく脱コピペを実践する。

# 最終的な目標
・[とりあえず1から作ってみる](#とりあえず1から作ってみる)
・[pugをhtmlにコンパイル](#pugをhtmlにコンパイル)
・[sassをcssにコンパイルしてmin化](#sassをcssにコンパイルしてmin化)
・[jsをまとめてコンパイルしmin化](#jsをまとめてコンパイルしmin化)
・[eslintでjsの構文チェック](#eslintでjsの構文チェック)
・[imgを圧縮してコンパイル](#imgを圧縮してコンパイル)
・[変更をしないファイルをコピー](#変更をしないファイルをコピー)
・[ファイルに変更が入った場合にタスクを通してブラウザをリロード](#ファイルに変更が入った場合にタスクを通してブラウザをリロード)

# とりあえず1から作ってみる

まずは何をするにもベースとなる'path'を用意するしておく
今回は以下の設定で行うことにした

```gulpfile.babel.js
const path = {
  src : {
    html    : './src/pug/',
    css     : './src/scss/',
    script  : './src/scripts/',
    images  : './src/images/'
  },
  dist : {
    html    : './public/',
    css     : './public/css/',
    script  : './public/js/',
    images  : './public/images/'
  }
}
```

ディレクトリ構成

```
src/
  └ html/
    └ pug/
      └ data/
      └ include/
        └ common/
        └ core/
      └ layout/
      └ pages/
    └ scss/
      └ module/
      └ lib/
    └ script/
      └ common/
      └ lib/

↓ コンパイル後

public/
  └ pages/
  └ css/
    └ lib/
  └ js/
    └ lib/
```

そして最低限以下の設定をする
※パッケージのインストールの仕方はわかってる前提で書きますので省略します

```gulpfile.babel.js
// -- gulp main packages
import gulp from 'gulp'
import fs from 'fs'

// -- gulp other packages
import plumber from 'gulp-plumber'
```

fsモジュールはnode.jsの標準機能
その中のfs.readFileSyncで簡単にファイルの同期読み込みができる
plumberでStream中に起こるのエラーが原因でタスクが強制停止することを防止
watch中にエラーが発生した場合のwatch自体が停止してしまうのを防止

# gulpfile.babel.js

gulpfile.babel.jsでconstを使うためbabelを通す
package.jsonに以下を追加する

```package.json
  "devDependencies": {
    "@babel/core": "^7.4.5",
    "@babel/preset-env": "^7.4.5",
    "@babel/register": "^7.4.4",
  }
```


# pugをhtmlにコンパイル

pugをコンパイルしてdistへ書き出す為に行った方法

```gulpfile.babel.js
// -- gulp html packages
import pug from 'gulp-pug'
```

pugを使ってコンパイルしてみる。
jsonファイルから読み込んでいる箇所もあるのでまずは変数を持たせる
そしてdistへ書き出し

 ```gulpfile.babel.js
 const html = () => {
  const json = JSON.parse(fs.readFileSync( path.src.html + 'data/default.json' ));

  return gulp.src( [path.src.html + '**.pug', path.src.html + '**/[^_]*.pug'], { since: gulp.lastRun(html) } )
    .pipe( plumber({
      errorHandler: function(err) {
        console.log(err.messageFormatted);
        this.emit('end');
      }
    }) )
    .pipe(
      pug({
        pretty: true
      })
    )
    .pipe( gulp.dest(path.dist.html) )
}
```

# sassをcssにコンパイルしてmin化

sassをコンパイルしてmin化するのとreset.cssなどは別ファイルとしてコンパイルできるようにする為に行った方法

```gulpfile.babel.js
// -- gulp sass packages
import autoprefixer from 'gulp-autoprefixer'
import sass         from 'gulp-sass'
import sourcemaps   from 'gulp-sourcemaps'
import cleanCSS     from 'gulp-clean-css'
```

まずsassでコンパイルする
cleanCSSで圧縮ファイルへ変換
sourcemapsでソースマップを作成する
autoprefixerでベンダープレフィックス付与する
そしてdistへ書き出し

```gulpfile.babel.js
// -- build css task
const css = () => {
  return gulp.src( [path.src.css + '**.scss', path.src.css + '**/[^_]*.scss'], { since: gulp.lastRun(css) } )
    .pipe( plumber({
      errorHandler: function(err) {
        console.log(err.messageFormatted);
        this.emit('end');
      }
    }) )
    .pipe(sass({outputStyle: 'expanded'}))
    .pipe(cleanCSS())
    .pipe(sourcemaps.write())
    .pipe(autoprefixer({
        overrideBrowserslist: ['last 3 versions'],
        cascade: false
      }))
    .pipe( gulp.dest(path.dist.css) )
}
```


# jsをまとめてコンパイルしmin化


```gulpfile.babel.js
// -- gulp js packages
import uglify from 'gulp-uglify'
import concat from 'gulp-concat'
import babel  from 'gulp-babel' 
```

`gulp-babel`を入れないと`const`が使えずコンパイルできなかったので注意
concatでファイルを1ファイルにまとめる
ファイル名指定可能なので`all.js`に設定
uglifyで`all.js`をmini化する
distファイルへ書き出す

```gulpfile.babel.js
// -- build js task
const js = () => {
  return gulp.src( [path.src.script + '**.js', path.src.script + 'common/[^_]*.js'], { since: gulp.lastRun(js) } )
    .pipe( plumber({
      errorHandler: function(err) {
        console.log(err.messageFormatted);
        this.emit('end');
      }
    }) )
    .pipe(babel())
    .pipe(concat('all.js'))
    .pipe(uglify({
        compress: true,
        mangle: true,
        output:{
          comments: /^!/
        }
      }))
    .pipe( gulp.dest(path.dist.script) )
}
 ```

# eslintでjsの構文チェック

```gulpfile.babel.js
// -- gulp other packages
import gulpEslint  from 'gulp-eslint'
```

```gulpfile.babel.js
const eslint = () => {
  return gulp.src( [path.src.script + '**.js', path.src.script + 'common/[^_]*.js'], { since: gulp.lastRun(eslint) } )
    .pipe( plumber({
      errorHandler: function(err) {
        console.log(err.messageFormatted)
        this.emit('end')
      }
    }) )
    .pipe(gulpEslint())
    .pipe(gulpEslint.format())
    .pipe(gulpEslint.failOnError())
}
```

eslintを通す前と通した後

```fuga.js
// eslint通す前
var setFuga = {
  init: function () {
    this.setEvent();
  },
  setEvent: function () {
    $('.js-fuga').on('click', function() {
      e.preventDefault();
      $('.js-fuga-button').submit();
    });
  }
}

$(function () {
  // 処理1
  if ($('.js-hoge').length) {
    setFuga.init();
  }
});

↓ eslintを通すとエラーが発生するのでエラーを解消する

// eslint通した後
const setFuga = {
  init() {
    this.setEvent()
  },
  setEvent() {
    $('.js-fuga').on('click', el => {
      el.preventDefault()
      $('.js-fuga-button').submit()
    })
  },
}

$(() => {
  // 処理1
  if ($('.js-hoge').length) {
    setFuga.init()
  }
})
```


# imgを圧縮してコンパイル

jpg,png,gifファイルを画像圧縮する

```gulpfile.babel.js
// -- gulp images packages
import imagemin    from 'gulp-imagemin'
import mozjpeg     from 'imagemin-mozjpeg'
import pngquant    from 'imagemin-pngquant'
import imageminPng from 'imagemin-optipng'
import imageminGif from 'imagemin-gifsicle'
```

`gulp-imagemin`を入れるとその中に`jpg,png,gif`のパッケージが入っているので使いやすい
また`imagemin-jpegtran`をjpgように当初使用していたがロスレスで圧縮しないとダメっぽいということがわかり`imagemin-mozjpeg,imagemin-pngquant`に変更した
svg用のパッケージも入っているが調べた範囲だと使わない方がいいらしい
なので今回はsvgは対象外にしてます

```gulpfile.babel.js
// -- build images task
const img = () => {
  return gulp.src( [path.src.images + '**.+(jpg|jpeg|png|gif)', path.src.images + '**/[^_]*.+(jpg|jpeg|png|gif)'], { since: gulp.lastRun(img) } )
    .pipe( plumber({
      errorHandler: function(err) {
        console.log(err.messageFormatted)
        this.emit('end')
      }
    }) )
    .pipe(changed(path.dist.images))
    .pipe(imagemin([
      mozjpeg({
        quality: 80
      }),
      pngquant(),
      imageminPng(),
      imageminGif({
        interlaced: false,
        optimizationLevel: 3,
        colors: 180
      })
    ]
    ))
    .pipe( gulp.dest(path.dist.images) )
}
```

# 変更をしないファイルをコピー

lib配下にあるjsは何もせずdistへコピーする

```gulpfile.babel.js
// -- build copy task
const copy = () => {
  return gulp.src( [path.src.script + 'lib/[^_]*.js'], { since: gulp.lastRun(copy) } )
    .pipe( plumber({
      errorHandler: function(err) {
        console.log(err.messageFormatted);
        this.emit('end');
      }
    }) )
    .pipe( gulp.dest(path.dist.script + 'lib/') )
}
```

# ファイルに変更が入った場合にタスクを通してブラウザをリロード

ブラウザシンクが使えるように設定する
watchをしてファイルに変更が入った場合ブラウザのリロードをする

```gulpfile.babel.js
// -- gulp other packages
import rimraf      from 'rimraf'
import browserSync from 'browser-sync'
```

以下の`server`指定でブラウザシンクの設定をして初期ファイルの表示を`list.html`に設定
`reload`のタスクを用意してwatchが走った場合に最後にリロードできるように設定

```gulpfile.babel.js
// -- build server task
const server = done => {
  return browserSync.init({
    open: 'external',
    server: {
      baseDir: path.dist.html,
      index  : 'list.html'
    }
  })
  done();
}

const reload = done => {
  browserSync.reload();
  done();
}

// -- build watch task
const watch = done => {
  gulp.watch([path.src.html + '**.pug', path.src.html + '**/*.pug'], gulp.parallel(html,reload))
  gulp.watch([path.src.css + '**.scss', path.src.css + '**/*.scss'], gulp.parallel(css,reload))
  gulp.watch([path.src.script + '**.js', path.src.script + 'common/*.js'], gulp.parallel(js,reload))
  gulp.watch([path.src.images + '**.+(jpg|jpeg|png|gif)', path.src.images + '**/[^_]*.+(jpg|jpeg|png|gif)'], gulp.parallel(img,reload))
  done();
}
```

gulp.series....直列処理
gulp.parallel..並列処理


## tags
gulp4, js, pug, Sass, babel, ESlint


## 感想
今まで使っていたgulpの機能がそもそもどうやって動いているのか、役割が何なのかを理解することができた
使用するパッケージも類似パッケージが多数あり何を選定するかも重要であるかが理解でき選定したパッケージ次第で書きかたなどが違うことなどもわかった
最初は苦手意識が高くエラーも多く吐き出して動かなかったがエラーを1つ1つ解消していくことにより理解も深まった感があった
eslintを導入する事でjsの書き方を統一する事ができるがエラーチェックが厳しいのでエラーを解消するのに時間がかかった