【2020年最新】Gulp4 + Webpackの超オススメ設定を公開【爆速コーディング環境構築】

こんにちは!フリーランスWebディレクター兼エンジニアののせっち@nosecchi01です。

以前Gulp4に関する記事を投稿しており、当ブログ内では常に上位アクセスのある人気記事となっています。

しかし、こちらはGulp v3からv4への移行期に書いた記事でして、非推奨な書き方や不要なものを含んでいました。

例えば下記。

  • gulp.taskは非推奨の書き方だった。
  • ソースマップが標準装備(他にもあるけど)になり、gulp-sourcemapsは不要になった。
  • autoprefixerのbrowserslistはpackage.json側への記載が推奨となった。

これらに基づいてgulpfileの中身を大幅に見直しました。

結果、コード量が減って見通しがよくなったと思います。

また、下記の通り新しい要素や整理した項目があり、控えめに言ってかなり進化しました。

  • webpackの導入
  • 新しいgulpプラグインの導入
  • 不要(使っていない)プラグインの整理

前回記事からの変更点を中心に書いていきますので、node、gulpのインストール等は割愛します。

node, gulpのインストールまでは前回記事と同様です!

ここから少し変更点の解説が入ります。

解説は要らないからコードを見たい!という方はこちらからジャンプしてくださいm(_ _)m

gulp.taskは非推奨の書き方だった。

Gulp4からgulp.taskは非推奨になりました。

<前回記事の書き方>

gulp.task('defaultTask', function(done) {
 // ここにタスクを書く
  done();
});

下記のような書き方になります。

function defaultTask(done) {
  // ここにタスクを書く
  done();
}

exports.default = defaultTask;

アロー関数などを使ってもう少しスッキリさせます。(本記事はこの書き方)

const defaultTask = done => {
  // ここにタスクを書く
  done();
}

exports.default = defaultTask;

非推奨とはいえ、gulp.taskでも動きますので過度に気にしなくてもいい気がしますが、gulp.taskを書かないことで、コード自体はスッキリします!

公式のリファレンスにサンプルコードがありますのでそちらも参考に!

ソースマップが標準装備(他にもあるけど)になり、gulp-sourcemapsは不要になった。

Gulp4からソースマップが標準装備になり、src ,destの第二引数に書けばOKになりました。

const { src, dest } = require("gulp");
const sass = require('gulp-sass');
// const sourcemaps = require('gulp-sourcemaps'); 要らない!!

const compileSass = done => {
  src("./src/scss/**/*.scss", { sourcemaps: true  /* init */ })
  .pipe(sass({outputStyle: 'expanded'}))
  .pipe(dest("./dist/css", { sourcemaps: './sourcemaps' /* write */ }));
  done();
}

.pipe(dest(“./dist/css”, { sourcemaps: true }));
とするとcss内に直接ソースマップが書き込まれます。

autoprefixerのbrowserslistはpackage.json側への記載が推奨となった。

これはGulp4での変更点ではなく、autoprefixerの方かもですが・・・。

前回記事では、gulpfile.jsにoverrideBrowserslist という形で記載していました。

具体的には下記の部分です。

var postcss = require('gulp-postcss'); //autoprefixerとセット
var autoprefixer = require('autoprefixer'); //ベンダープレフィックス付与

.pipe( postcss([ autoprefixer(
{
// ☆IEは11以上、Androidは5以上
// その他は最新2バージョンで必要なベンダープレフィックスを付与する
"overrideBrowserslist": ["last 2 version", "ie >= 11", "Android >= 5"],
cascade: false}
) ]) )

公式に下記のような記載があります。

The best way to provide browsers is a .browserslistrc file in your project root, or by adding a browserslist key to your package.json.

(中略)

overrideBrowserslist (array): list of queries for target browsers. Try to not use it. The best practice is to use .browserslistrc config or browserslist key in package.json …(以下略)

https://www.npmjs.com/package/autoprefixer

(ざっくり翻訳)

.browserslistrcファイルを作るか、package.jsonにbroserslistキーで書いてね!overrideBrowserslist は使わないようにしてね!

