Blog

最近使っているgulp.jsを晒してみる

Yuko Hashimoto
Yuko Hashimoto デザイナー

node.jsを使ったタスクランナーgulp
Sassのコンパイルなどに使用していますが、EJSのコンパイルやAMPページ作成の際にもこれ使えるんじゃない? とgulp.jsを書いてみたので公開してみます。

特にAMPについてはなかなか記事がなかったので、同じようなブログが増えてくれると良いなあと願って……。

(ちなみに、Bootstrap+Sassのみの時はYeomanのweb-appジェネレータを使っています)

EJS+Sass

ファイル構成

EJSファイルは細分化していくうちにごちゃつくので、ejsフォルダにまとめておきます。

ejs-project/
 ├ package.json
 ├ gulpfile.js
 ├ src/
   ├ assets/
     ├ fonts/
     ├ icons/
     ├ images/
   ├ ejs/
     ├ index.ejs
   ├ js/ 
     ├ main.js
   ├ scss/
     ├ main.scss

package.json

$ npm initで作ったものにパッケージを入れただけです。

brouserslistはautoprefixerの設定になります。

{
  "name": "project",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "autoprefixer": "^9.7.6",
    "browser-sync": "^2.26.7",
    "css-declaration-sorter": "^5.1.2",
    "css-mqpacker": "^7.0.0",
    "del": "^5.1.0",
    "gulp": "^4.0.2",
    "gulp-changed": "^4.0.2",
    "gulp-clean-css": "^4.3.0",
    "gulp-ejs": "^5.1.0",
    "gulp-htmlmin": "^5.0.1",
    "gulp-imagemin": "^7.1.0",
    "gulp-notify": "^3.2.0",
    "gulp-plumber": "^1.2.1",
    "gulp-postcss": "^8.0.0",
    "gulp-rename": "^2.0.0",
    "gulp-sass": "^4.0.2",
    "gulp-sass-glob": "^1.1.0",
    "gulp-sourcemaps": "^2.6.4",
    "gulp-uglify-es": "^2.0.0",
    "imagemin-mozjpeg": "^8.0.0",
    "imagemin-pngquant": "^8.0.0",
    "gulp-html-beautify": "^1.0.1"
  },
  "browserslist": [
    "last 2 versions",
    "ie >= 11",
    "Android >= 7"
  ]
}

gulp.js

**.min.cssと**.min.jsを用意できるようにはしていますが、あまり使ってないですね……。

htmlminとhtmlbeautifyは同時に使うことはないかも。でもとりあえず載せておきます。

var gulp = require('gulp');
var autoprefixer = require('autoprefixer');
var browserSync = require("browser-sync");
var cssdeclsort = require('css-declaration-sorter'); // プロパティをソートし直す
var changed = require("gulp-changed");
var cleanCSS = require('gulp-clean-css');
var ejs = require("gulp-ejs");
var htmlmin = require("gulp-htmlmin");
var imagemin = require("gulp-imagemin");
var notify = require('gulp-notify'); //エラー発生時にデスクトップ通知する
var plumber = require("gulp-plumber");
var postcss = require('gulp-postcss'); //autoprefixerとセット
var rename = require("gulp-rename");
var sass = require("gulp-sass");
var sassGlob = require('gulp-sass-glob');
var sourcemaps = require('gulp-sourcemaps');
var uglify = require("gulp-uglify-es").default;
var mozjpeg = require('imagemin-mozjpeg');
var mqpacker = require("css-mqpacker");
var pngquant = require('imagemin-pngquant');
var del = require('del');
var htmlbeautify = require("gulp-html-beautify");


// ディレクトリ
var dir = {
  src: 'src/',
  dest: 'dist/'
}

// scssのコンパイル・圧縮
gulp.task('sass', (done) => {
  gulp
    .src([dir.src + '/scss/**/*.scss', '!' + dir.src + '/scss/**/_*.scss'])
    .pipe(plumber({ errorHandler: notify.onError("Error: <%= error.message %>") }))
    .pipe(sassGlob()) //importの読み込みを簡潔にする
    .pipe(sass({
      outputStyle: 'expanded'
    }))
    .pipe(postcss([
      autoprefixer({
        cascade: false
      }),
      cssdeclsort({
        order: 'smacss'
      }),
      mqpacker()
    ]))
    .pipe(sourcemaps.write())
    // .min.cssの書き出し
    // .pipe(gulp.dest(dir.dest + '/css'))
    // .pipe(cleanCSS({ rebase: false }))
    // .pipe(rename({
    //   extname: '.min.css' //minifyしたファイルの名前変更
    // }))
    .pipe(gulp.dest(dir.dest + '/css')) //コンパイル後の出力先
  done()
});

