Gulpのタスク作成、すぐに使えるレシピを紹介

基本的なGulpのタスクをコード付きでまとめて紹介します。
技術的なこと、基礎としての記述方法の紹介などはすでに充実した記事がネット上にあるため、ここでは実際のタスク紹介をメインで扱います。

実際に使用・動作している内容を紹介するため、そのままコピペでも多分動くはず。
まだまだ私自身もGulpを使い始めなので、随時改善・追加予定です。

なお、Gulp本体及びプラグインのインストールが済んでいることを前提として進めていきます。

Gulp本体のインストール方法はこちら。

今回作成するタスクでは、以下のプラグインを使用します。

この記事の対象者

この記事は、主にGulpをとりあえず使ってみたいという方に向けて書きます。
手っ取り早くGulpのタスクを作成したい人、まずは動くものを試しに作成したい人に最適。
逆に、これからじっくりGulpと向き合っていきたい勤勉な方には、他の詳細な技術情報とあわせてご覧いただくことを推奨します。

コードだけ参照したい場合は、「gulpfile.jsに記述するタスクのまとめ」の項をご覧ください。

Gulpのタスクについて

タスクの設定はgulp.taskを用いて行いますが、その他の記述を含めた処理ごとのモジュールを指してタスクと呼ぶことが一般的なようです。
ここでは、Gulpで実行する処理をタスクと呼びます。

タスクを記述するgulpfile.js

タスクはデフォルトではgulpfile.jsにコードを記述することで作成します。
ファイルの拡張子からもわかるように、GulpのタスクはJavaScriptで記述されます。この点が、他の言語を使用するタスクランナーと比較して導入しやすく、Gulpの長所の一つでもあります。

実装する内容と準備

まずは、作成するタスクについて簡単に説明をします。
次に、実行する環境を整えます。
その後に実際にタスクを作成していきます。

タスクの簡単な説明

これから作成するタスクでは、以下の処理を実行します。

  • Sassのコンパイル
  • Sassコンパイル時にベンダープレフィックスを追加
  • 画像の圧縮
  • JavaScriptの圧縮
  • EJSのコンパイル
  • 開発用フォルダから公開用フォルダにファイルをコピー
  • 公開用フォルダのデータをサーバーにアップロード

さらに、開発用フォルダ内を監視し、ファイルの更新時にアップロード以外の処理を自動で実行するようにします。

ディレクトリ構造

今回紹介するタスクは、以下のディレクトリ構造を前提としています。
もし違う階層構造で実行する場合は、パスの記述などを適宜変更しましょう。

/example-project
|-- /develop
|   |-- /images
|   |   |-- example.jpg
|   |   |-- example.png
|   |
|   |-- /ejs
|   |   |-- example.ejs
|   |
|   |-- /js
|   |   |-- example.js
|   |
|   |-- /sass
|       |-- example.scss
|    
|-- /node_modules
|-- /release
|-- ftp.json
|-- gulpfile.js
|-- package.json

Gulp本体のインストールをした段階で、「node_modules」と「package.json」の二つは作成されているはずです。
他のフォルダやファイルについては、次節で説明します。

必要な準備

gulpfile.js
gulpfile.jsは、gulpを動作させるプログラムを記述するファイルです。これから記述をしていくので、同名のファイルを作成してください。

ftp.json
ftp.jsonは、FTP接続に必要な情報を記述するファイルです。こちらも同様に、同名のファイルを作成してください。

/develop
developディレクトリは、開発中のデータ(コンパイル前のSassファイルや圧縮前の画像、JSなど)を入れておくフォルダです。こちらも作成します。

もう一つ、releaseディレクトリは、公開用のデータ(コンパイル後のSassファイルや圧縮後の画像、JSなど)が格納されるフォルダです。こちらは、後ほど作成するタスクの実行時に自動的に生成されます。

これで準備は完了です。タスクの作成に進みましょう。

なお、gulpfile.js以外については、自由に名前を変更して構いません。その場合は、後述するタスクで指定するファイル名・ディレクトリ名をあわせて変更してください。

実装するタスク

実装するタスクの内容を確認しましょう。以下の通りです。

  • Sassのコンパイル,コンパイル時にベンダープレフィックスを追加
  • 画像の圧縮
  • JavaScriptの圧縮
  • EJSのコンパイル
  • 開発用フォルダから公開用フォルダにファイルをコピー
  • 公開用フォルダのデータをサーバーにアップロード

順番に作成していきます。

共通設定

まずは、共通して使用する記述を先に紹介します。

var gulp = require('gulp');//gulp本体の読み込み

