Web サイト作るお仕事をしている人向け gulp で作るフロントエンド開発環境

フロントエンドの開発を色々と捗らせてくれる gulp について、ここ最近、私が利用しているパッケージの紹介を中心に、主に Web サイトを制作するお仕事をしている方に向けた、なるべくわかりやすい解説としてまとめてみました。

gulp v4 が正式リリースされたため、本記事で紹介していた gulpfile.js ファイルの記述方法を v4 にあわせて修正しました。詳しくは 「gulp 4.0 が正式リリース、v3 からの移行に伴う gulpfile.js ファイルの修正点」 をご覧ください。

もう 4年くらい前になりますが Web デザイナーさん向けみたいな感じで、所謂 「黒い画面」 にあまり普段から慣れていない方向けの Grunt 導入記事を書いたんですけども、内容的にはもう古いし、Grunt いいよって紹介しておいてなんですが、私自身、記事を書いてから少し経ったところで gulp に開発環境を移行してしまったため、放置した感じになってしまいました。

そこで、今さら感はありますが、Grunt の時と同様、私が使用しているパッケージなどを紹介しつつ、主に Web サイトを制作するお仕事で HTML や CSS を書いている人向けの gulp 環境構築についてまとめておこうと思います。

なお、この手の話の場合、ほぼ必ずと言っていいほど Sass を利用する前提になっている事が多いと思いますが、私、個人的にはあまり Sass を使わずに CSS 書くことが多いので、Sass の話は一切なしになっております。gulp で Sass を使う話なんかは検索すれば腐るほど出てくると思いますので、必要に応じて別途調べてみてください。

また、これも私的なことですが、数年前から Mac は一切やめて、Windows 環境で開発しているため、今回の話も Windows を対象にしたものになっています。Mac 環境をご利用の場合はちょっと手順などがことなりますのでご注意ください。

書いていたら長くなったので目次

gulp さんに何をやってもらうのか

今回の記事で gulp にお任せするのは下記の作業。

  • HTML 関連
    • テンプレートエンジンとして 「Slim」 を使いたい
  • CSS 関連
    • PostCSS を使って CSS を書きたい
    • ベンダプレフィックスの管理とか面倒くさいこと色々
    • 複数の CSS を結合してまとめたり
    • CSS を圧縮 (Minify) とか無駄な記述の統合とか
  • JavaScript 関連
    • CoffeeScript で書きたい
    • 必要に応じて JavaScript ファイルの圧縮 (Minify) とか
  • 画像関連
    • 画像の最適化(PNG / JPEG / GIF / SVG)
  • ブラウザでの表示確認
  • バリデーション
    • HTML ファイルのバリデーション
    • CSS ファイルのバリデーション

これら作業を手作業でやるのは面倒なので、全部 gulp に任せて自動化する感じで。これは Grunt 使っていた時から特に変わっていません。

gulp を使用する前の準備

すでに環境が整っている方は読み飛ばしていただいて大丈夫だと思いますが、念のため gulp 導入までを簡単に。

Node.js をインストール

Node.js 公式サイト ダウンロードページの例

まずは Node.js をインストールします。Node.js 公式サイト にアクセスすると、ダウンロード用のリンクがあります。

ページにアクセスした時点で、自動的に利用中のシステムに合わせたダウンロードボタンが表示されているはずですが、「最新版」 と 「推奨版 (LTS:Long Term Support)」 のどちらを選択するべきかという点については、最新版の機能を使いたいといった特別な理由がないなら、とりあえず 「推奨版 (LTS)」 を選択しておけばよいでしょう。

インストールは基本的にデフォルトのまま進めちゃって大丈夫です。インストール先などを変更したい場合は任意で選択するなりしましょう。

Ruby をインストール

次に Ruby をインストールします。 Sass を利用する場合にも必要な Ruby ですが、今回はテンプレートエンジンとして使用する 「Slim」 で主に利用するためにインストールしておきます。

Windows 環境で Ruby をインストールする場合、RubyInstaller を利用するのが楽です。

RubyInstaller のダウンロードページに移動すると、リストがあると思いますが、利用中の環境に最適なバージョンは 「⇒」 アイコンが付いていると思いますので、それを選択すれば問題なし。

RubyInstaller ダウンロードページの例

インストーラーがダウンロードできたら、起動してインストールしましょう。

gulp をインストール

先ほど、Node.js のインストールが完了していますので、「Node.js command prompt」 というコマンドプロンプト (Windows 標準のコマンドプロンプトでもいいのですが、今回はこちらを前提に進めます) が使用できるようになっていると思います。早速立ち上げてみましょう。

黒い画面が表示されたら、試しに node -v とコマンドを入力し、エンターキーを押してみましょう。先ほどインストールした、Node.js のバージョンが表示されると思います。

ちなみに以下の解説で書いてある各コマンドはコピー & ペーストで使えますので入力が面倒ならコピーして、コマンドプロンプトに貼り付けてください。

いよいよ gulp をインストールしますが、まずは作業フォルダに移動しましょう。例えば、D ドライブ直下に 「project」 というフォルダを作ってそこで開発しますという話なら、

