gulp4の設定方法 - SassやAutoprefixer、ejs、画像の圧縮などを自動化する

gulpとはnode.jsベースで動くパッケージで、いままで面倒だった作業を自動化してくれる優れもの。今回gulp4で設定する内容は以下になります。
- Sass(expandedとcompressedを選択可)
- compressedときはcssを1行にする
- autoprefixer(grid対応も)
- htmlの共通部分のパーツ化(ejs)
- 画像の圧縮
- scriptの結合と圧縮(順番通りに結合してminify)
- 開発と本番で処理を分岐
- sourcemapの作成と削除
説明する環境は以下の通りです。
- macOS Mojar v10.14.6
- Visual Studio Code v1.39.2
gulp4に必要なパッケージを確認する
gulp4の設定を説明する前に、必要なパッケージがインストールできているか確認しましょう。必要なパッケージは3つ。
- Homebrew
- nodebrew
- node.js

gulpを使うにはまずnode.jsが必要になります。node.jsで使うgulpのようなパッケージはnpmで管理されますが、npmはnode.jsをインストールすると自動で付いてきます。
node.jsのバージョンによっては動かないパッケージがあるのでnode.jsのバージョンを自由に切り替えられるnodebrewが必要になります。Macにパッケージをバラバラにインストールすると管理が大変。そこでMacでインストールしたパッケージを一括して管理するためにhomebrewが必要になる、ということです。
このように管理する場所を明確にすることで、パッケージのインストール、バージョンアップや切り替えなどがスムーズに行えるようになります。
これらのHomebrew、nodebrew、node.jsをインストールする流れは以下の記事を参考にしてみてください。
gulp4の設定をする
まずは「gulp-model」というフォルダを作って、その中に必要なファイルやフォルダを作っていくことにします。ワークツリーのイメージは以下。「src」には処理前のデータを入れておいて、「dist」には処理後のデータを出力させます。

「dist」と「src」の中は以下のようになっています。

package.jsonを準備する
まずはターミナルを使ってpackage.jsonを作ります。VScodeを使っている場合は、【 control + shift + @ 】でターミナルを開いて、以下のコマンドを打ちましょう。
npm init -y
gulp4をインストールする
ローカルとグローバルの両方にインストールする方法を見かけますが、バージョン違いでエラーになることもあるので、ローカルのみにインストールします。
npm install -D gulp
gulp4で使うパッケージをインストールする
gulpに本体をインストールできたら、自動化に必要はパッケージをインストールしていきます。以下のコマンドを必ず1行ずつコピーして実行してください。
npm i gulp -D gulp-sass # Sassを使う
npm i gulp -D gulp-sass-glob # sassのimportを楽にする
npm i gulp -D gulp-postcss # Autoprefixと一緒に使うもの
npm i gulp -D autoprefixer # Autoprefix
npm i gulp -D gulp-plumber # エラーでも強制終了させない
npm i gulp -D gulp-notify # エラーのときはデスクトップに通知
npm i gulp -D gulp-htmlmin # htmlの圧縮・整理
npm i gulp -D del # ファイル、ディレクトリの削除
npm i gulp -D gulp-ejs # htmlのパーツ化
npm i gulp -D gulp-concat # ファイルの結合
npm i gulp -D gulp-order # 指定した順番で並べる
npm i gulp -D gulp-rename # ejsの拡張子を変更
npm i gulp -D gulp-clean-css # cssの圧縮
npm i gulp -D imagemin-pngquant # 画像圧縮
npm i gulp -D gulp-imagemin # 上と同じ
npm i gulp -D imagemin-mozjpeg # 上と同じ
npm i gulp -D gulp-uglify # script圧縮用
npm i gulp -D browser-sync # ブザウザ読み込み・反映
npm i gulp -D gulp-replace # 余計なテキストを削除
npm i gulp -D gulp-mode # 開発と本番の処理を分ける
まとめてインストールしたい方は半角スペースをあけて繋げて書くこともできます。
npm i -D gulp gulp-sass gulp-sass-glob gulp-postcss autoprefixer gulp-plumber gulp-notify gulp-htmlmin del gulp-ejs gulp-concat gulp-order gulp-rename gulp-clean-css imagemin-pngquant gulp-imagemin imagemin-mozjpeg gulp-uglify browser-sync gulp-replace gulp-mode
パッケージがインストールできると、package.jsonにパッケージ名が載っているので確認してみましょう。