// ejsのコンパイル・圧縮
gulp.task('ejs', (done) => {
  gulp
    .src([dir.src + 'ejs/**/*.ejs', '!' + dir.src + 'ejs/**/_*.ejs'])
    .pipe(plumber({ errorHandler: notify.onError("Error: <%= error.message %>") }))
    .pipe(ejs({}, {}, { "ext": ".html" }))
    .pipe(rename({ extname: ".html" }))
    .pipe(htmlmin({
      collapseWhitespace: true, // 余白を除去する
      removeComments: true // HTMLコメントを除去する
    }))
    .pipe(htmlbeautify({ // HTMLをきれいにする
      indent_size: 2,
      indent_with_tabs: false
    }))
    .pipe(gulp.dest(dir.dest))
  done()
});

// JS圧縮
gulp.task('js', (done) => {
  gulp
    .src([dir.src + 'js/*.js']) // src/js/ 配下の全ファイルを対象
    .pipe(plumber({ errorHandler: notify.onError("Error: <%= error.message %>") }))
    .pipe(uglify({
      compress: true, // 圧縮する
      mangle: true, // 変数の難読化を行う
      output: {
        comments: /^!/ //Licenseコメントの頭にある「*!」を残す
      }
    }))
    // .min.jsの書き出し
    // .pipe(gulp.dest(dir.dest + 'js'))
    // .pipe(rename({
    //   extname: '.min.js'
    // }))
    .pipe(gulp.dest(dir.dest + 'js')) // js 配下に出力する
  done()
});

// fonts
gulp.task('fonts', (done) => {
  gulp.src([dir.src + 'assets/fonts/**/*'])
  .pipe(gulp.dest(dir.dest + 'assets/fonts'))
  done()
})

// icons
gulp.task('icons', (done) => {
  gulp.src([dir.src + 'assets/icons/**/*'])
  .pipe(gulp.dest(dir.dest + 'assets/icons'))
  done()
})

// その他コピー
gulp.task('extras', (done) => {
  gulp.src([dir.src + '*' , '!' + dir.src + '*.html', '!' + dir.src + 'ejs', '!' + dir.src + 'scss', '!' + dir.src + 'js'], {dot: true})
  .pipe(gulp.dest(dir.dest))
  done()
})


//圧縮率の定義
var imageminOption = [
  pngquant({
    quality: [0.7, 0.85],
  }),
  mozjpeg({
    quality: 85
  }),
  imagemin.gifsicle({
    interlaced: false,
    optimizationLevel: 1,
    colors: 256
  }),
  imagemin.mozjpeg(),
  imagemin.optipng(),
  imagemin.svgo()
];


// 画像圧縮
gulp.task('img', function() {
  return gulp
    .src([dir.src + 'assets/images/**/*.{png,jpg,gif,svg}'])
    .pipe(changed(dir.dest + 'assets/images'))
    .pipe(imagemin(imageminOption))
    .pipe(gulp.dest(dir.dest + 'assets/images'))
});


// Browser Sync
gulp.task('browser-sync', function(done) {
  browserSync.init({
    server: { //ローカルサーバ
      baseDir: dir.dest,
      index: "index.html"
    }
  });
  done();
});


//Clean
gulp.task('clean', function(done) {
  del.sync(dir.dest + '/**', '!' + dir.dest);
  done();
});


// 監視
gulp.task('watch', function() {
  const reload = () => {
    browserSync.reload(); //リロード
  };
  gulp.watch(dir.src + 'scss/**/*.scss').on('change', gulp.series('sass', reload));
  gulp.watch(dir.src + 'js/**/*.js').on('change', gulp.series('js', reload));
  gulp.watch(dir.src + 'ejs/**/*.ejs').on('change', gulp.series('ejs', reload));
  gulp.watch(dir.src + 'assets/images/**/*').on('change', gulp.series('img', reload));
  gulp.watch(dir.src + 'assets/fonts/**/*').on('change', gulp.series('fonts', reload));
  gulp.watch(dir.src + 'assets/icons/**/*').on('change', gulp.series('icons', reload));
  gulp.watch(dir.src + '*').on('change', gulp.series('extras', reload));
});


