静的サイト開発でよく使うgulpfileスニペット（初心者向け）

# はじめに
もはやgulpのコードなんて巷にあふれているかと思いますが、
個人的によく使うものをスニペットとしてまとめてみました。

現在私は中小規模の静的サイト、いわゆる普通のコーポレートサイトや動的サイトのモックなどを制作することが多く今回はそういったシンプルな案件で、よく使っているものになります。


# 対象となるかもしれない方
個人的なメモではありますが、まだまだ不慣れな方にも役立つかもと思い投稿しました。
私が独学でタスクランナーの勉強を始めた時に一番困ったのが、やりたいことに対してオーバースペックな情報が多く（最低限理解して）実際に動作させるまでに、かなり時間が掛かってしまったことでした。

これらのスニペットでとりあえず動作はするかとは思いますので、__色々と試すきっかけ__にでもなれば幸いです。

> - gulpのインストールや基本的な使い方は、既に良い記事がたくさんありますのでここでは割愛します。
> - ここではタスクランナー = gulpとします
> - またコード内で`require`しているものはインストールが必要なモジュールになります

# サンプルファイル
このスニペットはディレクトリ構造も含めgithubに上げています
https://github.com/nekoneko-wanwan/gulp


# gulpfileスニペット

## reload-only: ライブリロードの実現
- 静的なサーバを立てる
- 指定ディレクトリ（./root/）のhtml, css, js変更を監視し、ブラウザの自動更新（ライブリロード）を行う
- とりあえずさっと確認・作成したい時によく使う

