CSS/JavaScript/画像ファイルが更新されたらブラウザキャッシュの対策をしたいのです。
で、それらのMD5値をQueryStringに付ければいいんじゃない?と思ったので、それを自動でやる方法。…について調べてたら、色々とやりたくなった。
そのために、CSSやJavaScript、画像が更新された際にQueryStringを付けて呼び出すようにする。
前提条件
- CSSとJavaScriptはライブラリも含めて1ファイルにしてしまう。
- SCSSやTypeScriptのプリプロセッサは別物が動作しても良いようにする。もちろん、gulpでプリプロセスしてもO.K
- 私が使っている環境はWindows10 / cygwin
gulpを使えるようにする。
Node.jsをインストール
適当なディレクトリを掘って移動
$ mkdir gulp_sample $ cd gulp_sample
package.jsonを作る
$ npm init -y
gulpをpackage.jsonにも記載する形でインストール。
$ npm i -D gulp
gulpを実行
$ npx gulp
npxを通してgulp実行することでこのプロジェクト内でのnodeライブラリのバージョンで実行できる。npxはRailsのbundle execみたいな感じ。
npm 5.2.0の新機能! 「npx」でローカルパッケージを手軽に実行しよう - Qiita
が参考になった。
ファイル構成
こんな感じ
$ tree . ├── gulpfile.js ├── htdocs │ ├── css │ ├── index.html │ └── js ├── package.json ├── package-lock.json └── src ├── index.html ├── javascripts │ ├── main.js │ └── vendor │ └── jquery-3.4.1.js └── scss ├── _base.scss ├── _footer.scss ├── _header.scss ├── _mixin.scss ├── _vars.scss ├── main.scss └── vendor ├── lity.scss ├── media-queries.scss ├── normalize.scss └── slick.scss
gulpfile.jsを作る
var gulp = require('gulp'); gulp.task('html', function (cb) { gulp.src('./src/*.html') .pipe(gulp.dest('./htdocs')); cb(); }); gulp.task('default', gulp.series('html'));
"html"というタスクを作り、そのタスクは、src/*htmlというファイルをhtdocsに出力するというだけのタスク。別にタスク名はhtmlでもhtml-compileでも構わない。
$ npx gulp
でコンパイル?してくれる。
cb()の必要性
これが無いとエラーになる。
qiita.com
watchで自動gulp実行
gulp.task('html', function (cb) { gulp.src('./src/*.html') .pipe(gulp.dest('./htdocs')); cb(); }); gulp.task('watch', function(cb){ gulp.watch(['./src/*.html'], gulp.parallel('html')); cb(); }); gulp.task('default', gulp.series('html', 'watch'));
taskに'watch'というのを加えて、これを実行するようにしている。
実行内容は、'./src/*.html' というファイルを監視して、それに変更があった場合にparallelでタスク"html"を実行する。タスク1個だけ指定なのでparallelの意味は無いけど。
ちなみに、最後の、「gulp.task('default', gulp.series('html', 'watch'));」に'html'を入れているのは最初の1回はファイル監視対象外になってしまうので1回は初期時に入れる必要がある。seriesは直列実行、parallelは並列実行。
ブラウザのlive reloadを利用したい
gulpでサーバーを立てて、ファイルを監視してファイルが変更あったら自動的にリロードするやつ。
gulp-connectをインストールしてgulpfile.jsを記述
$ npm i -D gulp-connect
// connectを使えるようにして var connect = require('gulp-connect'); // タスクを登録 gulp.task('connect', function(done) { connect.server({ root: 'htdocs', livereload: true }, function () { this.server.on('close', done) }); }); // initに加える gulp.task('default', gulp.series('html', 'watch', 'connect'));
デフォルトポートは8080なので、http://localhost:8080/ でアクセスできるようになる。しかし、livereloadが効かない。
livereloadを効かす
なんか、Gulp4とgulp-connectの相性が悪いらしい。
github.com
で、この方のコメントを参考に直してみる。
要は、srcディレクトリを監視して、リロードするというタスクを加える。
var gulp = require('gulp'); var connect = require('gulp-connect'); gulp.task('html', function (cb) { gulp.src('./src/*.html') .pipe(gulp.dest('./htdocs')); cb(); }); gulp.task('watch', function(cb){ gulp.watch(['./src/*.html'], gulp.parallel('html')); cb(); }); gulp.task('connect', function(done) { connect.server({ root: 'htdocs', livereload: true }, function () { this.server.on('close', done) }); gulp.watch(['htdocs/*'], gulp.parallel('live-reload')); }); gulp.task('live-reload', function(done){ gulp.src('htdocs/*').pipe(connect.reload()); done(); }); gulp.task('default', gulp.series('html', 'watch', 'connect', 'live-reload'));
なんか、 gulp.watch(['htdocs/'], gulp.parallel('live-reload'));とgulp.src('htdocs/')とで同じパスを指定しないといけないのが辛いなー。参照元の人は、filepathをonchangeで手に入れてるのが何とかならんかな…
ということで、ちょっと見直してこんな感じ。
var gulp = require('gulp'); var connect = require('gulp-connect'); gulp.task('html', function (cb) { gulp.src('./src/*.html') .pipe(gulp.dest('./htdocs')); cb(); }); gulp.task('watch', function(cb){ gulp.watch(['./src/*.html'], gulp.parallel('html')); gulp.watch('htdocs').on('change', (filepath)=>{ gulp.src(filepath, { read: false }).pipe(connect.reload()); }); cb(); }); gulp.task('connect', function(done) { connect.server({ root: 'htdocs', livereload: true }, function () { this.server.on('close', done) }); }); gulp.task('default', gulp.series('html', 'watch', 'connect'));
いい感じになった。
JavaScriptの圧縮
JavaScriptをMinifyするgulp-uglifyと、複数ファイルをまとめるgulp-concat、JavaScript/SCSSの誤りでwatchが停止しないようgulp-plumber
$ npm i -D gulp-uglify gulp-plumber gulp-concat
gulpfile.jsに追加
var uglify = require("gulp-uglify"); var plumber = require("gulp-plumber"); var concat = require("gulp-concat"); var sourcemaps = require("gulp-sourcemaps"); gulp.task('javascript', function(cb){ gulp.src(['./src/javascripts/vendor/**/*.js', './src/javascripts/*.js']) .pipe(plumber()) .pipe(concat('dist.js')) .pipe(uglify()) .pipe(gulp.dest("htdocs/js/")); cb(); }); // (略) gulp.task('watch', function(cb){ gulp.watch(['./src/**/*.html', './src/js/main.js'], gulp.parallel('html', 'javascript')); gulp.watch('htdocs').on('change', (filepath)=>{ gulp.src(filepath, { read: false }).pipe(connect.reload()); }); cb(); }); // (略) gulp.task('default', gulp.series('html', 'javascript', 'watch', 'connect'));
SCSSのコンパイルと圧縮
よく使われるやつ。
ソースマップを見たいのでgulp-sourcemapsを入れる
$ npm i -D gulp-sourcemaps
更に、引数でsourcemapsを有効/無効にしたいので、minimistも入れる。あと、引数を元にifするのが意外とすんなりいかなかったのでこの記事を参考に、gulp-ifを使うようにした。gulp-modeもあったんだけど、廃止予定のgulp-utilを含んでいるので使わないようにした。 symfonycasts.com
$ npm i -D minimist $ npm i -D gulp-if
gulpfile.jsに追加(下記はJavaScriptの所だけ)
var minimist = require("minimist"); var gulpif = require('gulp-if'); const options = minimist(process.argv.slice(2), { string: 'env', default: { env: 'production' } }); const config = { production: options.env !== 'dev', sourceMaps: options.env === 'dev' }; gulp.task('javascript', function(cb){ gulp.src(['./src/javascripts/**/*.js']) .pipe(plumber()) .pipe(gulpif(config.sourceMaps, sourcemaps.init())) .pipe(concat('dist.js')) .pipe(uglify()) .pipe(gulpif(config.sourceMaps, sourcemaps.write())) .pipe(gulp.dest("htdocs/js/")); cb(); });
これで、以下の様にgulpを起動した時だけSourceMapが付く。
$ npx gulp --env dev
ということで、まとめ
package.js
{ "name": "gulp2", "version": "1.0.0", "description": "", "main": "index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "keywords": [], "author": "", "license": "ISC", "devDependencies": { "gulp": "^4.0.2", "gulp-concat": "^2.6.1", "gulp-connect": "^5.7.0", "gulp-if": "^2.0.2", "gulp-plumber": "^1.2.1", "gulp-sass": "^4.0.2", "gulp-sourcemaps": "^2.6.5", "gulp-uglify": "^3.0.2", "minimist": "^1.2.0" } }
gulpfile.js
var gulp = require('gulp'); var connect = require('gulp-connect'); var uglify = require("gulp-uglify"); var plumber = require("gulp-plumber"); var concat = require("gulp-concat"); var sourcemaps = require("gulp-sourcemaps"); var sass = require('gulp-sass'); var minimist = require("minimist"); var gulpif = require('gulp-if'); const options = minimist(process.argv.slice(2), { string: 'env', default: { env: 'production' } }); const config = { production: options.env !== 'dev', sourceMaps: options.env === 'dev' }; gulp.task('javascript', function(cb){ gulp.src(['./src/javascripts/**/*.js']) .pipe(plumber()) .pipe(gulpif(config.sourceMaps, sourcemaps.init())) .pipe(concat('dist.js')) .pipe(uglify()) .pipe(gulpif(config.sourceMaps, sourcemaps.write())) .pipe(gulp.dest("htdocs/js/")); cb(); }); gulp.task('sass', function (cb) { gulp.src(['./src/scss/**/*.scss']) .pipe(plumber()) .pipe(gulpif(config.sourceMaps, sourcemaps.init())) .pipe(concat('style.css')) .pipe(sass()) .pipe(gulpif(config.sourceMaps, sourcemaps.write())) .pipe(gulp.dest('./htdocs/css/')); cb(); }); gulp.task('html', function (cb) { gulp.src('./src/**/*.html') .pipe(gulp.dest('./htdocs')); cb(); }); gulp.task('watch', function(cb){ gulp.watch( ['./src/**/*.html', './src/javascripts/**/*.js', './src/scss/**/*.scss', ], gulp.parallel('html', 'javascript', 'sass')); gulp.watch('htdocs').on('change', function(filepath){ gulp.src(filepath, { read: false }).pipe(connect.reload()); }); cb(); }); gulp.task('connect', function(done) { connect.server({ root: 'htdocs', livereload: true }, function () { this.server.on('close', done) }); }); gulp.task('default', gulp.series('html', 'javascript', 'sass', 'watch', 'connect'));
何か色々ととりあえずだけど。