cd /d D:¥project

のようにコマンドを入力すれば、該当フォルダに移動します (フィルダは先に作っておいてくださいね)。

今回は、このフォルダ (D:¥project) を作業フォルダにする前提で進めます。

まずは下記のようにコマンドを入力して、gulp-cli をインストールします。

npm install --global gulp-cli

上記コマンドは、下記のように短縮形で書いても同じ意味です。

npm i -g gulp-cli

-g を付与しているため、インストールは 「グローバル」 に行われます。グローバルというのは、プロジェクトの作業フォルダに関係なく、共通の場所という意味です。

今回、作業フォルダの前提としている D:¥project のようなプロジェクトごとの作業フォルダは、「プロジェクトローカル」 として区別しますので、例えば 「ローカルにインストール」と言えば、作業フォルダ直下にインストールする場合を指します。

gulp-cli をグローバルにインストールするの気持ち悪いっていう考えもありますし、私もなるべくグローバルにインストールしない方法がよいと思いますが、まぁ今まで色々なプロジェクトで利用して特に問題が出たことはないのと、一応、gulp 公式でアナウンスされている方法なのでそちらを尊重する形で今回は進めていきます。

続いて、下記のコマンドを実行し、「package.json」 というインストール済みパッケージの情報を記録しておくためのファイルを生成します。

npm init -y

作業フォルダ直下に、「package.json」 というファイルが生成されていればとりあえず OK。

次に gulp をインストールします。こちらはプロジェクトローカルにインストールしますので、コマンドは下記のようになります。

npm install --save-dev gulp

-g の代わりに、--save-dev を付与していますが、これによって、先ほど作業フォルダ直下に作成した package.json にインストールされたパッケージが書き込まれていきます。後々の管理のためにも必ず付けるようにしましょう。

上記コマンドは、下記のように短縮形で書いても同じ意味です。なお、--save-dev の短縮形は -d ではなく、-D なので注意してください。-d だと別の意味 (--loglevel info の省略形) になってしまいます。

npm i -D gulp

各パッケージを追加

まず、gulp とは直接関係ないのですが、npm で色々とパッケージを追加した際、あとでそれらのアップデートを簡単にするため、npm-check-updates というパッケージを入れておくと便利です。

npm-check-updates はグローバルにインストールします。

npm i -g npm-check-updates

このパッケージを入れておくと、作業フォルダで ncu というコマンドを打つだけで、package.json に記述された各パッケージに更新がないかをチェックしてくれるだけでなく、ncu -u するだけで、アップデートまで一括でやってくれます。

続いて、今回の開発環境構築に必要なパッケージをインストールしていきます。導入するパッケージは下記の通り (アルファベット順)。

各パッケージの説明はとりあえず置いておいて、まずは一気にインストールしてしまいましょう。

npm i -D ^
browser-sync ^
browsersync-ssi ^
css-mqpacker ^
gulp-autoprefixer ^
gulp-coffee ^
gulp-concat ^
gulp-csslint ^
gulp-cssmin ^
gulp-htmlhint ^
gulp-imagemin ^
gulp-notify ^
gulp-plumber ^
gulp-postcss ^
gulp-rename ^
gulp-slim ^
gulp-tinypng-compress ^
gulp-terser ^
node-notifier ^
postcss ^
postcss-color-function ^
postcss-css-variables ^
postcss-custom-properties ^
postcss-extend ^
postcss-import ^
postcss-nested ^
postcss-simple-vars

ちなみに、上記はコマンドを改行して書きたい関係上、末尾に 「^」 を記述していますが、改行なしなら下記のように続けて書けば同じです。

npm i -D browser-sync browsersync-ssi ...(略)... postcss-simple-vars

なお、上記のパッケージを一度インストールした後は、インストール済みのパッケージが package.json に書き込まれていますので、例えば他の作業フォルダで同じ環境を作りたい時は、その作業フォルダに package.json をコピーした上で、

npm i

とするだけで、 package.json に書かれたパッケージが一括インストールされますよ。その後、必要に応じて ncu で更新チェック → ncu -u でアップデートみたいな感じでやると楽です。

あと最後に Slim のインストールも行います。こちらは Ruby 用のパッケージ管理ツールである 「gem」 を使ってインストールします。

gem i slim

gulpfile.js を作成する

必要なパッケージをインストールし終わったところで、gulp の設定ファイルである gulpfile.js を作成しますが、その前に、作業フォルダは下記のようなフォルダ構成になっているものとして書いています。