>[参考イメージ]
>![live-reload.gif](https://qiita-image-store.s3.amazonaws.com/0/58436/cf7eb3be-9257-73bd-96cc-fdfe92578e2b.gif)


```gulpfile.js
/* 静的サーバを立てて、root以下の更新を監視するだけ */
/* gulp-webserverはlivereloadが遅かったので、BrowserSyncを使用 */
var gulp        = require("gulp");
var browserSync = require("browser-sync").create();

/* 監視するファイル群 */
var watch_paths = [
  "./root/**/*.css",
  "./root/**/*.html",
  "./root/**/*.js"
];

/* static server */
gulp.task("server", function() {
  browserSync.init({
    server: {
      baseDir: "./root/",
      directory: false
    },
    open: false,
    port: 8001  // localhost:8001 
  });
});

/* watch */
/* htmlファイルはhtmlとして正しい形式（タグ）となっていないとライブリロードが動作しないので注意 */
gulp.task("watch", function() {
  gulp.watch(watch_paths).on("change", browserSync.reload);
});

/* コマンドラインで実行 -> $ gulp */
gulp.task("default", ["server", "watch"]);
```


## SSI: SSIインクルードを実現する
- コードは上記`reload-only`に以下を追加するだけ

```gulpfile.js
var ssi = require("browsersync-ssi");
//...
    server: {
      baseDir: "./root/",
      directory: false,
      middleware: [
        ssi({
          baseDir: __dirname + "/root",
          ext: ".shtml" // or html
        })
      ]
    },
//...
```

> **注意事項**
> ※2015年8月17日時点
> 使用したモジュール（browsersync-ssi）では、使用できないSSIコマンドも結構あるみたいです（インクルード `<!--#include virtual="" -->`を除く）。オプションで解決できるものかどうかは未確認です。


## sftp: ファイル送信
- ローカルファイルをどこかサーバー上へsftp送信
- 指定したディレクトリ以下、もしくはdocumentRoot以下を一括送信
- アップ先の同名ファイルは上書きされるので注意

> スニペットを分けはしたものの、実際にはタスクの一つとして同時に行うことが多い

```gulpfile.js
var gulp = require("gulp");
var sftp = require("gulp-sftp");

/* gulpをdefault（引数なし）で実行したときの送受信path */
var target = {
  local : "./root/hoge/**",
  host  : "/var/www/html/public_html/hoge/"
};

/* 必要なものだけSFTPで送る */
/* ここではvagrantサーバを指定 */
gulp.task("sftp", function() {
  gulp.src(target.local)
    .pipe(sftp({
        host: "192.168.33.10",
        port: 22,
        user: "vagrant",
        pass: "vagrant",
        remotePath: target.host
    }));
});

/* ./root/以下を全て送る */
/* コマンドラインで実行 -> $ gulp all */
gulp.task("all", function() {
  gulp.src("./root/**")
    .pipe(sftp({
        host: "192.168.33.10",
        port: 22,
        user: "vagrant",
        pass: "vagrant",
        remotePath: "/var/www/html/public_html/"
    }));
});

gulp.task("default", ["sftp"]);
```


## sass: scssの自動コンパイル
- 静的サーバおよびライブリロード
- scssのコンパイル
- compassを使わない場合の土台にするシンプルなもの

```gulpfile.js
var gulp        = require('gulp');
var browserSync = require('browser-sync').create();
var sass        = require('gulp-ruby-sass');
var plumber     = require('gulp-plumber');

var ROOT = './root/';
var SCSS = {
  src : ROOT + '_scss/**/*.scss',
  dist: ROOT + 'css/'
};

/* 更新を監視するファイル群 */
var reloadWatchPaths = [
  ROOT + '**/*.css',
  ROOT + '**/*.js',
  ROOT + '**/*.html'
];


/* static server */
gulp.task('server', function() {
  browserSync.init({
    server: {
      baseDir: ROOT,
      middleware: function (req, res, next) {
        res.setHeader('Access-Control-Allow-Origin', '*');
        next();
      },
      directory: false
    },
    open: false,
    notify: false,
    port: 9999  // localhost:8001 
  });
});


/* sass */
gulp.task('sass', function () {
  return sass(SCSS.src, {
      style: 'expanded',
      stopOnError: false,
      cacheLocation: './sass-cache'
    })
    .pipe(plumber())
    .pipe(gulp.dest(SCSS.dist));
});


/* watch */
/* htmlファイルはhtmlとして正しい形式（タグ）が入っていないと動作しないので注意 */
gulp.task('watch', function() {
  gulp.watch(SCSS.src, ['sass']);
  gulp.watch(reloadWatchPaths).on('change', browserSync.reload);
});


// CI -> $ gulp
gulp.task('default', ['server', 'sass', 'watch']);
```




## compass: scssの自動コンパイル
- 静的サーバおよびライブリロード
- scssのコンパイル（compass）
- prefixや圧縮の設定などはscss/config.rbで管理
- 正直これだけで十分満足してしまう案件も（私の場合）多い

```gulpfile.js
var gulp        = require("gulp");
var browserSync = require("browser-sync").create();
var compass     = require("gulp-compass");
var plumber     = require("gulp-plumber");

var watch_paths = [
  "./root/**/*.css",
  "./root/**/*.html",
  "./root/**/*.js"
];

/* static server */
gulp.task("server", function() {
  browserSync.init({
    server: {
      baseDir: "./root/",
      directory: false
    },
    open: false,
    port: 8001
  });
});

/* compass */
gulp.task("compass", function() {
    gulp.src("root/scss/**/*.scss")
        .pipe(plumber()) //エラーが出てもwatchを止めない
    .pipe(compass({
        config_file: "root/scss/config.rb", //compassの設定ファイルの場所
        css: "root/css/", //出力するcssのフォルダ場所
        sass: "root/scss" //sassファイルの場所
    }));
});

/* watch */
gulp.task("watch", function() {
    gulp.watch("root/scss/**/*.scss", ["compass"]);
});

/* reload */
gulp.task("reload", function() {
  gulp.watch(watch_paths).on("change", browserSync.reload);
});

gulp.task("default", ["server", "compass", "watch", "reload"]);
```


## custom: 色々なタスク
- 静的サーバおよびライブリロード
- scss
- jsの圧縮結合
- htmlテンプレートエンジンの利用（jade）
- ファイルの削除
- 少しパフォーマンスや効率化を意識するときに使用することが多い

> なおここでは以下は除外しています
>・coffeeScriptなどaltJS
>・png画像の圧縮 → 別ツールを使うことが多い
>・CSSスプライト → 別ツールを使うことが多い [高速CSSスプライト出力環境を用意してみる](http://qiita.com/nekoneko-wanwan/items/22b247a1eaec168334cb)
>・BrowserifyやWebpackなどによるモジュール管理

```gulpfile.js
var gulp        = require("gulp");
var browserSync = require("browser-sync").create();
var uglify      = require("gulp-uglify");  // 圧縮
var concat      = require("gulp-concat");  // 結合
var jade        = require("gulp-jade");    // jade(htmlテンプレートエンジン)
var compass     = require("gulp-compass");
var plumber     = require("gulp-plumber");
var rimraf      = require("gulp-rimraf");  // ファイル削除

/* 変数 */
var SRC  = "./_src/";
var DIST = "./deploy/";
var path = {

    //開発用
    dev : {
        scss   : SRC + "scss/**/*.scss",
        coffee : SRC + "coffee/**/*.coffee",
        js : {
            lib : SRC + "js/lib/*.js",  //lib
            origin : [ SRC + "js/*.js", "!" + SRC + "js/lib/*.js"] //自作
        },
        jade : [SRC + "jade/**/*.jade", "!" + SRC + "jade/**/_*.jade"]  //htmlとして書き出す対象(_partialを除外)
    },
    //公開用
    deploy : {
        root : DIST,
        css  : DIST + "common/css/",
        js   : DIST + "common/js/"
    }
};
var watch_paths = [
  DIST + "**/*.html",
  DIST + "common/css/*.css",
  DIST + "common/js/*.js"
];

/* static server */
gulp.task("server", function() {
  browserSync.init({
    server: {
      baseDir: DIST,
      directory: false
    },
    open: false,
    port: 8001  // localhost:8001 
  });
});

/* jade */
/* htmlと同様に、正しい形式（タグ）となっていないと動作しないので注意 */
gulp.task("jade", function() {
    gulp.src(path.dev.jade)
    .pipe(plumber())
    .pipe(jade({
        pretty: true
    }))
    .pipe(gulp.dest(path.deploy.root));
});

/* compass */
gulp.task("compass", function() {
    gulp.src(path.dev.scss)
    .pipe(plumber())  //エラーが出てもwatchを止めない
    .pipe(compass({
        config_file: SRC + "scss/config.rb",  //compassの設定ファイルの場所
        css: path.deploy.css,  //出力するcssのフォルダ場所
        sass: SRC + "scss"  //sassファイルの場所
    }));
});

/* javascript */
gulp.task("js-min", function() {
    //libの圧縮と結合
    gulp.src(path.dev.js.lib)
    .pipe(plumber())
    .pipe(concat("lib.js"))
    .pipe(uglify({
        preserveComments:"some"
    }))
    .pipe(gulp.dest(path.deploy.js));

    //自作jsの圧縮と結合
    gulp.src(path.dev.js.origin)
    .pipe(plumber())
    .pipe(concat("script.js") )
    .pipe(uglify())
    .pipe(gulp.dest(path.deploy.js));
});

/* watch */
gulp.task("watch", function() {
    gulp.watch(path.dev.jade,  ["jade"]);
    gulp.watch(path.dev.scss, ["compass"]);
    gulp.watch([path.dev.js.lib, path.dev.js.origin],   ["js-min"]);
});

/* reload */
gulp.task("reload", function() {
    gulp.watch(watch_paths).on("change", browserSync.reload);
});

/* default */
gulp.task("default", ["server", "jade", "compass", "js-min", "watch", "reload"]);

/* delete */
/* コマンドライン -> $ gulp del */
gulp.task("del", function() {
    return gulp.src(watch_paths, { read: false }) // much faster
           .pipe(rimraf({ force: true }));
});
```

## sequence: タスクの処理順を守る
- 並列処理ではなく決められた順番（シーケンス）に処理を行ないたい場合
- 例えばコンパイル処理がすべて終わったらSFTP送信したい場合など

```gulpfile.js
var gulp = require("gulp");
var runSequence = require("run-sequence");

/* 1秒かかる処理 */
gulp.task("hoge", function(callback) {
  setTimeout(function() {
    console.log("1 hoge");
    callback();
  }, 1000);
});

/* すぐに終わる処理 */
gulp.task("fuga", function(callback) {
  console.log("2 fuga");
  callback();
});

/* 0.5秒かかる処理 */
gulp.task("moge", function(callback) {
  setTimeout(function() {
    console.log("3 moge");
    callback();
  }, 500);
});

/* 普通に実行すると fuga -> moge -> hoge となる */
gulp.task("default", ["hoge", "fuga", "moge"]);

/* $gulp seq で hoge -> fuga -> mogeの順で実行される */
gulp.task("seq", function() {
  runSequence("hoge", "fuga", "moge");
  // runSequence(["hoge", "fuga", "moge"]); 配列に入れると並列処理になる
});
```

## once: 同じ処理を何度もしない
- 連続イベントの発生を抑制
- 例えばwatch時に複数ファイルの変更を（ほぼ）同時に感知した時に、何度も同じ処理をさせないようにする

```gulpfile.js
var gulp = require("gulp");
var browserSync = require("browser-sync").create();

/* reloadを返す普通の関数 */
function reload() {
  return browserSync.reload();
}

/* static server */
gulp.task("server", function() {
  browserSync.init({
    server: {
      baseDir: "./root/",
      directory: false
    },
    open: false,
    port: 8001  // localhost:8001 
  });
});

/* watch
 * 特定ディレクトリのファイル変更を監視し、ブラウザをリロードする
 * ただし同時に複数ファイルの変更を感知しても、リロード処理は1回のみ行う
 */
gulp.task("watch", function() {
  var timer;
  gulp.watch("./root/copy/*.html").on("change", function() {

    /* 連続イベントの間引き処理 200ミリ秒以内で再度発生した場合は無視する */
    /* 200ミリ秒は適当なので、必要に応じて調整 */
    clearTimeout(timer);
    timer = setTimeout(function () {
      reload();
    }, 200);
  });
});

/* 特定ディレクトリ以下にファイルをコピー
 * defaultとは別セッションで $gulp copyを行うことでwatch changeを複数発生させるためのテストタスク
 * watch内のclearTimeout, setTimeoutを消すと、生成ファイルの数だけ処理が起こっていることが分かる。ハズ
 */
gulp.task("copy", function() {
  gulp.src("./root/*html")
    .pipe(gulp.dest("./root/copy/"));
});

/* default */
gulp.task("default", ["server", "watch"]);
```


## browserify: 主にJSの依存関係解決
- browserifyの実行
- html, css, jsのライブリロード
- エラーが出てもwatchは止めない
- 圧縮することも可能

```gulpfile.js
var browserify  = require("browserify");
var browserSync = require("browser-sync").create();
var buffer      = require("vinyl-buffer");
var glob        = require("glob");
var gulp        = require("gulp");
var source      = require("vinyl-source-stream");
var uglify      = require("gulp-uglify");

var jsFiles = {
  src: "./root/js/src/**/*.js",  // コンパイルするsrc元
  distDir: "./root/js/dist/"  // 出力するディレクトリ先
};

/* 自動リロードの監視対象 */
var watchPaths = [
  "./root/**/*.css",
  "./root/**/*.html",
  jsFiles.distDir + "*.js"
];

/* static server */
gulp.task("server", function() {
  browserSync.init({
    server: {
      baseDir: "./root/",
      directory: false
    },
    open: false,
    port: 9999  // localhost:9999 
  });
});

/* browserify */
gulp.task("jsCompile", function(){
  var srcFiles = glob.sync(jsFiles.src);
  return browserify({
    entries: srcFiles
  })
  // エラーが出ても止めない
  .bundle().on('error', function(err) {
      console.log("ERROR! \n" + err.message);
      this.emit("end");
  })
  .pipe(source("app.js"))  // 出力ファイル名
  .pipe(buffer())  // uglifyする時には必須
  // .pipe(uglify())  // 圧縮（使用しない変数関数は出力しない。多分）
  .pipe(gulp.dest(jsFiles.distDir));
});

/* watch */
/* htmlファイルはhtmlとして正しい形式（タグ）が入っていないと動作しないので注意 */
gulp.task("watch", function(e) {
  gulp.watch(jsFiles.src, ["jsCompile"]);
  gulp.watch(watchPaths).on("change", browserSync.reload);
});

// CI -> $ gulp
gulp.task("default", ["server", "watch", "jsCompile"]);
```

#おわりに
初心者向け→チキンな私が傷つかないようにするための予防線です