ということで、package.jsonにお引越しです。

{
  // 省略

  "browserslist": [
    "last 1 version",
    "> 1%",
    "IE 11"
  ]
}

お引越しとともに、IE11以外は狭めの対応に変えました。

参考:そのベンダープレフィックス、いつまでつけてるの?

https://qiita.com/amamamaou/items/42197e443134478befaf

修正版の全体像を確認する

僕のgulpfile.js及びwebpack.config.jsの中身を全て公開しますので、好きなように持っていっていただいてOKです。

基本的にscssとbrowserSync、webpackでjs x babelしかやっていません。

gulpfile.js

const { src, dest, watch, series, parallel } = require("gulp");
// 直列処理(順番に処理):series(), 並列処理(順番を問わない):parallel()
const sass = require('gulp-sass');
const plumber = require('gulp-plumber');
const postcss = require('gulp-postcss');
const autoprefixer = require('autoprefixer');
const cssdeclsort = require('css-declaration-sorter');
const sassGlob = require('gulp-sass-glob'); // @importを纏めて指定
const browserSync = require('browser-sync');
const gcmq = require('gulp-group-css-media-queries'); // media queryを纏める
const mode = require('gulp-mode')({
  modes: ['production', 'development'], // 本番実装時は 'gulp --production'
  default: 'development',
  verbose: false,
})
const webpack = require("webpack");
const webpackStream = require("webpack-stream"); // gulpでwebpackを使うために必要なプラグイン

// webpackの設定ファイルの読み込み
const webpackConfig = require("./webpack.config");

const compileSass = done => {
  const postcssPlugins = [
    autoprefixer({
    // browserlistはpackage.jsonに記載[推奨]
    cascade: false,
    // grid: 'autoplace' // IE11のgrid対応('-ms-')
    }),
    cssdeclsort({ order: 'alphabetical' /* smacss, concentric-css */ })
  ]

  src("./src/scss/**/*.scss", { sourcemaps: true  /* init */})
  .pipe(plumber())
  .pipe(sassGlob())
  .pipe(sass({outputStyle: 'expanded'}))
  .pipe(postcss(postcssPlugins))
  .pipe(mode.production(gcmq()))
  .pipe(dest("./dist/css", { sourcemaps: './sourcemaps' /* write */ }));
  done(); // 終了宣言
}

// ローカルサーバ起動
const buildServer = done => {
  browserSync.init({
    port: 8080,
    notify: false,
  // 静的サイト
    server: {
      baseDir: './',
    },
    // 動的サイト
    // files: ['./**/*.php'],
    // proxy: 'http://localsite.wp/',
  })
  done()
}

// ブラウザ自動リロード
const browserReload = done => {
  browserSync.reload()
  done()
}

// webpack
const bundleJs = () => {
  // webpackStreamの第2引数にwebpackを渡す
  return webpackStream(webpackConfig, webpack)
    .pipe(dest("./dist/js"));
};

// ファイル監視
const watchFiles = () => {
  watch('./**/*.html', browserReload)
  watch('.src/scss/**/*.scss', series(compileSass, browserReload))
  watch('.src/js/**/*.js', series(bundleJs, browserReload))
}

exports.sass = compileSass;
exports.default = parallel(buildServer, watchFiles);

webpack.config.js

module.exports = {
  // モード値を production に設定すると最適化された状態で、
  // development に設定するとソースマップ有効でJSファイルが出力される
  mode: "development",

  // ローカル開発用環境を立ち上げる
  // 実行時にブラウザが自動的に localhost を開く
  devServer: {
    contentBase: "dist",
    open: true // 自動的にブラウザが立ち上がる
  },

  // メインとなるJavaScriptファイル(エントリーポイント)
  entry: `./js/main.js`,

  // babel
  module: {
    rules: [
      {
        // 拡張子 .js の場合
        test: /\.js$/,
        use: [
          {
            // Babel を利用する
            loader: 'babel-loader',
            // Babel のオプションを指定する
            options: {
              presets: [
                // プリセットを指定することで、ES2020 を ES5 に変換
                '@babel/preset-env',
              ]
            }
          }
        ]
      }
    ]
  },

  // ファイルの出力設定
  output: {
    // 出力ファイル名
    filename: "bundle.js"
  },

};