//共通して使用するパスをまとめておく
var paths = {
  srcDir: 'develop/',//開発用フォルダ
  dstDir: 'release/'//公開用フォルダ
}

1行目では、Gulpの本体を読み込む指定をしています。

3行目以降では、それぞれのタスクで共通して利用するパスの設定を記述します。
記述や修正の簡略化が目的なので、それぞれのタスクで直接パスを指定する場合は3行目以降の記述は必要ありません。

Sassコンパイル+ベンダープレフィックス付与

Gulpの用途として挙げられることの多いSassコンパイルのタスクを作成します。
タスクを実行すると、 /develop/sass 内の.scssファイルがコンパイルされ、 /release/css 内に.cssファイルが出力されます。
Sassコンパイルを可能にする「gulp-sass」プラグインと、CSSにベンダープレフィックスを追加してくれるプラグイン「gulp-autoprefixer」を使用します。

ちなみに、別途CSSがある場合は、 /develop/css 以下にファイルを設置することで、 /release/css 内にまとめて配置することができます。
これは、EJSのコンパイルの際にも同様です。最終的なファイル名が重複しないように気をつけてください。

//Sassコンパイル&ベンダープレフィックス追加
var sass = require('gulp-sass');
var autoprefixer = require('gulp-autoprefixer');

var options = {
  outputStyle: 'compressed',
  sourceMap: true,
  sourceComments: false
};
gulp.task('diff-sass', function() {
  gulp.src(paths.srcDir + 'sass/**/*.scss')//develop/sass以下の.scssファイルを対象にする
    .pipe(sass(options).on('error', sass.logError))
    .pipe(autoprefixer({
      browsers: ['last 3 version', 'ie >= 6', 'Android 4.0']
    }))
    .pipe(gulp.dest(paths.dstDir + 'css/'));
});

optionsでは出力するCSSの圧縮について指定しています。指定可能なオプションについては、プラグインの公式ページを参照してください。

画像圧縮

画像ファイルの圧縮タスクを作成します。
対象の拡張子は、.jpg、.jpeg、.png、.gif、.svgです。ディレクトリ構造を維持したまま、 /release/images 以下に圧縮した画像ファイルを出力します。
ほぼ自動で圧縮を行えるため非常に便利ですが、圧縮率やクオリティにこだわる方は別途専用のソフトやサービスを利用した方が良いかもしれません。

//画像圧縮
var imagemin = require('gulp-imagemin');

gulp.task('diff-minify-img', function() {
  gulp.src(paths.srcDir + 'images/**/*.+(jpg|jpeg|png|gif|svg)')
    .pipe(imagemin({
      optimizationLevel: 7
    }))
    .pipe(gulp.dest(paths.dstDir + 'images/'));
});

JavaScript圧縮

JavaScriptの圧縮を行うタスクを作成します。
ディレクトリ構造を維持したまま、 /release/js 以下に圧縮したJavaScriptファイルを出力します。

//JS圧縮
var uglify = require('gulp-uglify');

gulp.task('diff-minify-js', function() {
  gulp.src(paths.srcDir + 'js/**/*.js')
    .pipe(uglify())
    .pipe(gulp.dest(paths.dstDir + 'js/'));
});

EJSコンパイル

EJSのコンパイルを行うタスクを作成します。
develop/ejs 以下にあるejsファイルをコンパイルし、 /release 直下にhtmlファイルを出力します。
階層構造は、eje以下の構造をrelease以下に作成します。
例)develop/ejs/about/index.ejs → release/about/index.html

また、モジュール等として使用する公開しないejsファイルは、ファイル名の先頭にアンダースコアをつけることで区別します。

// EJS
var ejs = require('gulp-ejs');

gulp.task('diff-ejs', function() {
  gulp.src([
    paths.srcDir + 'ejs/**/*.ejs',
    '!' + paths.srcDir + 'ejs/**/_*.ejs'
  ], {base:'develop/ejs/'})//アンダースコアから始まるファイルは対象から除外,階層構造も含む
    .pipe(ejs({}, {}, {'ext': '.html'}))//出力後の拡張子を指定
    .pipe(gulp.dest(paths.dstDir));
});

その他ファイルのコピー

コンパイルや圧縮等の処理を行わないファイルは、 /develop から /release にそのままコピーします。

