つい先日ですが、gulp 4.0 が正式リリースされ、本記事執筆現在、npm install gulp
すると gulp 4.0.0
がインストールされるようになっています(今まではプレリリース扱いということで、npm install gulp@next
と指定しなければ 4.0 はインストールされませんでした)。
そこで早速ですが、試しにテスト環境作って gulp 4.0 を入れてみたのですが、v4 になって gulp.task
と gulp.watch
の引数に変更が入ったことで、v3 を対象に記述していた gulpfile.js ファイルを一部修正する必要がありましたので、そこだけ簡単にまとめておこうと思います。
で、現状では公式サイトの方にまとまった移行ガイドがないので、今回は下記のサイトを参考にさせて頂きました。とても助かったです。
まず、v3 をすでに使用している環境の場合、一旦旧バージョンを削除してから v4 をインストールします。私の場合、gulp はローカルに入れていたので、
npm rm gulp --save-dev
をしてから、
npm i gulp --save-dev
します。
インストールが完了したら、一応 gulp -v
して、正しく gulp 4.0.0 がインストールされている事を確認します。
私の環境で使っていた 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({
grid: 'autoplace'
}))
.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 ファイルだと、watch
、browser-sync
の前にこの記述があったため、AssertionError: Task never defined: watch
といった形のエラーが出ていました(正確には末尾に移動しなくても、watch
、browser-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({
grid: 'autoplace'
}))
.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 書き換えて修正は完了と。
追記:Autoprefixer のバージョンアップに伴う修正
Autoprefixer のバージョンアップに伴い、元々 gulpfile.js に記述していた対象ブラウザ設定は、package.json に記述するようになりました。
// 修正前
.pipe(autoprefixer({
browsers: ['last 2 version'],
grid: true
}))
上記が元々のコード。下記が修正後のコードです。
// 修正後
.pipe(autoprefixer({
grid: 'autoplace'
}))
対象ブラウザ設定は package.json に下記のように記述します。
// package.json
"browserslist": [
"last 2 version"
]
これで正常に動作するようになりました。下記が修正済み 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({
grid: 'autoplace'
}))
.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 ファイルは既存のプロジェクトから流用したいなんていう時は少し注意が必要かもしれません。