gulpfile.jsに自動化処理を書く
gulpfile.jsを作成します。これはターミナルを使わず、通常のファイルを作成する方法と同じです。
gulpfile.jsには以下の内容を書いています。
//gulpとgulpのパッケージを読み込み
const { src, dest, watch, lastRun, parallel, series } = require("gulp");
const sass = require("gulp-sass"); //Sassを使う
const glob = require("gulp-sass-glob"); //sassのimportを楽にする
const postcss = require("gulp-postcss"); //Autoprefixと一緒に使うもの
const autoprefixer = require("autoprefixer"); //Autoprefix
const plumber = require("gulp-plumber"); //エラーでも強制終了させない
const notify = require("gulp-notify"); //エラーのときはデスクトップに通知
const htmlmin = require("gulp-htmlmin"); //htmlの圧縮・整理
const del = require("del"); //ファイル、ディレクトリの削除
const ejs = require("gulp-ejs"); //htmlのパーツ化
const concat = require("gulp-concat"); //ファイルの結合
const order = require("gulp-order"); //指定した順番で並べる
const rename = require("gulp-rename"); //ejsの拡張子を変更
const cleanCSS = require("gulp-clean-css"); //cssの圧縮
const pngquant = require("imagemin-pngquant"); //画像圧縮
const imagemin = require("gulp-imagemin"); //上と同じ
const mozjpeg = require("imagemin-mozjpeg"); //上と同じ
const uglify = require("gulp-uglify"); //script圧縮用
const browserSync = require("browser-sync"); //ブザウザ読み込み・反映
const replace = require("gulp-replace"); //余計なテキストを削除
//開発と本番で処理を分ける
//今回はhtmlの処理のところで使っています
const mode = require("gulp-mode")({ //開発と本番の処理を分ける
modes: ["production", "development"],
default: "development",
verbose: false
});
//読み込むパスと出力するパスを指定
const srcPath = {
html: {
src: ["./src/ejs/**/*.ejs", "!" + "./src/ejs/**/_*.ejs"],
dist: "./dist/"
},
styles: {
src: "./src/scss/**/*.scss",
dist: "./dist/css/",
map: "./dist/css/map"
},
scripts: {
src: "./src/js/**/*.js",
dist: "./dist/js/",
map: "./dist/js/map",
core: "src/js/core/**/*.js",
app: "src/js/app/**/*.js"
},
images: {
src: "./src/img/**/*.{jpg,jpeg,png,gif,svg}",
dist: "./dist/img/"
}
};
//htmlの処理自動化
const htmlFunc = () => {
return src(srcPath.html.src)
.pipe(
plumber({ errorHandler: notify.onError("Error: <%= error.message %>") })
)
.pipe(ejs({}, {}, { ext: ".html" })) //ejsを纏める
.pipe(rename({ extname: ".html" })) //拡張子をhtmlに
.pipe(
mode.production(
//本番環境のみ
htmlmin({
//htmlの圧縮
collapseWhitespace: true, // 余白を除去する
preserveLineBreaks: true, //改行を詰める
minifyJS: true, // jsの圧縮
removeComments: true // HTMLコメントを除去する
})
)
)
.pipe(replace(/[\s\S]*?(<!DOCTYPE)/, "$1"))
.pipe(dest(srcPath.html.dist))
.pipe(browserSync.reload({ stream: true }));
};
//Sassの処理自動化(開発用)
const stylesFunc = () => {
return src(srcPath.styles.src, { sourcemaps: true })
.pipe(
plumber({ errorHandler: notify.onError("Error: <%= error.message %>") })
)
.pipe(glob())
.pipe(sass({ outputStyle: "expanded" }).on("error", sass.logError))
.pipe(
postcss([
autoprefixer({
// IEは11以上、Androidは4、ios safariは8以上
// その他は最新2バージョンで必要なベンダープレフィックスを付与する
//指定の内容はpackage.jsonに記入している
cascade: false,
grid: true
})
])
)
.pipe(dest(srcPath.styles.dist, { sourcemaps: "./map" }))
.pipe(browserSync.reload({ stream: true }));
};
//Sassの処理自動化(本番用)
const stylesCompress = () => {
return src(srcPath.styles.src)
.pipe(
plumber({ errorHandler: notify.onError("Error: <%= error.message %>") })
)
.pipe(glob())
.pipe(sass({ outputStyle: "compressed" }).on("error", sass.logError))
.pipe(
postcss([
autoprefixer({
//上の指定と同じ
cascade: false,
grid: true
})
])
)
.pipe(cleanCSS())
.pipe(dest(srcPath.styles.dist))
.pipe(browserSync.reload({ stream: true }));
};
//scriptの処理自動化
const scriptFunc = () => {
return src(srcPath.scripts.src, { sourcemaps: true })
.pipe(order([srcPath.scripts.core, srcPath.scripts.app], { base: "./" }))
.pipe(
plumber({ errorHandler: notify.onError("Error: <%= error.message %>") })
)
.pipe(concat("init.js"))
.pipe(uglify({ output: { comments: /^!/ } }))
.pipe(
rename({
suffix: ".min"
})
)
.pipe(dest(srcPath.scripts.dist, { sourcemaps: "./map" }))
.pipe(browserSync.reload({ stream: true }));
};
//画像圧縮の定義
const imagesBase = [
pngquant({
quality: [0.7, 0.85]
}),
mozjpeg({
quality: 85
}),
imagemin.gifsicle(),
//imagemin.jpegtran(), //最新だとjpegtranだとエラーになる
imagemin.mozjpeg(),
imagemin.optipng(),
imagemin.svgo({
removeViewBox: false
})
];
//画像の処理自動化
const imagesFunc = () => {
return src(srcPath.images.src, { since: lastRun(imagesFunc) })
.pipe(plumber({ errorHandler: notify.onError("<%= error.message %>") }))
.pipe(imagemin(imagesBase))
.pipe(dest(srcPath.images.dist));
};
// マップファイル除去
const cleanMap = () => {
return del([srcPath.styles.map, srcPath.scripts.map]);
};
// ブラウザの読み込み処理
const browserSyncFunc = () => {
browserSync({
server: {
baseDir: "./",
index: "index.html"
},
reloadOnRestart: true
});
};
// ファイルに変更があったら反映
const watchFiles = () => {
watch(srcPath.html.src, htmlFunc);
watch(srcPath.styles.src, stylesFunc);
watch(srcPath.scripts.src, scriptFunc);
watch(srcPath.images.src, imagesFunc);
};
exports.default = parallel(watchFiles, browserSyncFunc); //gulpの初期処理
exports.build = parallel(htmlFunc, stylesFunc, scriptFunc, imagesFunc);
exports.sasscompress = stylesCompress;
exports.cleanmap = cleanMap;
Autoprefixerの指定を書く
Autoprefixerの指定をpackage.jsonに加えます。今回の指定は以下のようにしています。
- ie11以上
- Android4以上
- ios safari8以上
- その他は最新の2バージョンに対してベンダープレフィックスをつける
package.jsonを開いて以下を追記します。
"browserslist": [
"last 2 versions",
"ie >= 11",
"Android >= 4",
"ios_saf >= 8"
]