//コピーするファイルの種類を指定
var othersDstSrc = [
  paths.srcDir + '**/.htaccess',
  paths.srcDir + '**/*.html',
  paths.srcDir + '**/*.css',
  paths.srcDir + '**/*.php',
  paths.srcDir + '**/*.svg',
  paths.srcDir + '**/*.png',
  paths.srcDir + '**/*.gif',
  paths.srcDir + '**/*.ico',
  paths.srcDir + '**/*.txt',
  paths.srcDir + '**/*.xml',
  paths.srcDir + '**/*.otf',
  paths.srcDir + '**/*.ttf',
  paths.srcDir + '**/*.woff',
  paths.srcDir + '**/*.woff2',
  paths.srcDir + '**/*.eot'
];

//ファイルのコピー
gulp.task('diff-others', function() {
  return gulp
    .src(othersDstSrc, {base: paths.srcDir})
    .pipe(gulp.dest(paths.dstDir));
});

差分ファイルのアップロード

FTP接続によるサーバーへのアップロードを行います。
別途作成したftp.jsonファイルに、以下のようにサーバー情報を記載し、保存しておきます。
接続に必要な情報をgulpfile.jsとは別に作成しておくことで、複数のサイトで同じgulpfile.jsを流用することができます。

{
  "account": {
    "host": "サーバーホスト名",
    "user": "ユーザー名",
    "pass": "パスワード"
  },
  "path": "アップロード先ディレクトリのパス"
}

作成したftp.jsonの情報を用いてサーバーに接続し、ファイルをアップロードします。

//ファイルをアップロード
var ftp = require('vinyl-ftp');
var fs = require('fs'); //npm installは不要

var ftpConfig = JSON.parse(fs.readFileSync('ftp.json', 'utf8'));//設定ファイルを読み込み
var conn = ftp.create(ftpConfig.account);

var uploadSrc = [
  paths.dstDir + '**/*.js',
  paths.dstDir + '**/*.jpg',
  paths.dstDir + '**/*.jpeg',
  paths.dstDir + '**/*.gif',
  paths.dstDir + '**/.htaccess',
  paths.dstDir + '**/*.html',
  paths.dstDir + '**/*.css',
  paths.dstDir + '**/*.php',
  paths.dstDir + '**/*.svg',
  paths.dstDir + '**/*.png',
  paths.dstDir + '**/*.otf',
  paths.dstDir + '**/*.ttf',
  paths.dstDir + '**/*.woff',
  paths.dstDir + '**/*.woff2',
  paths.dstDir + '**/*.eot'
]

gulp.task('upload', function () {
  return gulp
    .src(uploadSrc, {base: paths.dstDir, buffer: false })
    .pipe( conn.dest(ftpConfig.path));
});

ファイルの更新を監視

Gulpの本領ともいうべき、監視タスクの設定を行います。
ここでは、これまでに作成したアップロード以外のタスクを監視タスクに設定し、ファイルに変更が加えられた(保存された)タイミングでタスクを実行するよう指定しています。

//開発フォルダ監視タスクの設定
gulp.task('watch-dev', function(){
  gulp.watch(othersDstSrc, ['diff-others']);
  gulp.watch([paths.srcDir + 'sass/**/*.scss'], ['diff-sass']);
  gulp.watch([paths.srcDir + 'images/**/*.+(jpg|jpeg|png|gif|svg)'], ['diff-minify-img']);
  gulp.watch([paths.srcDir + 'js/**/*.js'], ['diff-minify-js']);
  gulp.watch([paths.srcDir + 'ejs/**/*.ejs'], ['diff-ejs']);
});

アップロードするファイルの種類は、先ほどothersDstSrcとして設定したものをそのまま使用します。

gulpfile.jsに記述するタスクのまとめ

以上で、基本的なタスクの作成は完了です。

このままでも問題なく動作はしますが、エラー発生時に監視が止まってしまうため、それを防ぐプラグイン「gulp-plumber」を導入します。
さらに、「gulp-changed」を用いて差分データを取得することで、更新されたファイルだけをアップロードできるようにします。

上記のすべてのタスクをまとめたものを以下に掲載します。

var gulp = require('gulp');

var plumber = require("gulp-plumber");//エラー時の監視停止を防ぐ
var changed = require('gulp-changed');//差分データを取得する

var paths = {
  srcDir: 'develop/',
  dstDir: 'release/',
  difDir: 'release/'//差分チェックに使用するフォルダ
}

//Sassコンパイル&ベンダープレフィックス追加
var sass = require('gulp-sass');
var autoprefixer = require('gulp-autoprefixer');

var options = {
  outputStyle: 'compressed',
  sourceMap: true,
  sourceComments: false
};
gulp.task('diff-sass', function() {
  gulp.src(paths.srcDir + 'sass/**/*.scss')
    .pipe(plumber())
    .pipe(changed(paths.difDir))
    .pipe(sass(options).on('error', sass.logError))
    .pipe(autoprefixer({
      browsers: ['last 3 version', 'ie >= 6', 'Android 4.0']
    }))
    .pipe(gulp.dest(paths.dstDir + 'css/'));
});