project
 │
 ├ htdocs (ここ以下が実際の Web サイトのデータを置くところ)
 │  ├ css
 │  │  └ s.min.css(最終的に完成する CSS ファイル)
 │  ├ js
 │  │  ├ script.js(最終的に完成する JavaScript ファイル)
 │  │  └ min
 │  │     └ script.min.js(圧縮済みの JavaScript ファイル)
 │  └ index.html (Slim から書き出される HTML ファイル)
 │
 ├ docs (ここ以下が開発用の各ファイルを置くところ)
 │  └ temp
 │      ├ css (実際の CSS コーディングはここのファイルを編集)
 │      │  ├ 01_css-variables.css
 │      │  ├ 02_normalize.css
 │      │  └ 03_style.css
 │      │
 │      ├ concat
 │      │  └ s.css (一時ファイルとして書き出される未圧縮の CSS ファイル)
 │      │
 │      ├ js (実際の JavaScript コーディングはここのファイルを編集)
 │      │  └ script.coffee
 │      │
 │      ├ slim (Slim を利用したい HTML ファイルはここで編集)
 │      │  ├ index.slim
 │      │  └ inc
 │      │      ├ header.slim
 │      │      └ footer.slim
 │      │
 │      └ img (画像の圧縮処理をする場合に使う)
 │
 ├ node_modules
 ├ package.json
 └ gulpfile.js

実際に作業するのは、プロジェクトフォルダ内の、docs/temp/cssdocs/temp/jsdocs/temp/slim フォルダになります。

例えば CSS ですが、私の場合、CSS ファイルは役割ごとに細かく分割して書いていきたいので、docs/temp/css フォルダ内に複数のファイルを置いて書いていきます。これを gulp で結合して最終的な CSS ファイルを出力するという流れになりますね。

例えば css-variables.css には、変数をまとめて書いておいて、各ファイルにインクルードして使うみたいなことをやっています。この辺はどういう風に分割した方が楽かなとか、あとでわかりやすいかみたいなことを考えつつ試行錯誤しているので、「絶対にこういう分割じゃなきゃダメ」 みたいのはないんですが。

ということで、上記を踏まえて、とりあえず完成版の gulpfile.js を下記に。続けて細かく説明していきます。

今回サンプルとして使用している gulpfile.js と、package.json は GitHub にも上げています (burnworks/gulpfile-sample-for-web-development) のでそちらから落とせます。

var gulp = require('gulp'),
    autoprefixer = require('gulp-autoprefixer'),
    browserSync = require('browser-sync'),
    cssimport = require('postcss-import'),
    cssmin = require('gulp-cssmin'),
    cssv = require('gulp-csslint'),
    cssvariables = require('postcss-css-variables'),
    colorfunction = require('postcss-color-function'),
    coffee = require('gulp-coffee'),
    concat = require('gulp-concat'),
    htmlv = require('gulp-htmlhint'),
    imagemin = require('gulp-imagemin'),
    mqpacker = require('css-mqpacker'),
    nested = require('postcss-nested'),
    notify = require('gulp-notify'),
    plumber = require('gulp-plumber'),
    postcss = require('gulp-postcss'),
    rename = require('gulp-rename'),
    simplevars = require('postcss-simple-vars'),
    slim = require('gulp-slim'),
    ssi = require('browsersync-ssi'),
    tinyping = require('gulp-tinypng-compress'),
    terser = require('gulp-terser');

// concat
gulp.task('css.concat', () => {
  var plugins = [
  colorfunction,
  cssimport,
  cssvariables,
  mqpacker,
  nested,
  simplevars
  ];
  return gulp.src('docs/tmp/css/*.css')
  .pipe(plumber({
    errorHandler: notify.onError('Error: <%= error.message %>')
  }))
  .pipe(postcss(plugins))
  .pipe(autoprefixer({
    browsers: ['last 2 version'],
    grid: true
  }))
  .pipe(concat('s.css'))
  .pipe(gulp.dest('docs/tmp/concat'))
});

// cssmin
gulp.task('cssmin', () => {
  return gulp.src('docs/tmp/concat/s.css')
  .pipe(plumber({
    errorHandler: notify.onError('Error: <%= error.message %>')
  }))
  .pipe(cssmin())
  .pipe(rename({suffix: '.min'}))
  .pipe(gulp.dest('htdocs/css'))
  .pipe(browserSync.stream())
});

// css
gulp.task('css', gulp.series('css.concat', 'cssmin'));

// coffee
gulp.task('coffee', () => {
  return gulp.src('docs/tmp/js/*.coffee')
  .pipe(plumber({
    errorHandler: notify.onError('Error: <%= error.message %>')
  }))
  .pipe(coffee({bare: true}))
  .pipe(gulp.dest('htdocs/js'))
  .pipe(browserSync.stream())
});

// terser
gulp.task('jsmin', () => {
  return gulp.src('htdocs/js/*.js')
  .pipe(plumber({
    errorHandler: notify.onError('Error: <%= error.message %>')
  }))
  .pipe(terser())
  .pipe(rename({suffix: '.min'}))
  .pipe(gulp.dest('htdocs/js/min'))
});

// slim
gulp.task('slim', () => {
  return gulp.src('docs/tmp/slim/*.slim')
  .pipe(plumber({
    errorHandler: notify.onError('Error: <%= error.message %>')
  }))
  .pipe(slim({
    require: 'slim/include',
    pretty: true,
    options: 'include_dirs=["docs/tmp/slim/inc"]'
  }))
  .pipe(gulp.dest('htdocs'))
  .pipe(browserSync.stream())
});

