gulp 4.0 が正式リリース、v3 からの移行に伴う gulpfile.js ファイルの修正点

gulp 4.0 が正式リリースされましたが、v3 から v4 へのバージョンアップに伴い実施されたいくつかの修正が原因で、v3 環境で使用していた gulpfile.js ファイルがそのままでは正常に動作しなくなりました。v4 移行のために行った gulpfile.js ファイルの修正点をまとめます。

つい先日ですが、gulp 4.0 が正式リリースされ、本記事執筆現在、npm install gulp すると gulp 4.0.0 がインストールされるようになっています(今まではプレリリース扱いということで、npm install gulp@next と指定しなければ 4.0 はインストールされませんでした)。

そこで早速ですが、試しにテスト環境作って gulp 4.0 を入れてみたのですが、v4 になって gulp.taskgulp.watch の引数に変更が入ったことで、v3 を対象に記述していた gulpfile.js ファイルを一部修正する必要がありましたので、そこだけ簡単にまとめておこうと思います。

で、現状では公式サイトの方にまとまった移行ガイドがないので、今回は下記のサイトを参考にさせて頂きました。とても助かったです。

まず、v3 をすでに使用している環境の場合、一旦旧バージョンを削除してから v4 をインストールします。私の場合、gulp はローカルに入れていたので、

npm rm gulp --save-dev

をしてから、

npm i gulp --save-dev

します。

インストールが完了したら、一応 gulp -v して、正しく gulp 4.0.0 がインストールされている事を確認します。

gulp -v で v4 がインストールされていることを確認

私の環境で使っていた gulpfile.js ファイルは、下記のような感じでした。過去に書いた、下記、gulp 導入記事でも紹介したものです。

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'),
    pump = require('pump'),
    rename = require('gulp-rename'),
    simplevars = require('postcss-simple-vars'),
    slim = require('gulp-slim'),
    ssi = require('browsersync-ssi'),
    tinyping = require('gulp-tinypng-compress'),
    uglify = require('gulp-uglify');
 
// default
gulp.task('default', ['watch', 'browser-sync']);
 
// concat
gulp.task('css.concat', () => {
  var plugins = [
  colorfunction,
  cssimport,
  cssvariables,
  mqpacker,
  nested,
  simplevars
  ];
  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', ['css.concat'], () => {
  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', ['css.concat', 'cssmin']);
 
// coffee
gulp.task('coffee', () => {
  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())
});
 
// uglify
gulp.task('jsmin', (cb) => {
  pump(
    [
      gulp.src('htdocs/js/*js'),
      uglify(),
      rename({suffix: '.min'}),
      gulp.dest('htdocs/js/min')
    ],
    cb
  );
});
 
// slim
gulp.task('slim', () => {
  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'], ['css']);
  gulp.watch(['docs/tmp/js/*.coffee'], ['coffee']);
  gulp.watch(['docs/tmp/slim/**/*.slim'], ['slim']);
});
 
// browser-sync
gulp.task('browser-sync', () => {
  browserSync({
    server: {
      baseDir: 'htdocs',
      middleware:[
        ssi({
          ext: '.html',
          baseDir: 'htdocs'
        })
      ]
    }
  });
});
 
// tinypng
gulp.task('tinypng', () => {
  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', () => {
  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', () => {
  gulp.src('htdocs/*.html')
  .pipe(htmlv())
  .pipe(htmlv.reporter())
});
 
// csslint
gulp.task('cssv', () => {
  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'))
});

このうち、とりあえず正常に動作させるために修正した箇所は下記のとおり。

gulp.task の引数変更に関連した修正

// default v3 での記述
gulp.task('default', ['watch', 'browser-sync']);

v4 では下記に修正。タスクを並列処理する gulp.parallel を使用して書き直します。

// default v4 での記述
gulp.task('default', gulp.parallel('watch', 'browser-sync'));

また、この記述の位置を末尾に移動しました。v4 では、タスク名を使用する際に、そのタスクが既に登録されている必要があります。元々の gulpfile.js ファイルだと、watchbrowser-sync の前にこの記述があったため、AssertionError: Task never defined: watch といった形のエラーが出ていました(正確には末尾に移動しなくても、watchbrowser-sync 各タスクより後ろに記述すれば問題ないです)。

さらに、下記の記述については、タスクを直列処理する gulp.series を使用して書き直します。

// css v3 での記述
gulp.task('css', ['css.concat', 'cssmin']);

下記のように変更することで、css.concat タスクの終了後に cssmin タスクを実行するという処理を、標準 API で記述することができます。

// css v4 での記述
gulp.task('css', gulp.series('css.concat', 'cssmin'));

合わせて、元々、cssmin タスクは css.concat タスク完了後に実行するため、下記のように依存関係を定義する記述になっていましたが、

// cssmin v3 での記述
gulp.task('cssmin', ['css.concat'], () => {
  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 タスク側で直列処理をするように指定したため、cssmin タスクからは依存関係を定義する記述を外します。

// cssmin v4 での記述
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())
});