// gulpコマンドで最初に動作
gulp.task('default',
  gulp.series(
    'clean',
    gulp.parallel(
      'ejs',
      'sass',
      'js',
      'img',
      'fonts',
      'icons',
      'extras',
      'watch',
      'browser-sync'
    )
  )
);

AMP+Sass

AMPページのコーディングでSassをコンパイルして容量を測ってからHTMLに差し込んで……の流れが面倒くさかったので、Sassから圧縮したCSSをHTML内のstyleタグに挿入して書き出せるようにしたものです。

ファイル構成

amp-project/
 ├ package.json
 ├ gulpfile.js
 ├ src/
   ├ assets/
     ├ images/
   ├ scss/
     ├ main.scss
   ├ index.html

HTMLの中身

<!-- inject:head:css -->〜<!-- endinject -->の行に圧縮したCSSを挿入したHTMLをdistフォルダに書き出します。

<!doctype html>
<html amp lang="ja">
<head>
  <meta charset="utf-8" />
  <title>タイトル</title>
  <meta name="description" content="ページの説明"/>
  <meta name="keywords" content="キーワード"/>
  <meta name="viewport" content="width=device-width,initial-scale=1.0,minimum-scale=1.0,user-scalable=no" />
  <link rel="canonical" href="ページURL(AMPのみの時はAMPを指定)" />
  <link rel="amphtml" href="AMPのページURL(通常版がある時に指定)" />
 
  <script type="application/ld+json">
  <!-- 省略 -->
  </script> 
  
  <!-- inject:head:css -->
  <!-- この中にcssが入ります -->
  <!-- endinject -->
 
  <script async src="https://cdn.ampproject.org/v0.js"></script>
</head>
<body>
<h1>ページコンテンツ</h1>
</body>
</html>

package.json

JSを使わなかったりrenameしなかったりなので、EJS版から不要なものを消します。

{
  "name": "project",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "autoprefixer": "^9.7.6",
    "browser-sync": "^2.26.7",
    "css-declaration-sorter": "^5.1.2",
    "del": "^5.1.0",
    "gulp": "^4.0.2",
    "gulp-changed": "^4.0.2",
    "gulp-htmlmin": "^5.0.1",
    "gulp-imagemin": "^7.1.0",
    "gulp-inject": "^5.0.4",
    "gulp-notify": "^3.2.0",
    "gulp-plumber": "^1.2.1",
    "gulp-postcss": "^8.0.0",
    "gulp-sass": "^4.0.2",
    "gulp-sass-glob": "^1.1.0",
    "imagemin-mozjpeg": "^8.0.0",
    "imagemin-pngquant": "^8.0.0"
  },
  "browserslist": [
    "last 2 versions",
    "ie >= 11",
    "Android >= 7"
  ]
}

gulp.js

var gulp = require('gulp');
var autoprefixer = require('autoprefixer');
var browserSync = require("browser-sync");
var cssdeclsort = require('css-declaration-sorter'); // プロパティをソートし直す
var changed = require("gulp-changed");
var htmlmin = require("gulp-htmlmin");
var imagemin = require("gulp-imagemin");
var notify = require('gulp-notify'); //エラー発生時にデスクトップ通知する
var plumber = require("gulp-plumber");
var postcss = require('gulp-postcss'); //autoprefixerとセット
var sass = require("gulp-sass");
var sassGlob = require('gulp-sass-glob');
var mozjpeg = require('imagemin-mozjpeg');
var pngquant = require('imagemin-pngquant');
var del = require('del');
var inject = require('gulp-inject');


// ディレクトリ
var dir = {
  src: 'src/',
  dest: 'dist/'
}