// watch
gulp.task('watch', () => {
  gulp.watch('docs/tmp/css/*.css', gulp.task('css'));
  gulp.watch('docs/tmp/js/*.coffee', gulp.task('coffee'));
  gulp.watch('docs/tmp/slim/**/*.slim', gulp.task('slim'));
});

// browser-sync
gulp.task('browser-sync', () => {
  browserSync({
    server: {
      baseDir: 'htdocs',
      middleware:[
        ssi({
          ext: '.html',
          baseDir: 'htdocs'
        })
      ]
    }
  });
});

// tinypng
gulp.task('tinypng', () => {
  return gulp.src('docs/tmp/img/src/**/*.{png,jpg}')
    .pipe(tinyping({
      key: '__API_Key__'
      summarize: true
    }))
    .pipe(gulp.dest('docs/tmp/img/dist'))
});

// imagemin
gulp.task('imagemin', () => {
  return gulp.src('docs/tmp/img/src/**/*.{png,jpg,gif,svg}')
  .pipe(imagemin([
    imagemin.gifsicle({interlaced: true}),
    imagemin.jpegtran({progressive: true}),
    imagemin.optipng({optimizationLevel: 4}),
    imagemin.svgo({
      plugins: [
        {removeViewBox: false}
      ]
    })
  ]))
  .pipe(gulp.dest('docs/tmp/img/dist'))
});

// htmlhint
gulp.task('htmlv', () => {
  return gulp.src('htdocs/*.html')
  .pipe(htmlv())
  .pipe(htmlv.reporter())
});

// csslint
gulp.task('cssv', () => {
  return gulp.src('htdocs/css/s.min.css')
  .pipe(cssv({
    'adjoining-classes': false,
    'box-model': false,
    'box-sizing': false,
    'bulletproof-font-face': false,
    'compatible-vendor-prefixes': false,
    'empty-rules': true,
    'display-property-grouping': true,
    'duplicate-background-images': false,
    'duplicate-properties': true,
    'fallback-colors': false,
    'floats': false,
    'font-faces': false,
    'font-sizes': false,
    'gradients': false,
    'ids': false,
    'import': false,
    'important': false,
    'known-properties': true,
    'order-alphabetical': false,
    'outline-none': true,
    'overqualified-elements': false,
    'qualified-headings': false,
    'regex-selectors': false,
    'shorthand': false,
    'star-property-hack': false,
    'text-indent': false,
    'underscore-property-hack': false,
    'unique-headings': false,
    'universal-selector': false,
    'unqualified-attributes': false,
    'vendor-prefix': false,
    'zero-units': true
  }))
  .pipe(cssv.formatter('compact'))
});

// default
gulp.task('default', gulp.parallel('watch', 'browser-sync'));

gulpfile.js に記述した内容の詳しい解説

まず冒頭の部分は、gulpfile.js のお約束として、使用するパッケージの読み込みを行います。それぞれの用途は記述の通り。

var gulp = require('gulp'),
    // ↑ gulp 本体
    autoprefixer = require('gulp-autoprefixer'),
    // ↑ ベンダプレフィックスの管理用。コレがないともう無理
    browserSync = require('browser-sync'),
    // ↑ ローカルでの動作確認時にサーバの立ち上げからブラウザ間での操作の同期までやってくれる
    cssimport = require('postcss-import'),
    // ↑  PostCSS のプラグイン。CSS ファイルをインポートする機能を提供。変数をまとめたファイルを各 CSS に読み込むのに使ってる
    cssmin = require('gulp-cssmin'),
    // ↑ CSS を圧縮 (Minify)
    cssv = require('gulp-csslint'),
    // ↑ CSS のバリデーション
    cssvariables = require('postcss-css-variables'),
    // ↑  PostCSS のプラグイン。変数を使いたいので。postcss-simple-vars と機能は似てるんですが、CSS カスタムプロパティの仕様に基づいて書ける
    colorfunction = require('postcss-color-function'),
    // ↑  PostCSS のプラグイン。color() 関数が使える
    concat = require('gulp-concat'),
    // ↑ 複数のファイルを結合するやつ
    coffee = require('gulp-coffee'),
    // ↑ CoffeeScript を書くため
    htmlv = require('gulp-htmlhint'),
    // ↑ HTML のバリデーション
    imagemin = require('gulp-imagemin'),
    // ↑ 画像の最適化(TinyPNG は制限があるので基本はこっち)
    mqpacker = require('css-mqpacker'),
    // ↑ 分散しちゃったメディアクエリをまとめてくれる
    nested = require('postcss-nested'),
    // ↑ PostCSS のプラグイン。ネストが使える
    notify = require('gulp-notify'),
    // ↑ デスクトップ通知用
    plumber = require('gulp-plumber'),
    // ↑ 主に watch タスク実行中のエラーで watch タスクを中断されないように。あと gulp-notify と組み合わせてエラー通知とか
    postcss = require('gulp-postcss'),
    // ↑ PostCSS を使うため
    rename = require('gulp-rename'),
    // ↑ ファイル名の変更
    simplevars = require('postcss-simple-vars'),
    // ↑ postcss-css-variables と同等ですが、こっちの方が書くの楽なので使うことが多い
    slim = require('gulp-slim'),
    // ↑ Slim を使うため
    ssi = require('browsersync-ssi'),
    // ↑ browser-sync で SSI を使うため
    tinyping = require('gulp-tinypng-compress'),
    // ↑ TinyPNG を利用した PNG / JPEG ファイルの最適化 (API キーが必要 / 500 画像/月まで無料)
    uglify = require('gulp-terser');
    // ↑ JavaScript を圧縮 (Minify)