Webpack設定に関しては、ICS MEDIAさんのものを丸パクリで構築させていただきましたm(_ _)m

イチから構築し直す場合、以下の順番で作っていくと混乱が少ないかもです。

ということで、Webpackに関しては完全に他力本願です・・・。

ICS MEDIAさんの記事は最新版にリライトされているので安心ですし、わかりやすすぎて詰まる事なく構築できるためオススメです。(所要時間1時間弱)

gulpfile.jsの中身を解説する

ということで、gulpfile.jsの解説を中心にやっていきます。

gulp-sass

const { src, dest, watch, series, parallel } = require("gulp");
const sass = require('gulp-sass');
const plumber = require('gulp-plumber');
const postcss = require('gulp-postcss');
const autoprefixer = require('autoprefixer');
const cssdeclsort = require('css-declaration-sorter');
const sassGlob = require('gulp-sass-glob'); // @importを纏めて指定
const gcmq = require('gulp-group-css-media-queries'); // media queryを纏める
const mode = require('gulp-mode')({
  modes: ['production', 'development'], // 本番実装時は 'gulp --production'
  default: 'development',
  verbose: false,
})

const compileSass = done => {
  const postcssPlugins = [
    autoprefixer({
    // browserlistはpackage.jsonに記載[推奨]
    cascade: false,
    // grid: 'autoplace' // IE11のgrid対応('-ms-')
    }),
    cssdeclsort({ order: 'alphabetical' /* smacss, concentric-css */ })
  ]

  src("./src/scss/**/*.scss", { sourcemaps: true  /* init */})
  .pipe(plumber())
  .pipe(sassGlob())
  .pipe(sass({outputStyle: 'expanded'}))
  .pipe(postcss(postcssPlugins))
  .pipe(mode.production(gcmq()))
  .pipe(dest("./dist/css", { sourcemaps: './sourcemaps' /* write */ }));
  done(); // 終了宣言
}

// ファイル監視
const watchFiles = () => {
  watch('./scss/**/*.scss', series(compileSass, browserReload))
}

exports.sass = compileSass;
exports.default = parallel( /* buildServer, */ watchFiles);

変更点

  • sourcemapsの記述変更(解説済み)
  • メディアクエリを纏める gulp-group-css-media-queries を導入
  • 開発モードと本番モードを分ける gulp-mode を導入

メディアクエリを纏める gulp-group-css-media-queries を導入

僕はscssではクラスごとにメディアクエリを書いていくのですが、そうすると、cssにコンパイルする際に同じメディアクエリを何回も書いていることになります。

 gulp-group-css-media-queries (gcmq)を使うと、いい感じにまとめてくれるのでオススメです!

いい感じ:max-widthなら大きい順、min-widthなら小さい順に並べてくれる!

開発モードと本番モードを分ける gulp-mode を導入

うまいやり方があるのかもしれませんが、sourcemapsとgcmqは多分両立しません。

なので、下記の対応を取ることにしています。

  • 開発時はsourcemapsを使用。
  • 本番環境にsourcemapsは要らないので、gcmqでメディアクエリを纏める。

gcmqgulp-modeを組み合わせると、こんな感じになります。(コードは適当)

.contents {
  margin: 0 auto;
  max-width: 800px;
  width: 100%;
  @media screen and (max-width: 768px){
    padding: 0 25px;
  }
  @media screen and (max-width: 375px){
    padding: 0 15px;
  }
}

h1 {
  font-size: 48px;
  @media screen and (max-width: 768px){
    font-size: 32px;
  }
  @media screen and (max-width: 375px){
    font-size: 24px;
  }
}

開発モード(development)でコンパイル

gulp sass
.contents {
  margin: 0 auto;
  max-width: 800px;
  width: 100%;
}

@media screen and (max-width: 768px) {
  .contents {
    padding: 0 25px;
  }
}

@media screen and (max-width: 375px) {
  .contents {
    padding: 0 15px;
  }
}

