Gulp4設定まとめ(Pug/Sass)

# 1. はじめに

Gulp+Pug+SassでWebサイトの開発環境を作りました。
何度か作っているのですが、毎回忘れてしまうのでまとめです。

※ gulp自体のインストール(グローバルインストールとローカルインストール)はされている前提です。
※ npmを使っています。

# 2. ディレクトリ構造
```
dest/
  ├css/
  ├images/
  └javascripts/
  index.html
src/
  ├images/
  ├javascripts/
  ├pages/
  ├pug/
  └sass/
gulpfile.js
package.json
```

# 3. 環境
<img width="577" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/152545/cfb28475-8972-6e73-31e4-0acba9dccd93.png">

Gulp 4.0.2


# 4. インストールしたパッケージ

| パッケージ | 概要 |
| --- | --- |
| gulp-pug | Pugをコンパイル |
| gulp-sass | Sassをコンパイル |
| gulp-autoprefixer | CSSプロパティにベンダープレフィックスを付与 |
| gulp-cssmin| CSSを圧縮|
| gulp-uglify| JSを圧縮 |
| browser-sync| ブラウザのリロード, ローカルサーバーの立ち上げ |
| gulp-plumber| エラーを検出 |
| gulp-notify| デスクトップに通知 |
| del| フォルダやファイルを削除 |

インストールコマンドの一覧です。

```
$ npm i -D gulp-pug
$ npm i -D gulp-sass
$ npm i -D gulp-autoprefixer
$ npm i -D gulp-cssmin
$ npm i -D gulp-uglify
$ npm i -D browser-sync
$ npm i -D gulp-plumber
$ npm i -D gulp-notify
$ npm i -D del
```

# 5. gulpfile.js

`gulp`を実行したら以下のような処理になるようにしました。

**1. destディレクトリ配下のコンテンツを丸ごと削除**
srcディレクトリとdestディレクトリにあるコンテンツに差分がない状態を保ちたいので、`gulp`を実行したらdestディレクトリ配下のコンテンツを丸ごと削除します。

**2. 他タスクを並列で実行**
1の処理が終わったら、コンパイル、ローカルサーバー起動、ファイル監視を並列で実行します。

## 各処理のポイント

### destディレクトリ配下のコンテンツ削除
コンテンツを削除するために使用している`del`パッケージですが、`paths.dest + '/**'`としか書いていない場合、タスクの実行が成功してもコンテンツが削除されないので気をつけましょう。

