すぐに使えるGulp4を使ったフロントエンド開発


モダンな開発環境が欲しかった

Gulpもバージョンが4に上がったことにより、インターネット上に掲載されているGulp周りのサンプルコードが古かったりして参考にならなかったのでGulp4で開発環境を構築しました。

アーキテクチャ構成

Gulp4, Babel7, Pug, scss(postcss併用), ES6, Imagemin, Browser-sync

Pug

公式サイト
https://pugjs.org/api/getting-started.html

楽(らく)したかった。
開始タグと閉じタグをとにかく入力するのが面倒でとにかくタイピング数を減らしたかった。

scss

公式サイト
https://sass-lang.com/

ネスト構造とmixinと変数宣言は便利なので使いたかった。
autoprefixerとcssnanoはpostcssを使用してます。

ES6

処理をモジュールごとに分けてES5にもコンパイルしたかった。そして圧縮もしたかった。

Imagemin

パンダの圧縮サイトを使用するのも良いけど、サイトに入ってー、ファイルをドラッグ&ドロップしてー、ダウンロードしてー、開発環境にコピーしてー・・・・・って面倒くさいことをフロントエンドに成り立ての頃は平気でしていましたが、途中から面倒くさい!!!コピーするところ間違えた!!!!あれ!?画像ファイル消えた!???なんてこともありました。
当時は、GITも取り入れていないので、その後の末路はご想像にお任せします。

そこで、npmのgulp-imageminを使用することで今までの悩みを数秒で解決してくれました。
本当に、Gulpとgulp-imageminを開発してくれた方々に感謝です。
jpg, png, gif, svgを事前に設定した画質に圧縮し、gulp-changedと組み合わせることにより、srcフォルダとdistフォルダを比較して、差分があるファイルのみ圧縮してくれるので、毎回すべての画像は圧縮されないので更に時間短縮をすることができます。

Browser-sync

公式サイト
https://browsersync.io/

言わずとしれた、ローカルサーバー起動ライブラリ。
昔は、MAMPを起動して開発環境を起動していましたが、その必要もなくなりこのライブラリだけで、ローカルサーバーを起動することができます。更にファイルの変更も検知してブラウザを自動でリロードしてくれるので、一々、cmd+rを押さなくて良いし、画面のリロードボタンを押す必要も無くなりました。
これだけでも全国のエンジニアの腱鞘炎問題が解決されたのではないかと思います。

タスクのディレクトリ構成

.
├── gulp
│   └── tasks
│       ├── babel
│       │   └── index.js
│       ├── browser_sync
│       │   └── index.js
│       ├── delete
│       │   └── index.js
│       ├── imagemin
│       │   └── index.js
│       ├── pug
│       │   └── index.js
│       └── scss
│           └── index.js
└── gulpfile.js

全体のディレクトリ構造

.
├── gulp
│   └── tasks
│       ├── babel
│       │   └── index.js
│       ├── browser_sync
│       │   └── index.js
│       ├── delete
│       │   └── index.js
│       ├── imagemin
│       │   └── index.js
│       ├── pug
│       │   └── index.js
│       └── scss
│           └── index.js
├── gulpfile.js
├── package.json
└── src
    ├── img
    │   └── sample.png
    ├── js
    │   ├── index.js
    │   └── modules
    │       └── sample.js
    ├── pug
    │   ├── _include
    │   │   ├── _footer.pug
    │   │   ├── _header.pug
    │   │   └── _layout.pug
    │   ├── about
    │   │   └── index.pug
    │   └── index.pug
    └── scss
        ├── base
        │   ├── _layout.scss
        │   ├── _reset.scss
        │   └── _valiable.scss
        ├── components
        │   ├── _footer.scss
        │   └── _header.scss
        └── style.scss

実際のコード

Pug

すごいシンプルです!
この記述だけで、Pug構文をhtmlに変換してくれます。

const gulp = require('gulp');
const pug = require('gulp-pug');

const path = {
  dstDir: './dist',
  srcDir: './src/pug/**/*.pug',
  rmSrcDir: '!./src/pug/**/_*.pug'
}

gulp.task('build:pug', function() {
  return gulp.src([path.srcDir, path.rmSrcDir])
    .pipe(pug())
    .pipe(gulp.dest(path.dstDir));
});

gulp.task('watch:pug', function() {

  const pugOptions = {
    pretty: true // 圧縮はしないでネスト上で出力
  };

  return gulp.src([path.srcDir, path.rmSrcDir])
    .pipe(pug(pugOptions))
    .pipe(gulp.dest(path.dstDir))
})

scss

ネスト、変数宣言、mixinの機能は、gulp-sassのライブラリを使用しています。
cssに変換後は、postcssを使用してベンダープレフィックスと圧縮をしています。
巷ではpostcssを駆使してscssっぽく記述できるようにしている方が多く見受けられましたが、
scssの機能はgulp-sassライブラリに任せれば数行で片付くの工数のことも考えるとベストかと思います。

const gulp = require('gulp');
const scss = require('gulp-sass');
const sourcemaps = require('gulp-sourcemaps');
const postcss = require('gulp-postcss');
const autoprefixer = require('autoprefixer');
const cssnano = require('cssnano');

gulp.task('build:scss', function() {

   const plugins = [
     autoprefixer(),
     cssnano()
   ]

  return gulp.src('./src/scss/**/*.scss')
    .pipe(scss().on('error', scss.logError))
    .pipe(postcss(plugins))
    .pipe(gulp.dest('./dist/css/'));
});