gulp-tinypng-compress については、無理に使うことないと思います。基本は gulp-imagemin で事足りる。TinyPNG の API キーを持っているなら導入しておくと便利かも。

タスクごと

長くなってきましたけども、各タスクごとに記述を見ていきましょう。

default タスク

default タスクは、下記のように watchbrowser-sync を指定しています。default タスクというのは、タスクを特に指定せず、gulp とだけコマンドを打った場合に実行されるタスクのことです。

// default
gulp.task('default', gulp.parallel('watch', 'browser-sync'));

開発を始める時に、gulp とコマンドを打てば、上記、2つのタスクが実行されますので、あとは CSS なり HTML なり書いていく感じですね。

例えばまだ表示確認する必要がない状態なのに毎回 browser-sync が立ち上がるのは邪魔くさいという場合は、gulp watch として、watch タスクだけ実行すればよいです。

CSS ファイルを結合したり、圧縮するためのタスク

CSS 関連のタスクを見ていきましょう。CSS プリプロセッサとして使っているのは PostCSS (+各プラグイン) で、それに gulp-concatgulp-cssmin を組み合わせてファイル結合や圧縮 (Minify) して仕上げるみたいな流れです。

// concat
gulp.task('css.concat', () => {
  var plugins = [
  colorfunction,
  cssimport,
  cssvariables,
  mqpacker,
  nested,
  simplevars
  ];
  return gulp.src('docs/tmp/css/*.css')
  .pipe(plumber({
    errorHandler: notify.onError('Error: <%= error.message %>')
  }))
  .pipe(postcss(plugins))
  .pipe(autoprefixer({
    browsers: ['last 2 version'],
    grid: true
  }))
  .pipe(concat('s.css'))
  .pipe(gulp.dest('docs/tmp/concat'))
});

まず、gulp-concat タスクを実行する際に、PostCSS の各機能を使ってベンダプレフィックスを付けたり、変数やネストを処理したりみたいなことをやりつつ、ファイルを統合して、docs/tmp/concat に一時ファイルを書き出すところまでやります。

PostCSS で使用している各プラグインの簡単な説明は後述していますので参考まで。

この一時ファイルは圧縮されていない CSS ファイルなので、「圧縮していない CSS ファイルが欲しい」 みたいなときにはこれが使えます。

ちなみに、autoprefixer の指定に grid: true とありますが、これは gulp-autoprefixer 5.0 から利用できるオプションです。IE11 を対象に CSS Grid Layout を書く場合はとても便利なので、指定しておきましょう。

その他、browsers: ['last 2 version'] の指定で、各ブラウザの最新版から数えて、2バージョンまで (つまり最新版と、1世代前までですね) を対象にベンダプレフィックスを付与するようにしています。

続けて、上記の一時ファイルを、gulp-cssmin で圧縮して、htdocs/css に書き出すところまでが下記のタスク。この際、gulp-rename を使用して、s.min.css という形にファイル名を変更しています。

// cssmin
gulp.task('cssmin', () => {
  return gulp.src('docs/tmp/concat/s.css')
  .pipe(plumber({
    errorHandler: notify.onError('Error: <%= error.message %>')
  }))
  .pipe(cssmin())
  .pipe(rename({suffix: '.min'}))
  .pipe(gulp.dest('htdocs/css'))
  .pipe(browserSync.stream())
});

なお、.pipe(browserSync.stream()) という記述について、詳しくは後述しますが、browser-sync に対して変更部分を反映させるための記述になります。こう書いておくと、CSS を修正した際に、動作確認用に browser-sync で表示しているページをいちいち手動で再読込したりしなくてよいので便利。

で、実際に gulp-watch から指定しているのは下記のタスクになります。

gulp.task('css', gulp.series('css.concat', 'cssmin'));

つまり、

  1. watch タスクで監視しているフォルダ内の CSS に変更を加える
  2. css タスクが実行される
  3. まず gulp-concat が実行されて、docs/tmp/concat 内に未圧縮状態の CSS ファイルが書き出される
  4. 続けて gulp-cssmin で上の CSS ファイルが圧縮され、本番用の CSS ファイルとして書き出される
  5. browser-sync で表示している Web ページに変更した内容が反映される

という感じの動作になって、作業が自動化されますよと。

JavaScript コーディング関連のタスク

仕事柄、JavaScript をゴリゴリ書くようなことはあまりないのですが、書く際は CoffeeScript を使っています。

CoffeeScript で書いたスクリプトを、JavaScript ファイルにコンパイルするためのタスクが下記。gulp-coffee を使用します。

