【Gulp4対応】ほどよくこだわるgulpfile


⚠︎Gulp4になって地味に記法が変わっていたので、本記事はちゃんとGulp4で推奨されている記法に沿って書いております！

# 静的ページをガリガリ書こう！とはいえ…
制作する上でたくさん処理がありますよね
sassのコンパイルから始まり、ベンダープレフィックスも書かなきゃいけないし、画像の最適化もしたいし、他にも他にも……
<br>
**め、めんどくさ〜〜い！**
<br>
📣 なら自動化しましょう！
簡単に導入できて、カスタマイズ自在、様々な処理を自動化したいそのお悩み、解決できますよ
そう、gulpならね

<TABLE ALIGN=CENTER>
    <TD WIDTH=400px>
      &ensp;&ensp;&ensp;<A HREF="https://gulpjs.com/" TARGET="_blank"><strong>Gulp.js</strong></a><br>
    <p>　A toolkit to automate & enhance your workflow　　
　　　</p>
    </TD>
    <TD WIDTH=150px>
      <IMG STYLE="float:left;margin-right:.5em;" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/690270/50a28b2d-e13c-7597-919f-8f851225df9f.png" ALT="" WIDTH="120" HEIGHT="120" />
    </TD>

</TABLE>

**gulp**とは、webページなどの制作における煩雑な処理を自動化する**タスクランナー**ツールです
gulpを使用するにはNode.jsが必要なので、インストールした状態で記事を読み進めてください

<br>