gulp.task('watch:scss', function() {

  const plugins = [
    autoprefixer()
  ]

 return gulp.src('./src/scss/**/*.scss')
   .pipe(sourcemaps.init())
   .pipe(scss().on('error', scss.logError))
   .pipe(postcss(plugins))
   .pipe(sourcemaps.write('./maps/'))
   .pipe(gulp.dest('./dist/css/'));
});

ES6

このタスクが一番苦労しました。
Gulp4 + Babel7 の情報が少なく、ネット上には古いバージョンのサンプルコードが散乱していました。
下記のコードで、ES6をES5に変換してモジュールのインポートの依存関係も解消してくれます。

const gulp = require('gulp');
const uglify = require('gulp-uglify');
const browserify = require('browserify');
const source = require('vinyl-source-stream');
const babelify = require('babelify');

const path = {
  dstDir: './dist/js',
  srcDir: './src/js/index.js'
}

gulp.task('babel', function() {
  return browserify(path.srcDir)
    .transform(babelify.configure({
      presets: ["@babel/env"],
      sourceType: "module"
    }))
    .bundle()
    .pipe(source('bundle.js'))
    .pipe(gulp.dest(path.dstDir));
});

gulp.task('babel:compress', function() {
  return gulp.src('./dist/js/bundle.js')
    .pipe(uglify())
    .pipe(gulp.dest(path.dstDir));
});

Imagemin

世の中、本当に便利になりましたね。
この数十行のタスクで、ディレクトリ構造を維持したまま画像をdistフォルダへコピーまでしてくれます。
おまけにgulp-changedライブラリで、distフォルダとsrcフォルダの画像差分をチェックしてdistフォルダに存在していない画像だけを圧縮してくれます。こちらも画像数が膨大になってくると圧縮時間に数分を費やすこともあるので大幅な時間短縮に繋げることができました。

const gulp = require('gulp');
const imagemin = require('gulp-imagemin');
const pngquant = require('imagemin-pngquant');
const mozjpeg = require('imagemin-mozjpeg');
const changed = require('gulp-changed');

const paths = {
  srcDir : './src/img',
  dstDir : './dist/img'
}

gulp.task('imagemin', function(){
  const srcGlob = paths.srcDir + '/**/*.{png,jpg,gif,svg}';
  const dstGlob = paths.dstDir;
  const options = [
    pngquant({
      quality: '65-80',
    }),
    mozjpeg({
      quality: 70,
    }),
    imagemin.svgo(),
    imagemin.optipng(),
    imagemin.gifsicle()
  ]

  return gulp.src(srcGlob)
    .pipe(changed(dstGlob))
    .pipe(imagemin(options))
    .pipe(gulp.dest(dstGlob));
});

Browser-sync

ローカルサーバーを起動中はファイルの変更を検知してブラウザを自動でリロードしてくれます。

const gulp = require('gulp');
const browserSync = require('browser-sync').create();
const requireDir = require('require-dir');

requireDir('../../tasks', {
  recurse: true 
});

gulp.task('browser-sync', function() {

  browserSync.init({
    server: './dist',
  });

  gulp.watch('./src/pug/**/*.pug', gulp.series('watch:pug'));
  gulp.watch('dist/index.html').on('change', browserSync.reload);

  gulp.watch('./src/**/*.scss', gulp.series('watch:scss'));
  gulp.watch('dist/css/style.css').on('change', browserSync.reload);

  gulp.watch('./src/js/**/*.js', gulp.series('babel'));
  gulp.watch('dist/js/babel.js').on('change', browserSync.reload);
});

gulpfile.js

細分化しているタスクを読み込んで実行しています。
gulpのタスク自体はnpmscriptsを使用して実行しています。

const gulp = require('gulp');
const requireDir = require('require-dir');

requireDir('./gulp/tasks', {recurse: true});

gulp.task('server', gulp.series('browser-sync'));

gulp.task('build', gulp.series('delete', 'build:pug', 'build:scss', 'babel', 'babel:compress', 'imagemin'));

package.json

サーバー起動 html, css, jsの変更を検知してブラウザをリロード
yarn dev
本番モードビルド html, css, js, imageを圧縮
yarn prd
{
  "name": "gulp",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "dev": "gulp server",
    "prd": "gulp build"
  },
  "author": "Tech Three",
  "license": "ISC",
  "devDependencies": {
    "@babel/core": "^7.0.0",
    "@babel/preset-env": "^7.7.1",
    "autoprefixer": "^9.7.1",
    "babelify": "^10.0.0",
    "browser-sync": "^2.26.7",
    "browserify": "^16.5.0",
    "cssnano": "^4.1.10",
    "gulp": "^4.0.2",
    "gulp-babel": "^8.0.0",
    "gulp-changed": "^4.0.2",
    "gulp-imagemin": "^6.1.1",
    "gulp-notify": "^3.2.0",
    "gulp-plumber": "^1.2.1",
    "gulp-postcss": "^8.0.0",
    "gulp-pug": "^4.0.1",
    "gulp-rename": "^1.4.0",
    "gulp-sass": "^4.0.2",
    "gulp-sourcemaps": "^2.6.5",
    "gulp-uglify": "^3.0.2",
    "imagemin-mozjpeg": "^8.0.0",
    "imagemin-pngquant": "^8.0.0",
    "require-dir": "^1.2.0",
    "rimraf": "^3.0.0",
    "vinyl-source-stream": "^2.0.0"
  },
  "browserslist": [
    "last 2 versions"
  ]
}

おわりに

フロントエンドの開発環境はこれがベスト オブ ベストっていうのは無いと思っています。
あくまで案件に則った開発環境構築がベストかと思っています。
今回は、いますぐ使えるGulpを使った開発環境を紹介させていただきました。
少しでも皆様が楽になれることを願っております。