// coffee
gulp.task('coffee', () => {
  return gulp.src('docs/tmp/js/*.coffee')
  .pipe(plumber({
    errorHandler: notify.onError('Error: <%= error.message %>')
  }))
  .pipe(coffee({bare: true}))
  .pipe(gulp.dest('htdocs/js'))
  .pipe(browserSync.stream())
});

このタスクにも、.pipe(browserSync.stream()) という記述を加えて、browser-sync での確認時に楽になるようにしています。

あと、必要に応じて下記のタスク (gulp-terser を使用) で圧縮 (Minify) しますが、オプション的な感じで使っているため、後述する watch タスクには含めていません。任意で実行する感じです。

// terser
gulp.task('jsmin', () => {
  return gulp.src('htdocs/js/*.js')
  .pipe(plumber({
    errorHandler: notify.onError('Error: <%= error.message %>')
  }))
  .pipe(terser())
  .pipe(rename({suffix: '.min'}))
  .pipe(gulp.dest('htdocs/js/min'))
});

タスクを実行した場合、htdocs/js/ 以下にある .js ファイルを圧縮し、htdocs/js/min/ 以下に、.min.js にリネームした上で保存します。

Slim

Slim 関連のタスクです。docs/tmp/slim/ 以下にある .slim ファイルを、htdocs 以下に書き出します。

後述しますが、こちらも watch タスクで監視対象にするので、変更を加える度にこのタスクが実行されて、HTML が生成されることになります。

// slim
gulp.task('slim', () => {
  return gulp.src('docs/tmp/slim/*.slim')
  .pipe(plumber({
    errorHandler: notify.onError('Error: <%= error.message %>')
  }))
  .pipe(slim({
    require: 'slim/include',
    pretty: true,
    options: 'include_dirs=["docs/tmp/slim/inc"]'
  }))
  .pipe(gulp.dest('htdocs'))
  .pipe(browserSync.stream())
});

このタスクにも CSS 関連のタスク同様、.pipe(browserSync.stream()) の記述を入れることで、HTML に変更があったら即座に確認用のブラウザ画面にも反映されるようにしておきます。

watch タスク

特定のフォルダ内のファイル変更を監視し、変更があった場合に指定したタスクを実行することで、自動化を行います。ここで監視するのは、CSS や CoffeeScript を編集しているフォルダと、Slim 関連のファイルが置いてあるフォルダになります。

// watch
gulp.task('watch', () => {
  gulp.watch('docs/tmp/css/*.css', gulp.task('css'));
  gulp.watch('docs/tmp/js/*.coffee', gulp.task('coffee'));
  gulp.watch('docs/tmp/slim/**/*.slim', gulp.task('slim'));
});

上記の指定は、下記のような意味になります。

  • docs/tmp/css/ 以下の .css ファイルに変更があったら、css タスクを実行
  • docs/tmp/js/ 以下の .coffee ファイルに変更があったら、coffee タスクを実行
  • docs/tmp/slim/ 以下の .slim ファイルに変更があったら、slim タスクを実行

browser-sync によるブラウザでの動作確認

gulp-webserver を使うこともあるんですが、ここ数年はこちらがメイン。browser-sync が便利なのは複数のブラウザ間での操作を同期してくれるので、まとめて動作チェックしたいときなんかは楽です。

// browser-sync
gulp.task('browser-sync', () => {
  browserSync({
    server: {
      baseDir: 'htdocs',
      middleware:[
        ssi({
          ext: '.html',
          baseDir: 'htdocs'
        })
      ]
    }
  });
});

SSI を使いたい場合のために middleware 項目に、browsersync-ssi 用の設定を書いておきます。タスクを実行すると、http://localhost:3000htdocs フォルダをルートディレクトリに Web ページの表示が可能になります。

.pipe(browserSync.stream()) オプション

各タスクに、.pipe(browserSync.stream()) の記述が入っていたと思いますが、このオプションを入れておくと、各タスク実行後に変更されたファイルを browser-sync に伝えてくれます。すると、browser-sync さんが、変更されたファイルだけを再読込してくれるという便利機能。

ブラウザの画面自体を強制的に再読込 (リロード) するのではなく、変更があった部分だけ反映してくれます。画面をリロードされてしまうと面倒なケースは多々あると思いますが、そういう時にも便利です。

画像圧縮 (最適化) 系のタスク

画像を最適化するためのタスクは下記の 2つを使い分け。

画像の最適化は最後にまとめてやればいいや派なので、watch タスクに入れたりみたいなことはせず、納品前に一気に処理してという感じにしています。

htdocs フォルダ内ではなく、docs/tmp/img 以下に作業フォルダを分けているのも、画像の最適化は完全に別作業として分けたいという気分の問題です。

// tinypng
gulp.task('tinypng', () => {
  return gulp.src('docs/tmp/img/src/**/*.{png,jpg}')
    .pipe(tinyping({
      key: '__API_Key__'
      summarize: true
    }))
    .pipe(gulp.dest('docs/tmp/img/dist'))
});

tinypng タスク (上記) は API キーが必要なのと、ひと月あたりに無料で処理できる画像の数が決まっていますので、メインで使うのは下記の imagemin です。