gulpfile.jsの中身を説明
gulpfile.jsに書いた内容を簡単に説明していきます。
読み込むパスと出力するパスを指定
gulpで自動化する上で、処理前にどのフォルダからファイルを読み込むか、処理後に出力されるファイルをどこのフォルダへ出力するか指定する必要があります。
例えばcssの処理するパスは以下のように設定しています。srcの部分は、処理前のファイルパスを、distの部分は処理後に出力するフォルダまでのパスを、mapの部分にはsourcemapを出力するフォルダまでのパスを示しています。
styles: {
src: "./src/scss/**/*.scss", // ここから読み込んで
dist: "./dist/css/", // ここに出力する
map: "./dist/css/map" //sourcemapはここに出力する
}
ワークツリーで見るとこんな感じになります。

htmlの処理自動化
headerやfooterなど各ページで共通部分があればejsを使ってパーツ化することができます。それぞれのページと共通部分をejsの拡張子で作成。そのあとに拡張子をhtmlに変更して、それぞれのフォルダに出力させる処理をしています。
細かい設定については、はにわまんさんの記事を参考にしてみてください。
ワークツリーでは以下のようになります。

htmlの自動化では開発環境と本番環境で処理を分けています。
本番環境に出力するコマンドを打つと、html上にあったコメントや上下の隙間を消しています。1行に圧縮していませんが、インデントが無くなるためコードがすべて左寄りになります。使う場合は以下のコマンドを打ちます。
npx gulp --production
Sassの処理自動化
通常はexpandedでcssへ出力し、soucemapも作成する設定をしています。本番環境への出力のときは、compressedで出力して1行にまとめます。そのときはsoucemapは出力させません。
本番環境への出力のときは以下のコマンドを打ちます。
npx gulp sasscompress
scriptの処理自動化
scriptの自動化処理では、最初にjQuery本体、その次にプラグイン、最後に自分で設定を書いているinit.jsの順番で結合してから圧縮させています。この圧縮処理をするとプラグインのライセンス情報が消えてしまうため、jQuery本体のほうに追記しておく必要があります。
こんな感じになります。