※ [公式ドキュメント](https://www.npmjs.com/package/del)を読むと、`paths.dest + '/**', '！' + paths.dest` のように明示的に「親ディレクトリを削除しない」と書かないと親ディレクトリも削除対象になるということが書かれているのですが、私の環境では削除自体が実行されませんでした。


``` gulpfile.js
//Clean
gulp.task('clean', function(done) {
  del.sync(paths.dest + '/**', '！' + paths.dest);
  done();
});
```
### Sass -> CSS

SassからCSSにコンパイルする処理では、コンパイルエラーの検知・通知、ベンダープレフィックスの自動付与、CSS圧縮が行われます。


```gulpfile.js
//Sass
gulp.task('css', function() {
  return gulp.src([
    paths.src + '/sass/**/*.scss',
    '!' + paths.src + '/sass/**/_*.scss'
  ])
  .pipe(plumber({ //エラーを検知しデスクトップ通知
    errorHandler: notify.onError("Error: <%= error.message %>")
  }))
  .pipe(sass()) //Sass -> CSS
  .pipe(autoprefixer({ //ベンダープレフィックスを付与
      overrideBrowserslist: 'last 2 versions'
  }))
  .pipe(cssmin()) //圧縮
  .pipe(gulp.dest(paths.dest + '/css'))
});
```

### Pug -> HTML

PugからHTMLにコンパイルする処理では、コンパイルエラーの検知・通知、読みやすいコードへの整形が行われます。

```gulpfile.js
//Pug
gulp.task('html', function() {
  return gulp.src([
    paths.src + '/pages/**/*.pug',
    '!' + paths.src + '/pages/**/_*.pug'
  ])
  .pipe(plumber({ //エラーを検知しデスクトップ通知
    errorHandler: notify.onError("Error: <%= error.message %>")
  }))
  .pipe(pug({pretty: true})) // 読みやすいコードに整形
  .pipe(gulp.dest(paths.dest))
});
```

### JavaScript

JavaScriptは圧縮します。

```gulpfile.js
//JavaScript
gulp.task('js', function() {
  return gulp.src(
    paths.src + '/javascripts/**/*'
  )
  .pipe(uglify())//圧縮
  .pipe(gulp.dest(paths.dest + '/javascripts'))
});
```

### 監視
srcディレクトリ配下の各種ファイルを監視し、変更を検知したら各種ファイル形式にあわせたコンパイルタスクを実行しリロードします。

```gulpfile.js
//Watch
gulp.task('watch', function () {
  const reload = () => {
    browserSync.reload(); //リロード
  };
  gulp.watch(paths.src + '/sass/**/*.scss').on('change', gulp.series('css', reload));
  gulp.watch(paths.src + '/pages/**/*.pug').on('change', gulp.series('html', reload));
  gulp.watch(paths.src + '/javascripts/**/*').on('change', gulp.series('js', reload));
  gulp.watch(paths.src + '/images/**/*').on('change', gulp.series('image', reload));
});
```

## サンプルコード
gulpfile.jsの全文です。

```gulpfile.js
const gulp = require('gulp');
const pug = require('gulp-pug');
const sass = require('gulp-sass');
const autoprefixer =require('gulp-autoprefixer'); 
const cssmin = require('gulp-cssmin');
const uglify = require('gulp-uglify');
const browsersync = require('browser-sync');
const notify = require('gulp-notify');
const plumber = require('gulp-plumber');
const del = require('del');

const paths = {
  src: 'src',
  dest: 'dest'
};

//Pug
gulp.task('html', function() {
  return gulp.src([
    paths.src + '/pages/**/*.pug',
    '!' + paths.src + '/pages/**/_*.pug'
  ])
  .pipe(plumber({
    errorHandler: notify.onError("Error: <%= error.message %>")
  }))
  .pipe(pug({pretty: true}))
  .pipe(gulp.dest(paths.dest))
});

//Sass
gulp.task('css', function() {
  return gulp.src([
    paths.src + '/sass/**/*.scss',
    '!' + paths.src + '/sass/**/_*.scss'
  ])
  .pipe(plumber({
    errorHandler: notify.onError("Error: <%= error.message %>")
  }))
  .pipe(sass({
    outputStyle: 'expanded'
  }))
  .pipe(autoprefixer({
      overrideBrowserslist: 'last 2 versions'
  }))
  .pipe(cssmin())
  .pipe(gulp.dest(paths.dest + '/css'))
});

//JavaScript
gulp.task('js', function() {
  return gulp.src(
    paths.src + '/javascripts/**/*'
  )
  .pipe(uglify())
  .pipe(gulp.dest(paths.dest + '/javascripts'))
});

//Image File
gulp.task('image', function() {
  return gulp.src(
    paths.src + '/images/**/*'
  )
  .pipe(gulp.dest(paths.dest + '/images'))
});

//Browser Sync
gulp.task('browser-sync', function (done) {
  browsersync({
    server: { //ローカルサーバー起動
        baseDir: paths.dest
  }});
  done();
});

//Watch
gulp.task('watch', function () {
  const reload = () => {
    browsersync.reload(); //リロード
  };
  gulp.watch(paths.src + '/sass/**/*').on('change', gulp.series('css', reload));
  gulp.watch(paths.src + '/pages/**/*').on('change', gulp.series('html', reload));
  gulp.watch(paths.src + '/javascripts/**/*').on('change', gulp.series('js', reload));
  gulp.watch(paths.src + '/images/**/*').on('change', gulp.series('image', reload));
});

//Clean
gulp.task('clean', function(done) {
  del.sync(paths.dest + '/**', '！' + paths.dest);
  done();
});

//Default
gulp.task('default',
  gulp.series(
  'clean',
    gulp.parallel(
      'html',
      'css',
      'js',
      'image',
      'watch',
      'browser-sync'
)));
```

# 6. さいごに
毎回Gulpの設定を忘れてしまうので、今後はこの記事を更新するようにしたいと思います。