// imagemin
gulp.task('imagemin', () => {
  return gulp.src('docs/tmp/img/src/**/*.{png,jpg,gif,svg}')
  .pipe(imagemin([
    imagemin.gifsicle({interlaced: true}),
    imagemin.jpegtran({progressive: true}),
    imagemin.optipng({optimizationLevel: 4}),
    imagemin.svgo({
      plugins: [
        {removeViewBox: false}
      ]
    })
  ]))
  .pipe(gulp.dest('docs/tmp/img/dist'))
});

オプションとして、いくつかの指定をしてあります。各オプションについて詳しくは、下記の関連 API のページに書かれていますので参考まで。

バリデーション関連のタスク

こちらも画像関連のタスクと同様、ファイル修正の度にというよりは、ひと段落つくごとに実行する感じですが、HTML ファイルや CSS ファイルにエラーがないかを確認します。

// htmlhint
gulp.task('htmlv', () => {
  return gulp.src('htdocs/*.html')
  .pipe(htmlv())
  .pipe(htmlv.reporter())
});

CSS のバリデーションタスク (下記) は、かなり多くのオプションを指定しています。詳しくは csslint の Wiki に書かれていますが、正直、余計なお世話なルールも多いので、必要最低限のルールだけ true にしています。

※ 各オプションは、デフォルト値が true なので true にしたい場合は記述する必要はないのですが、どのルールを true にしているのか、確認しやすいようにあえて書いています。

// csslint
gulp.task('cssv', () => {
  return gulp.src('htdocs/css/s.min.css')
  .pipe(cssv({
    'adjoining-classes': false,
    'box-model': false,
    'box-sizing': false,
    'bulletproof-font-face': false,
    'compatible-vendor-prefixes': false,
    'empty-rules': true,
    'display-property-grouping': true,
    'duplicate-background-images': false,
    'duplicate-properties': true,
    'fallback-colors': false,
    'floats': false,
    'font-faces': false,
    'font-sizes': false,
    'gradients': false,
    'ids': false,
    'import': false,
    'important': false,
    'known-properties': true,
    'order-alphabetical': false,
    'outline-none': true,
    'overqualified-elements': false,
    'qualified-headings': false,
    'regex-selectors': false,
    'shorthand': false,
    'star-property-hack': false,
    'text-indent': false,
    'underscore-property-hack': false,
    'unique-headings': false,
    'universal-selector': false,
    'unqualified-attributes': false,
    'vendor-prefix': false,
    'zero-units': true
  }))
  .pipe(cssv.formatter('compact'))
});

地味に calc()cacl() ってタイポ (恥ずかしい......) しててこいつに指摘されて気がついたことがありますので、ある程度 CSS 書いたらバリデーション通すようにはしています。

(ヒント) gulpfile.js の書き方でひと工夫

タスクを色々と書いていると、同じようなフォルダパスの記述 (例えば docs/tmp/css/ みたいな) を何回も書くことがあると思います。この辺を一括で管理したいなら、下記のように書いてもいいと思います。

// まとめてフォルダのパスを指定しておいて
var docsDir = 'docs/tmp/',
    cssDir = 'docs/tmp/css/';

// タスク内で呼び出す
gulp.src(cssDir + '*.css')

必ずやった方がよいという話ではありませんが、例えばフォルダのパスをプロジェクトごとに変更する必要がある場合などは管理が楽なようにしておくとよいでしょう。

各パッケージでできることを簡単に

導入の仕方は解説してきましたので、今回導入しているパッケージで、実際にどういう感じで CSS や HTML が書けるのかを簡単にですが解説しておこうと思います。

PostCSS 関連

PostCSS 自体は特別な機能を持たない CSS パーサなのですが、PostCSS 用の各種プラグインを用いることで、様々な機能を追加することができます。

それによって、まだ策定途中だったり、サポートブラウザが少なく、そのままでは使いにくい最新の Web 標準仕様に基づいて記述した CSS を、現在のブラウザが解釈可能な CSS として書き出す CSS プリプロセッサとして利用することができるわけです。

と、説明してもわかりにくいので下記に具体的に例を挙げてみます。

postcss-custom-properties

CSS Custom Properties (CSS カスタムプロパティ)」 に基づいて変数を定義することができます。

CSS カスタムプロパティについて詳しくは、「CSS Variables (CSS カスタムプロパティ) の使い方」 という記事を過去に書いていますので参考まで。

といっても、本来の CSS カスタムプロパティは、定義した変数を CSS 内で動的にセットしていくことができますが、postcss-custom-properties は CSS にコンパイルする際に定義した変数が使われるという形なので、書き出し後の CSS に変数の記述は残りません。

ですが、将来的に CSS カスタムプロパティがほぼ全ての環境で使えるようになった場合には、定義した変数の再利用が容易というメリットがあります。

具体的には、下記のように :root セレクタ内で変数を定義しておいて、

:root {
  --bg-color: red;
  --border-radius: 4px;
  --box-padding: 10px 20px;
  --txt-color: white;
}

下記のように定義した変数を var(--foo) という形式で呼び出します。