また、v4 ではタスクの完了を明示してあげないと、

The following tasks did not complete: css, css.concat
Did you forget to signal async completion?

という感じで、タスクが完了してないよというエラーが出てしまいますので、return を付けて、stream を返却してあげるようにします。例えば css.concat なら下記のような感じになります。

// concat v4 での記述
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.watch の引数変更に関連した修正

gulp.watch に関しても、引数に変更があったため、修正しないと正常に動作しません。

// watch v3 での記述
gulp.task('watch', () => {
  gulp.watch(['docs/tmp/css/*.css'], ['css']);
  gulp.watch(['docs/tmp/js/*.coffee'], ['coffee']);
  gulp.watch(['docs/tmp/slim/**/*.slim'], ['slim']);
});

下記のように変更します。

// watch v4 での記述
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'));
});

gulp-uglify -> gulp-terser への変更

それから gulp-uglify を gulp-terser に置き換えました。

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'),
    // pump = require('pump'), <- 削除
    rename = require('gulp-rename'),
    simplevars = require('postcss-simple-vars'),
    slim = require('gulp-slim'),
    ssi = require('browsersync-ssi'),
    tinyping = require('gulp-tinypng-compress'),
    // uglify = require('gulp-uglify'); <- 削除
    terser = require('gulp-terser'); // <- 追加

上記のように、gulp-uglify で使用していた pump もあわせて削除し、gulp-terser を追加したら、jsmin タスクを、下記のように gulp-terser で書き直します。

// 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'))
});

セキュリティ上問題のあるパッケージを削除

gulp のバージョンアップとは直接は関係ないのですが、この機に npm audit で脆弱性の警告がでるパッケージを削除、置き換えしました。

  • browsersync-ssi
  • gulp-tinypng-compress

の 2パッケージですが、両方とも古いバージョンの minimatch が使われていて、それで警告が出ていました。gulp-tinypng-compress は元々あまり使っていなかったので削除。browsersync-ssi は connect-ssi で置き換という形で整理し、それに合わせて一部 gulpfile.js 書き換えて修正は完了と。

これで正常に動作するようになりました。下記が修正済み v4 版の gulpfile.js ファイル全体になります。

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'),
    connectSSI = require('connect-ssi'),
    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'),
    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: [
        connectSSI({
         ext: '.html',
         baseDir: 'htdocs'
        })
      ]
    }
  });
});
 
// 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 サンプルは (burnworks/gulpfile-sample-for-web-development/project/gulpfile.js) にも上げていますので参考までにどうぞ。

地味に修正が面倒なので既存環境を v3 から v4 へとアップデートする場合や、今後新規で gulp をインストールして、gulpfile.js ファイルは既存のプロジェクトから流用したいなんていう時は少し注意が必要かもしれません。

関連エントリー