# gulpにやってもらうこと
- [ローカルサーバーの立ち上げ（プロキシ指定ver.もあるよ）](#ローカルサーバーの立ち上げプロキシ指定verもあるよ)
- [ファイルの監視、自動リロード](#ファイルの監視自動リロード)
- [ビルド失敗時のデスクトップ通知](#ビルド失敗時のデスクトップ通知)
- [画像の最適化](#画像の最適化)
- [pugの使用とコンパイル](#pugの使用とコンパイル)
- [sassのコンパイル、ベンダープレフィックスの自動付与、cssの圧縮](#sassのコンパイルベンダープレフィックスの自動付与cssの圧縮)
- [Javascriptの検証、圧縮](#javascriptの検証圧縮)


**まずはサクッとgulpをインストール🥤**
プロジェクトのディレクトリを初期化して、

```
npm init -y
```
gulpをインストール

```
npm install --save-dev gulp 
```

<br>
# 完成品
先に、**コピペしたら前項の機能全部使える**package.jsonとgulpfileを置いておきます
次項からの[タスク別解説](#タスク別解説)をすべて合体するとこの形になります

**「説明なんかいらん！とりあえず動くものよこせ！」**

といった方は、これをコピペして自由に俺流にカスタマイズしてください………泣いてないわよ
<br>

package.json👇
（`"name"`と`"repository" > "url"`は適宜変えてください）

``` package.json
{
  "name": "プロジェクト名",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "repository": {
    "type": "git",
    "url": "git://github.com/xxx/xxx.git"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "browser-sync": "^2.26.7",
    "gulp": "^4.0.2",
    "gulp-autoprefixer": "^7.0.1",
    "gulp-eslint": "^6.0.0",
    "gulp-imagemin": "^7.1.0",
    "gulp-pleeease": "^2.0.2",
    "gulp-plumber": "^1.2.1",
    "gulp-pug": "^4.0.1",
    "gulp-sass": "^4.0.2",
    "gulp-svgmin": "^2.2.0",
    "gulp-uglify": "^3.0.2",
    "node-notifier": "^7.0.2",
    "process": "^0.11.10"
  }
}

```
上コピペしたら、`npm install`


gulpfile👇
（`assets_dir`のパスと`src_file`のパスは適宜変えてください）
（ESLintを使用するので、適宜`.eslintrc.json`を用意してください）

``` gulpfile.js
const gulp = require('gulp');
const sass = require('gulp-sass');
const autoprefixer = require('gulp-autoprefixer');
const pleeease = require('gulp-pleeease');
const plumber = require('gulp-plumber');
const uglify = require('gulp-uglify');
const eslint = require('gulp-eslint');
const imgMin = require('gulp-imagemin');
const jpgMin = require('imagemin-mozjpeg');
const pngMin = require('imagemin-optipng');
const gifMin = require('imagemin-gifsicle');
const svgMin = require('gulp-svgmin');
const browserSync = require('browser-sync');
const process = require('process');
const notifier = require('node-notifier');
const pug = require('gulp-pug');


// setting
const assets_dir = {
	css: 'assets/css/',
	js: 'assets/js/',
	img: 'assets/img/',
	root: './assets/'
};
const src_file = {
	scss: 'src/scss/**/*.scss',
	js: 'src/js/**/*.js',
	img: 'src/img/**/*.+(jpg|jpeg|png|gif)',
	svg: 'src/img/**/*.svg',
	pug: '**/*.pug'
};


// task
function exit(done){
	process.exit(0);
	done();
}
exports.task = exit;

const errorHandler = function(error) {
	console.log(error.message);
	notifier.notify({
		message: 'Putted the error in the log',
		title: 'ERROR!',
		appIcon: './err_icon.jpeg'
	});
	// gulp.task(exit);
};

function pug_compile(){
	return gulp
		.src(src_file.pug)
		.pipe(plumber({
			errorHandler: errorHandler
		}))
		.pipe(pug())
		.pipe(gulp.dest('./'));
}
exports.task = pug_compile;

function sass_compile(){
	return gulp
		.src(src_file.scss)
		.pipe(plumber({
			errorHandler: errorHandler
		}))
		.pipe(sass())
		.pipe(pleeease())
		.pipe(autoprefixer())
		.pipe(gulp.dest(assets_dir.css));
}
exports.task = sass_compile;

function js_min(){
	return gulp
		.src(src_file.js)
		.pipe(plumber({
			errorHandler: errorHandler
		}))
		.pipe(eslint())
		.pipe(eslint.format())
		.pipe(eslint.failOnError())
		.pipe(uglify())
		.pipe(gulp.dest(assets_dir.js));
}
exports.task = js_min;

function img_min(){
	return gulp
		.src(src_file.img)
		.pipe(plumber({
			errorHandler: errorHandler
		}))
		.pipe(imgMin([
			jpgMin(),
			pngMin(),
			gifMin({
				optimizationLevel: 3
			})
		]))
		.pipe(gulp.dest(assets_dir.img));
}
function svg_min(){
	return gulp
		.src(src_file.svg)
		.pipe(plumber({
			errorHandler: errorHandler
		}))
		.pipe(svgMin())
		.pipe(gulp.dest(assets_dir.img));
}
exports.task = img_min;
exports.task = svg_min; 

function serve(done) {
	browserSync.init({
		// ローカルサーバー
		server: {
			baseDir: './'
		},
		port: 3000
		
		// プロキシ指定
		// proxy: 'http://●●/',
		// Host: '●●.com',
		// open: 'external'
	});
	done();
}
exports.task = serve;

function reload(done){
	browserSync.reload();
	done();
}
exports.task = reload;

function watch(done){
	gulp.watch(src_file.pug, gulp.task(pug_compile));
	gulp.watch(src_file.scss, gulp.task(sass_compile));
	gulp.watch(src_file.js, gulp.task(js_min));
	gulp.watch(src_file.img, gulp.task(img_min));
	gulp.watch(src_file.svg, gulp.task(svg_min));
	gulp.watch(['assets/**/*', '**/*.html'], gulp.task(reload));
	done();
}
exports.task = watch;

// default
gulp.task('default', gulp.series(serve, watch));
```
上コピペしたら、`npx gulp`で &ensp; $\textrm{So Happy...}$

<br>
# タスク別解説
前項のこだわりgulpfileの中身を、タスク別で分けて解説していきます
必要なタスクを組み合わせて、自分のプロジェクトに応じたgulpfileを作ってみてください

gulpfileの最後に書いているタスク呼び出しは、この記事👇が参考になります
複数タスクを組み合わせたいときに参考にしてみてください

<TABLE ALIGN=CENTER>
    <TD WIDTH=400px>
      <A HREF="https://azunobu.hatenablog.com/entry/2018/10/21/234240" TARGET="_blank"><strong>Gulp - v4 の series と parallel を使って直列・並列処理を制御する</strong></a>&ensp;&ensp;<br>
    <p>Gulp v4 で導入された series と parallel を使ってみました。</p>
    </TD>
    <TD WIDTH=150px>
      <IMG STYLE="float:left;margin-right:.5em;" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/690270/6898d84c-aaca-581d-49b0-fc74470091d0.png
" ALT="" WIDTH="120" HEIGHT="120" />
    </TD>

</TABLE>


本記事でのエントリーポイントと出力先パスは以下の通りに設定しています
自分のプロジェクトのディレクトリ構成に応じて、適宜変更してください

``` gulpfile.js
// setting
const assets_dir = {
	css: 'assets/css/',
	js: 'assets/js/',
	img: 'assets/img/',
	root: './assets/'
};
const src_file = {
	scss: 'src/scss/**/*.scss',
	js: 'src/js/**/*.js',
	img: 'src/img/**/*.+(jpg|jpeg|png|gif)',
	svg: 'src/img/**/*.svg',
	pug: '**/*.pug'
};
```
<br>
<br>
✂︎-------------< 以下、解説 >-------------✂︎
<br>
<br>

## ローカルサーバーの立ち上げ（プロキシ指定ver.もあるよ）

```
npm install --save-dev gulp browser-sync
```

``` gulpfile.js
const gulp = require('gulp');
const browserSync = require('browser-sync');

function serve(done) {
	browserSync.init({
		// ローカルサーバー
		server: {
			baseDir: './'
		},
		port: 3000
		
		// プロキシ指定
		// proxy: 'http://●●/',
		// Host: '●●.com',
		// open: 'external'
	});
	done();
}
exports.task = serve;

gulp.task('serve', serve); //gulp serve 実行でserveタスクが実行される
```
ローカルサーバーの立ち上げには、`browser-sync`パッケージを使用します
⚠︎便宜上プロキシ指定しているオプションはコメントアウトしています（基本的にどちらかを設定するため）

- `baseDir`
    - サーバー立ち上げた時に参照するディレクトリ
- `port`
    - 開放するポート
- `proxy`
    - 仮想サーバーのURL
- `Host`
    - ホスト名
- `open`
    - `external` 外部URLを開く

このままサーバーを立ち上げると、localhost:3000が開きます
プロキシ指定する際は、`server`と`port`オプションを削除して、`proxy` `Host` `open`オプションのコメントアウトを外してください
<br>
## ファイルの監視、自動リロード

```
npm install --save-dev gulp browser-sync
```

``` gulpfile.js
const gulp = require('gulp');
'browser-sync');

// 自動リロード
function reload(done){
	browserSync.reload();
	done();
}

exports.task = reload;

// ファイルの監視、ファイル更新時自動リロード
function watch(done){
	gulp.watch('監視するファイル(パス含む)', gulp.series('タスク名')); //ファイルの監視
	gulp.watch('監視するファイル(パス含む)', gulp.series(reload)); //ファイル更新時自動リロード
	done();
}

exports.task = watch;

gulp.task('watch', watch); //gulp watch 実行でwatchタスクが実行される
```
リロードは`browser-sync`パッケージを使用し、監視は`gulp`パッケージでできます

リロードタスクと監視タスクは分けています
どちらもAPIをそのまま使用するので、特にこちらでオプションを指定する必要はありません

ファイル監視のタスクでは、第一引数のファイルが変更された時に、第二引数のタスクが実行されます
ファイル更新時自動リロードのタスクでは、第一引数のファイルが変更された時に、第二引数の`reload`タスクが実行されます

監視したいファイルを複数指定したい時は配列に入れましょう
<br>
## ビルド失敗時のデスクトップ通知

```
npm install --save-dev gulp node-notifier process
```

``` gulpfile.js
const gulp = require('gulp');
const process = require('process');
const notifier = require('node-notifier');

// gulpの停止
// function exit(done){
// 	process.exit(0);
// 	done();
// }
// exports.task = exit;

// タスク失敗通知
const errorHandler = function(error) {
	console.log(error.message);
	notifier.notify({
		message: 'Putted the error in the log',
		title: 'ERROR!',
		appIcon: './err_icon.jpeg'
	});
	// gulp.task(exit);
};

```
⚠︎次項以降でこのタスクを使用するため、便宜上プロセス停止部分はコメントアウトしています
エラー吐いた時にプロセスも終了したい場合は、このコメントアウトを外してください

プロセス制御に`process`パッケージ、デスクトップ通知に`node-notifier`パッケージを使用します
動作するとこんな感じ👇
![スクリーンショット 2020-09-02 17.26.26.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/690270/267172b6-d1a4-81a2-021d-4370011c3b99.png)  

- `message`
    - 通知の本文
- `title`
    - 通知の見出し
- `appIcon`
    - 画像でいうと現場猫の部分 ~~なにもヨシ！ではない~~  
    癒し画像にするとエラー吐いても心穏やかにいられるよ

エラーログはコンソールに吐き出され、プロセスは終了します
このタスクは基本的に独立して使用するものではなく、他のタスクに組み込んで使用するので、`exports.task`でタスク定義は行いません
<br>
## 画像の最適化

```
npm install --save-dev gulp gulp-plumber gulp-imagemin gulp-svgmin
```

``` gulpfile.js
const gulp = require('gulp');
const plumber = require('gulp-plumber');
const imgMin = require('gulp-imagemin');
const svgMin = require('gulp-svgmin');
const jpgMin = require('imagemin-mozjpeg');
const pngMin = require('imagemin-optipng');
const gifMin = require('imagemin-gifsicle');

function img_min(){
	return gulp
		.src(src_file.img)
		.pipe(plumber({
			errorHandler: errorHandler
		}))
		.pipe(imgMin([
			jpgMin(),
			pngMin(),
			gifMin({
				optimizationLevel: 3
			})
		]))
		.pipe(gulp.dest(assets_dir.img));
}
function svg_min(){
	return gulp
		.src(src_file.svg)
		.pipe(plumber({
			errorHandler: errorHandler
		}))
		.pipe(svgMin())
		.pipe(gulp.dest(assets_dir.img));
}
exports.task = img_min;
exports.task = svg_min; 

gulp.task('imageMin', gulp.parallel(img_min, svg_min)); //gulp imageMin 実行でimg_minタスクとsvg_minタスクが実行される
```
エラー起因のプロセス強制終了を防ぐために`gulp-plumber`パッケージ、
画像圧縮するために`gulp-imagemin` `gulp-svgmin`パッケージを使用し、
画像ファイルそれぞれの拡張子に対応するため、`imagemin-mozjpeg` `imagemin-optipng` `imagemin-gifsicle`プラグインを呼び出しています

- `plumber`
    - `errorHandler`  
    エラーになった時の挙動を指定できます。今回は[先に書いたエラー通知タスク](#ビルド失敗時のデスクトップ通知)を実行するようにしています
- `imgMin`
    - `gifMin`
        - `optimizationLevel`  
        最適化レベルを1〜3で指定します  
今回は一番レベルの高い3を指定しています 時間はかかりますがより精密に最適化が行われます  

タスク内では、
**エントリーポイントの指定 → plumberの起動 → 最適化 → 指定した出力先に吐き出し**
の順で動作しています
<br>
## pugの使用とコンパイル

```
npm install --save-dev gulp gulp-pug gulp-plumber
```

``` gulpfile.js
const gulp = require('gulp');
const plumber = require('gulp-plumber');
const pug = require('gulp-pug');

function pug_compile(){
	return gulp
		.src(src_file.pug)
		.pipe(plumber({
			errorHandler: errorHandler
		}))
		.pipe(pug())
		.pipe(gulp.dest('./'));
}
exports.task = pug_compile;

gulp.task('pug', pug_compile); //gulp pug 実行でpug_compileタスクが実行される
```
エラー起因のプロセス強制終了を防ぐために`gulp-plumber`パッケージ、
pugの使用、HTMLへのコンパイルをするために`gulp-pug`パッケージを使用しています

- `plumber`
    - `errorHandler`  
    エラーになった時の挙動を指定できます。今回は[先に書いたエラー通知タスク](#ビルド失敗時のデスクトップ通知)を実行するようにしています

タスク内では、
**エントリーポイントの指定 → plumberの起動 → pugのコンパイル → 指定した出力先に吐き出し**
の順で動作しています
<br>
## sassのコンパイル、ベンダープレフィックスの自動付与、cssの圧縮

```
npm install --save-dev gulp gulp-sass gulp-plumber gulp-pleeease gulp-autoprefixer
```

``` gulpfile.js
const gulp = require('gulp');
const plumber = require('gulp-plumber');
const pleeease = require('gulp-pleeease');
const sass = require('gulp-sass');
const autoprefixer = require('gulp-autoprefixer');

function sass_compile(){
	return gulp
		.src(src_file.scss)
		.pipe(plumber({
			errorHandler: errorHandler
		}))
		.pipe(sass())
		.pipe(pleeease())
		.pipe(autoprefixer())
		.pipe(gulp.dest(assets_dir.css));
}

exports.task = sass_compile;

gulp.task('sass', sass_compile); //gulp sass 実行でsass_compileタスクが実行される
```
エラー起因のプロセス強制終了を防ぐためにgulp-plumberパッケージ、
sassの使用、cssへのコンパイルをするために`gulp-sass`パッケージ、
コンパイルされたcssの圧縮をするために`gulp-pleeease`パッケージ、
ベンダープレフィックスの自動付与をするために`gulp-autoprefixer`パッケージを使用しています

- `plumber`
    - `errorHandler`  
    エラーになった時の挙動を指定できます。今回は[先に書いたエラー通知タスク](#ビルド失敗時のデスクトップ通知)を実行するようにしています

タスク内では、
**エントリーポイントの指定 → plumberの起動 → sassのコンパイル → cssの圧縮 → ベンダープレフィックスの付与 → 指定した出力先に吐き出し**
の順で動作しています

## Javascriptの検証、圧縮

```
npm install --save-dev gulp gulp-plumber gulp-uglify gulp-eslint
```

``` gulpfile.js
const gulp = require('gulp');
const plumber = require('gulp-plumber');
const uglify = require('gulp-uglify');
const eslint = require('gulp-eslint');

function js_min(){
	return gulp
		.src(src_file.js)
		.pipe(plumber({
			errorHandler: errorHandler
		}))
		.pipe(eslint())
		.pipe(eslint.format())
		.pipe(eslint.failOnError())
		.pipe(uglify())
		.pipe(gulp.dest(assets_dir.js));
}
exports.task = js_min;

gulp.task('jsMin', js_min); //gulp jsMin 実行でjs_minタスクが実行される
```

``` .eslintrc.json
{
  "extends": ["eslint:recommended"],
  "env": {
    "browser": true
  }
}
```
エラー起因のプロセス強制終了を防ぐために`gulp-plumber`パッケージ、
構文チェックのために`gulp-eslint`パッケージ、
Javascriptの圧縮をするために`gulp-uglify`パッケージを使用しています

- `plumber`
    - `errorHandler`  
    エラーになった時の挙動を指定できます。今回は[先に書いたエラー通知タスク](#ビルド失敗時のデスクトップ通知)を実行するようにしています

ESLintを使用するには設定ファイルが必要です
`.eslintrc.json`を用意します
中身では、ESLintの推奨設定を継承して、ESLintで構文チェックする対象コードの実行環境を指定しています

タスク内では、
**エントリーポイントの指定 → plumberの起動 → 構文チェック → Javascriptの圧縮 → 指定した出力先に吐き出し**
の順で動作しています
<br>
<br>
---
<br>
以上！
面倒で煩雑な処理は全部gulpにお任せすれば、コーディングに集中できますね🎉

コピペしてうまく動作しないなどの不具合があったり、コードに間違いなどありましたら、編集リクエストやコメントで教えていただけますと助かります🙇‍♀️

## おまけ
今回は紹介しませんでしたが、Typescriptのパッケージもあるみたい
https://www.npmjs.com/package/gulp-typescript
<br>