.btn {
  display: inline-block;
  background-color: var(--bg-color);
  border-radius: var(--border-radius);
  color: var(--txt-color);
  padding: var(--box-padding);
}

CSS カスタムプロパティをサポートするブラウザであれば、この記述でそのまま利用できますが、残念ながら IE11 がサポートしていません。ヤツだけのために Pollyfill 導入するのもダルいので、今のところは postcss-custom-properties で変換して...... みたいな感じで使っています。

postcss-simple-vars

上の postcss-custom-properties と同じ、変数を定義して使えるプラグインなのですが、こちらは変数の定義に 「$」 を使います。書き方としては Sass に近いのでこちらの方が馴染みやすいという方もいるかもしれません。

$bg-color: red;
$border-radius: 4px;
$box-padding: 10px 20px;
$txt-color: white;

変数の定義も呼び出しも $foo という形式なのでシンプルですね。

.btn {
  display: inline-block;
  background-color: $bg-color;
  border-radius: $border-radiu;
  color: $box-padding;
  padding: $txt-color;
}

個人的にはこちらを利用する場合が多いです。

postcss-import

@import ルールを利用することで、CSS ファイル内で、別の CSS ファイルを読み込み、展開することができます。具体的には上で紹介した変数定義を 1つのファイルにまとめておいて、他の CSS ファイルで利用する際に使います。

例えばですが、「css-variables.css」 に変数定義だけを書いておいて、別の CSS ファイル 「style.css」 でその定義した変数を利用したいとします。その場合、「style.css」 の先頭行に、

@import 'css-variables.css';

と記述することで、「css-variables.css」 が 「style.css」 内で展開され、定義した変数を読み出すことができます。

postcss-color-function

color() 関数が利用できます。

例えば下記のように記述することで、

body {
  background-color: color(red a(90%))
}

下記のような CSS が書き出されます。

body {
  background-color: rgba(255, 0, 0, 0.9)
}

postcss-nested

個人的にはあまり使わないんですが、Sass のように入れ子で記述したい場合に便利なプラグイン。

.phone {
  &_title {
    width: 500px;
    @media (max-width: 500px) {
      width: auto;
    }
    body.is_dark & {
      color: white;
    }
  }
  img {
    display: block;
  }
}

みたいに記述することで、下記のような CSS が書き出されます。

.phone_title {
  width: 500px;
}
@media (max-width: 500px) {
  .phone_title {
    width: auto;
  }
}
body.is_dark .phone_title {
  color: white;
}
.phone img {
  display: block;
}

Slim 関連

Slim はテンプレートエンジンです。全部をこれで書いているわけではないのですが、HTML の大枠を作ったりする際に楽なので使っています。

例えば下記のように記述することで、HTML を生成できます。

/ index.slim
doctype html
html lang="ja" dir="ltr"
  head
    include header
  body
    main
      h1 Test Page
      p this page is Slim test page.
      table.sample-table
        tr
          th scope="col" head 01
          th scope="col" head 02
          th scope="col" head 03
        tr
          td data 01
          td data 02
          td data 03

include header という部分でインクルードしているヘッダのパーツ (inc/header.slim) は下記のような内容になります。

/ inc/header.slim
- Sitename = "Example Company"
- Title = "Test Page"
- Description = "Test description"
- CSSdir = "/css/"

meta charset="utf-8"
title #{Title} | #{Sitename}
meta name="viewport" content="width=device-width, initial-scale=1"
meta http-equiv="X-UA-Compatible" content="IE=edge"
meta name="description" content = Description
link rel="stylesheet" href="#{CSSdir}s.min.css"

これら .slim ファイルから、下記のような HTML が生成されます。

<!DOCTYPE html>
<html dir="ltr" lang="ja">
  <head>
    <meta charset="utf-8" />
    <title>Test Page | Example Company</title>
    <meta content="width=device-width, initial-scale=1" name="viewport" />
    <meta content="IE=edge" http-equiv="X-UA-Compatible" />
    <meta content="Test description" name="description" />
    <link href="/css/s.min.css" rel="stylesheet" />
  </head>
  <body>
    <main>
      <h1>
        Test Page
      </h1>
      <p>
        this page is Slim test page.
      </p>
      <table class="sample-table">
        <tr>
          <th scope="col">
            head 01
          </th>
          <th scope="col">
            head 02
          </th>
          <th scope="col">
            head 03
          </th>
        </tr>
        <tr>
          <td>
            data 01
          </td>
          <td>
            data 02
          </td>
          <td>
            data 03
          </td>
        </tr>
      </table>
    </main>
  </body>
</html>

まとめ

ということで、長々と説明してきましたが、上記が現時点で私が主に使っている gulp 環境を基にした解説になります。

ひとくちにフロントエンド開発といっても、Web サービスの開発をしていたり、自社 Web サイトの開発がメインの方なのか、受託でお客様の Web サイト制作をメインにしている方なのかなどによって開発手法、利用するツールやライブラリだけでなく、適した開発環境についてもそれぞれ異なるとは思いますが、もし少しでも参考になる方がいれば幸いです。