h1 {
  font-size: 48px;
}

@media screen and (max-width: 768px) {
  h1 {
    font-size: 32px;
  }
}

@media screen and (max-width: 375px) {
  h1 {
    font-size: 24px;
  }
}

max-width: 768pxとmax-width: 375pxが2回出てくる。

本番(produciton)でコンパイル

gulp sass --production
.contents {
  margin: 0 auto;
  max-width: 800px;
  width: 100%;
}

h1 {
  font-size: 48px;
}

@media screen and (max-width: 768px) {
  .contents {
    padding: 0 25px;
  }

  h1 {
    font-size: 32px;
  }
}

@media screen and (max-width: 375px) {
  .contents {
    padding: 0 15px;
  }

  h1 {
    font-size: 24px;
  }
}

メディアクエリを纏めてくれた。

その他の解説事項

  • autoprefixerのoptionでgrid: autoplaceにすると、gridをIE対応してくれる。
  • cssdeclsortはalphabeticalの他にsmacss, centric-cssを指定可。(smacssが好きな人は割といそう)
  • デスクトップ通知が欲しい方はgulp-notifyを入れましょう!

gulp-notifyの例

const { src, dest } = require("gulp");
const sass = require('gulp-sass');
const plumber = require('gulp-plumber');
const notify = require('gulp-notify');

const compileSass = done => {
  src("./src/scss/**/*.scss", { sourcemaps: true  /* init */})
  .pipe(plumber(({ errorHandler: notify.onError("Error: <%= error.message %>") })))
  .pipe(sass({outputStyle: 'expanded'}))
  .pipe(dest("./dist/css", { sourcemaps: './sourcemaps' /* write */ }));
  done()
}

exports.sass = compileSass;

browser-sync

機能面は変わりませんが、若干コードがスッキリしました。

const browserSync = require('browser-sync');

// ローカルサーバ起動
const buildServer = done => {
  browserSync.init({
    port: 8080,
    notify: false,
  // 静的サイト
    server: {
      baseDir: './',
    },
    // 動的サイト
    // files: ['./**/*.php'],
    // proxy: 'http://localsite.wp/',
  })
  done()
}

// ブラウザ自動リロード
const browserReload = done => {
  browserSync.reload()
  done()
}

// ファイル監視
const watchFiles = () => {
  watch('./**/*.html', browserReload)
  watch('./src/scss/**/*.scss', series(compileSass, browserReload))
  watch('./src/js/**/*.js', series(bundleJs, browserReload))
}

exports.default = parallel(buildServer, watchFiles);
  • notify: false,にすると、”browser-sync connected”が出なくなります。(邪魔だから)
  • 静的サイトはserver、動的サイトはproxyを使います。

整理したもの :gulp-imagemin, gulp-ejs

使わないので削除しました。

Tips: 下記のコマンドでgulpのプラグインを消すことができます。

npm uninstall 'plugin-name'

gulp-imagemin

外部サービスで事足りているので、gulpでの圧縮はやらなくなりました。

TinyPNGが定番ですが、最近は割とSquooshもオススメです。

一枚ずつしか圧縮できないのが微妙なのですが、

  • MozJPEG / WebP / OxiPNGなど圧縮できる(圧縮率高い)
  • 画像サイズ(width, height)も調整できる(アスペクト比維持してくれる)
  • 圧縮後の見た目を確認できる。(神)
  • 圧縮はローカルで行われる。(安心)

アプリならImageOptimがオススメです。

Squoosh同様高圧縮率なフォーマットを選択できるのに加え、不要なメタ情報も削除してくれます。

もしgulp-imageminを使うなら、Gulp4からの新機能であるlastRunを使うのは面白いかなと思います。

lastRunは差分処理をしてくれるので、前回から比較して変更されていない画像ファイルはスキップしてくれます。

const { src, dest, lastRun, watch } = require('gulp');
const imagemin = require('gulp-imagemin');

function images() {
  return src('src/images/**/*.jpg', { since: lastRun(images) })
    .pipe(imagemin())
    .pipe(dest('build/img/'));
}