//画像圧縮
var imagemin = require('gulp-imagemin');

gulp.task('diff-minify-img', function() {
  gulp.src(paths.srcDir + 'images/**/*.+(jpg|jpeg|png|gif|svg)')
    .pipe(plumber())
    .pipe(changed(paths.difDir))
    .pipe(imagemin({
      optimizationLevel: 7
    }))
    .pipe(gulp.dest(paths.dstDir + 'images/'));
});

//JS圧縮
var uglify = require('gulp-uglify');

gulp.task('diff-minify-js', function() {
  gulp.src(paths.srcDir + 'js/**/*.js')
    .pipe(plumber())
    .pipe(changed(paths.difDir))
    .pipe(uglify())
    .pipe(gulp.dest(paths.dstDir + 'js/'));
});

// EJS
var ejs = require('gulp-ejs');

gulp.task('diff-ejs', function() {
  gulp.src([
    paths.srcDir + 'ejs/**/*.ejs',
    '!' + paths.srcDir + 'ejs/**/_*.ejs'
  ], {base:'develop/ejs/'})
    .pipe(plumber())
    .pipe(changed(paths.difDir))
    .pipe(ejs({}, {}, {'ext': '.html'}))
    .pipe(gulp.dest(paths.dstDir));
});

//その他のファイルをコピー
var othersDstSrc = [
  paths.srcDir + '**/.htaccess',
  paths.srcDir + '**/*.html',
  paths.srcDir + '**/*.css',
  paths.srcDir + '**/*.php',
  paths.srcDir + '**/*.svg',
  paths.srcDir + '**/*.png',
  paths.srcDir + '**/*.gif',
  paths.srcDir + '**/*.ico',
  paths.srcDir + '**/*.txt',
  paths.srcDir + '**/*.xml',
  paths.srcDir + '**/*.otf',
  paths.srcDir + '**/*.ttf',
  paths.srcDir + '**/*.woff',
  paths.srcDir + '**/*.woff2',
  paths.srcDir + '**/*.eot'
];

gulp.task('diff-others', function() {
  return gulp
    .src(othersDstSrc, {base: paths.srcDir})
    .pipe(plumber())
    .pipe(changed(paths.difDir))
    .pipe(gulp.dest(paths.dstDir));
});

//出力した差分をアップロード
var ftp = require('vinyl-ftp');
var fs = require('fs');

var ftpConfig = JSON.parse(fs.readFileSync('ftp.json', 'utf8'));
var conn = ftp.create(ftpConfig.account);

var uploadSrc = [
  paths.dstDir + '**/*.js',
  paths.dstDir + '**/*.jpg',
  paths.dstDir + '**/*.jpeg',
  paths.dstDir + '**/*.gif',
  paths.dstDir + '**/.htaccess',
  paths.dstDir + '**/*.html',
  paths.dstDir + '**/*.css',
  paths.dstDir + '**/*.php',
  paths.dstDir + '**/*.svg',
  paths.dstDir + '**/*.png',
  paths.dstDir + '**/*.otf',
  paths.dstDir + '**/*.ttf',
  paths.dstDir + '**/*.woff',
  paths.dstDir + '**/*.woff2',
  paths.dstDir + '**/*.eot'
]

gulp.task('upload', function () {
  return gulp
    .src(uploadSrc, {base: paths.dstDir, buffer: false })
    .pipe( conn.newer(ftpConfig.path))
    .pipe( conn.dest(ftpConfig.path));
});

//開発フォルダ監視タスクの設定
gulp.task('watch-dev', function(){
  gulp.watch(othersDstSrc, ['diff-others']);
  gulp.watch([paths.srcDir + 'sass/**/*.scss'], ['diff-sass']);
  gulp.watch([paths.srcDir + 'images/**/*.+(jpg|jpeg|png|gif|svg)'], ['diff-minify-img']);
  gulp.watch([paths.srcDir + 'js/**/*.js'], ['diff-minify-js']);
  gulp.watch([paths.srcDir + 'ejs/**/*.ejs'], ['diff-ejs']);
});

個別のタスクとの変更点として、監視時にエラーで停止するのを防ぎ、変更があった場合のみタスクを実行するよう記述を追加しています。
3行目では、監視タスクに使用する「gulp-plumber」プラグインの読み込み、4行目では、同様に「gulp-changed」プラグインの読み込みを指定しています。