// htmlにcss挿入しdest
gulp.task('inject-css', (done) => {
  gulp
    .src([dir.src + '*.html'])
    .pipe(inject(gulp.src([dir.dest + '/css/main.css']), {
      starttag: '<!-- inject:head:{{ext}} -->',
      removeTags: true,
      transform: function (filePath, file) {
        const styleTagStart = '<style amp-custom>';
        const styleTagEnd  = '</style>';
        let styles = file.contents.toString('utf8');
        // パスを相対パスに変更
        styles = styles.replace('url(../', 'url(');
        // sassコンパイル時に生成される行を削除
        styles = styles.replace(/\/\*\#\ssourceMappingURL\=.*\.map\s\*\//i, '');
        styles = styles.replace(/\@charset \"utf-8\"\;/i, '').trim();
        return styleTagStart + styles + styleTagEnd;
      }
    }))
    .pipe(htmlmin({
      collapseWhitespace: false, // 余白を除去する
      removeComments: false, // HTMLコメントを除去する
      minifyCSS: true, // CSS圧縮
    }))
    .pipe(gulp.dest(dir.dest))
  done();
});


// scssのコンパイル・圧縮
gulp.task('sass', (done) => {
  gulp
    .src([dir.src + 'scss/**/*.scss', '!' + dir.src + 'scss/**/_*.scss'])
    .pipe(plumber({ errorHandler: notify.onError("Error: <%= error.message %>") }))
    .pipe(sassGlob()) //importの読み込みを簡潔にする
    .pipe(sass({
      outputStyle: 'expanded'
    }))
    .pipe(postcss([autoprefixer({
      cascade: false
    })]))
    .pipe(postcss([cssdeclsort({
      order: 'smacss'
    })]))
    .pipe(gulp.dest(dir.dest + '/css')) //コンパイル後の出力先
  done()
});


// その他コピー
gulp.task('extras', (done) => {
  gulp.src([dir.src + '*' , '!' + dir.src + '*.html', '!' + dir.src + 'ejs', '!' + dir.src + 'scss', '!' + dir.src + 'js'], {dot: true})
  .pipe(gulp.dest(dir.dest))
  done()
})


//圧縮率の定義
var imageminOption = [
  pngquant({
    quality: [0.7, 0.85],
  }),
  mozjpeg({
    quality: 85
  }),
  imagemin.gifsicle({
    interlaced: false,
    optimizationLevel: 1,
    colors: 256
  }),
  imagemin.mozjpeg(),
  imagemin.optipng(),
  imagemin.svgo()
];


// 画像圧縮
gulp.task('img', function() {
  return gulp
    .src([dir.src + 'assets/images/**/*.{png,jpg,gif,svg}'])
    .pipe(changed(dir.dest + 'assets/images'))
    .pipe(imagemin(imageminOption))
    .pipe(gulp.dest(dir.dest + 'assets/images'))
});


// Browser Sync
gulp.task('browser-sync', function(done) {
  browserSync.init({
    server: { //ローカルサーバ
      baseDir: dir.dest,
      index: "index.html"
    }
  });
  done();
});


//Clean
gulp.task('clean', function(done) {
  del.sync(dir.dest + '/**', '!' + dir.dest);
  done();
});


// 監視
gulp.task('watch', function() {
  const reload = () => {
    browserSync.reload(); //リロード
  };
  gulp.watch(dir.src + 'scss/**/*.scss').on('change', gulp.series('sass', reload));
  gulp.watch(dir.src + 'assets/images/**/*').on('change', gulp.series('img', reload));
  gulp.watch(dir.src + '*').on('change', gulp.series('extras', reload));
  gulp.watch(dir.src + '**/*.html').on('change', gulp.series('inject-css', reload));
  gulp.watch(dir.src + 'scss/**/*.scss').on('change', gulp.series('inject-css', reload));
});


// gulpコマンドで最初に動作
gulp.task('default',
  gulp.series(
    'clean',
    'sass',
    'img',
    'extras',
    'inject-css',
    gulp.parallel(
      'watch',
      'browser-sync',
    ),
  )
);



gulp.task('amp-css',
  gulp.series(
    'inject-css'
  ));

CSSの圧縮・挿入が不安だったので、テスト用のamp-cssタスクを残しています。

$gulp amp-cssで動かせます。

実行方法(共通)

$gulpでコンパイル&Browser Syncを起動します。

実はgulp.jsについてよくわかってない

なんとなく書いて動けばラッキー的になってるので、もっとしっかり内容を理解して行きたいです。