exports.default = function() {
  watch('src/images/**/*.jpg', images);
};

lastRun()

https://gulpjs.com/docs/en/api/lastrun/

それでも導入しなかったのは、watchでないと動かないからですね。

参考:gulp4 lastRun関数の挙動

https://qiita.com/masato_makino/items/da4be3ffa9c185314ae4

僕はimageminをwatchに入れないので、やっぱり要らないかなという判断です。

gulp-ejs

こちらも最近全然使っていないので削除しました。

使いたい方はこちらをどうぞ!

const ejs = require("gulp-ejs");
const rename = require("gulp-rename");
const replace = require("gulp-replace"); 

const compileEjs = done => {
  src(["./ejs/**/*.ejs", "!" + "./ejs/**/_*.ejs"])
  .pipe(plumber())
  .pipe(ejs())
  .pipe(rename({ extname: ".html" }))
  .pipe(replace(/[\s\S]*?(<!DOCTYPE)/, "$1"))
  .pipe(dest("./"));
  done();
}

const watchFiles = () => {
  watch('./ejs/**/*.ejs', series(compileEjs, browserReload))
}

exports.ejs = compileEjs;
exports.default = parallel(buildServer, watchFiles);

簡単なテンプレートファイルも記述しておきますm(_ _)m

<% 
const metaInfo = {
  title: 'トップページのタイトル',
  description: 'トップページのディスクリプション',
  path: './'
}
%>

<%- include(metaInfo.path + 'common/_header', {metaInfo: metaInfo} ) %>

<main>
  <h1>Hello ejs</h1>
  <p>トップページのテキスト</p>
</main>

<%- include(metaInfo.path + 'common/_footer', {metaInfo: metaInfo} ) %>
<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title><%= metaInfo.title %></title>
  <meta name="description" content="<%= metaInfo.description %>">
  <link rel="stylesheet" href="<%= metaInfo.path %>/dist/css/style.css">
</head>
<body>
  <script src="<%= metaInfo.path %>/dist/js/bundle.js"></script>
</body>
</html>

参考記事

下記の記事を参考にしました。ありがとうございますm(_ _)m

Gulp公式

https://gulpjs.com/

gulp v3からv4への記述法移行

https://qiita.com/soraxism/items/6146155b0255ef3f1e99

Gulp4がリリースされたのでgulpfile.jsをアップデートした

https://qiita.com/hibikikudo/items/493fbfbbea183c94b38b

→変更点が非常にわかりやすい記事です。

Gulp4が正式リリースされたので、gulpfile.jsの中身を見直してみましたgulp4+pug+scss

https://qiita.com/sam_sam/items/45db2c948e47e82a5386

gulp v4でgulp.taskを使わないで記載してみる

https://qiita.com/miwashutaro0611/items/24f156868350503d4875

gulp4再入門 gulpfileの分割とnodeモジュールの利用

https://qiita.com/masato_makino/items/ad11058e1a8a009abbdf

→gulpfile.jsは分割管理もできるようです

Javascriptの知識があると環境構築が捗る

gulpfile.jsは当然javascriptですから、バニラjsの知識があると環境構築はグンと捗ります。
今回のGulpアップデートにあたり、「バニラjsの勉強しておいて本当によかった・・・」と強く感じました。

Webサービスでの勉強なら断然ドットインストールがオススメです。
講座数がめちゃくちゃ多いのが特徴です!

また、書籍でしたら実はKindle Unlimitedが割とオススメです。

技術書は基本的に高いので、1〜2ヶ月に1冊程度のペースでも、月額980円のKindle Unlimitedの方がお得です!

30日間無料なので、「微妙だな・・・」と思ったら止めればOKです。

とはいえ、Kindleにも結構技術書はあります。 → Kindle Unlimitedの技術書(一部)

この記事を書いた人

のせっち

福岡県出身。早稲田大学卒。創業100年の鉄鋼商社でバンコク駐在最年少抜擢。毎日擦り切れるまで働かなくても幸せに生きている人々を見てフリーランスになりました。
現在は、主にバンコクでWeb制作、物販を中心に生きています。