処理前のファイルはcoreにjQuery本体、appにプラグイン、js直下に自分で書く用のinit.jsを置きます。

画像の自動処理化
jpgやpng、gif、svgの圧縮処理をします。lastRunによってwatchから2回目以降はアップロードした画像のみ圧縮処理をしてくれます。(1回目は毎回全画像を圧縮処理)
ファイルや画像が更新されたら自動で反映させる
以下の部分は更新されたファイルや画像をチェックして、先に設定しておいた自動化処理を反映させるための設定です。watchすることでファイルが保存された瞬間に自動化処理が実行されます。
// ファイルに変更があったら反映
const watchFiles = () => {
watch(srcPath.html.src, htmlFunc); // ejsファイルに変更があったら…
watch(srcPath.styles.src, stylesFunc); // sassファイルに変更があったら…
watch(srcPath.scripts.src, scriptFunc); // scriptに変更があったら…
watch(srcPath.images.src, imagesFunc); // 画像をアップロードしたら…
};
自動化処理を実行する
自動化処理のための定義を書いていたので、実際に定義した内容を実行するには以下のコマンドが必要になります。
グローバルにgulpをインストールしていないので、実行するコマンドに注意しましょう。
npx gulp
このコマンドによって、htmlとcss、script、画像の処理、ブラウザへの反映をまとめてやってくれます。コマンドを打ったあとはファイルを保存するだけで自動で反映されます。
ブラウザに表示させず、全体のファイルを更新するだけなら以下です。
npx gulp build
本番環境用に出力するなら以下。本番環境用の出力では、htmlのコメントや余計は上下の隙間を無くす処理を設定しています。
npx gulp --production
以下のコマンドはSassで出力したcssを圧縮する処理をします。これも本番環境用に最後に実行するコマンドです。
npx gulp sasscompress
以下はsourcemapを削除するコマンドです。本番環境に移すときに使います。
npx gulp cleanmap
gulp4の設定を他のプロジェクトでも使うには?
まずは設定済みのgulpfile.jsとpackage.jsonをコピーして他のプロジェクトのフォルダへ移しましょう。移せたら以下のコマンドを実行します。
npm install -D
これでpackage.jsonに書かれたパッケージがインストールされるので、gulpfile.jsがあれば同じ設定で使うことができます。
インストールしたら念のために以下のコマンドを打ってpackage.jsonに書かれたパッケージが正しくインストールできているか確認しましょう。
npm ls --depth=0
複数の同じ案件ならシンボリック化もあり
同じような案件が複数ある場合はnode_modulesをシンボリック化すると何度もインストールしなくて済みます。シンボリックとはwindowsで言うショートカットですね。ベースとなるディレクトリにnode_modulesの本体とpackage.jsonを用意して、それぞれのプロジェクトのディレクトリにはgulpfile.jsとnode_modulesのシンボリックを置いておきます。
シンボリックはfinderから移動させるのではなく、ターミナルから作成しましょう。以下のコマンドで作成することができます。/Users/path/to/の部分にはnode_modulesの本体までのパスを書いてください。
ln -s /Users/path/to/node_modules ./node_modules
gitではnode_modulesを除外する
node_modulesはnpmでインストールしたパッケージを保管する場所。package.jsonと同じ階層に作成されます。ただgitで管理するには重く、また特にnode_modulesがなくてもgulpfile.jsとpackage.jsonがあれば同じ環境を構築することができるので、gitの管理から外しておきます。
.gitignoreという名前のファイルを作成して、以下の内容を書きます。*のワイルドカードをつけることでどの階層にあってもnode_modulesを除外することができます。
#node_modulesを除外する
*node_modules/
まとめと参照サイト
今回はgulp4の設定方法を説明しました。プロジェクトに合わせて設定内容を調整してみてください。今回の設定をするにあたって多くのサイトを参考させてもらいました。特に勉強になったサイトを載せておきます。
- 絶対つまずかないGulp 4入門(2019年版)インストールとSassを使うまでの手順
- gulp4に移行するためにタスクを書き換えてみた
- 初めてのGulp!実戦投入まで!
- 自分のgulpv4 + webpackの設定
- gulp v4でgulp.taskを使わないで記載してみる
- Gulp4で作業爆速化!オススメ設定を公開する【コピペOK】