gulp4を使ってCache bustingする方法(その2・実際にQueryStringを付ける編)
やること
ということで、CSSとJavaScriptは1枚にまとまったのでHTMLに対してそれぞれをLinkしているタグのsrc/hrefにQueryStringを付けてやる。
<head> <link href="css/main.css" rel="stylesheet"> <script src="js/main.js"></script> </head> <body> <img src="images/sample.gif"> </body>
を
<head> <link href="css/main.css?v=(main.cssのMD5値)" rel="stylesheet"> <script src="js/main.js?v=(main.jsのMD5値)"></script> </head> <body> <img src="images/sample.gif?v=(sample.gifのMD5値)"> </body>
という感じです。
で、そもそもQueryStringを付けてうまくいくのか?
この記事 blog.open.tokyo.jp を読むと、
GETパラメータの付加は推奨されない 前記の対応のうち、GETパラメータにリビジョン番号を追加する方法はあまり推奨されません(参照:High Performance Web Sites)。 この方法は、ブラウザやWebサーバがキャッシュを利用しないため、サイトの負荷につながります。
とあった。んー、と思って調べると stackoverflow.com で同じような質問をしている人が。回答の返信の所だけど、「 stevesouders.com/blog/2008/08/23/ ではQueryString付きだとブラウザやCDNがキャッシュしてくれない」とあるよ…と。
Steve Souders: "To gain the benefit of caching by popular proxies, avoid revving with a querystring and instead rev the filename itself." The full explanation can be found here: stevesouders.com/blog/2008/08/23/… – lao Feb 18 '16 at 13:48 24
に対して「それは古い情報だ。CDNやProxyは対応している」と。CDNが対応しているならブラウザもだろう。Chromeで試すとQueryString付きのCSSは「Provisional headers are shown」と出てるのでキャッシュされている。
That blog post is approaching a decade old now. Do you think that cache providers and CDNs have yet to accommodate it? Squid seems to be able to cache documents with query strings now. – jeteon Mar 9 '16 at 22:44
で、先ほどのブログも、その根拠になっているhttps://www.amazon.co.jp/High-Performance-Web-Sites-Essential/dp/0596529309/は2007年の発行だった。
で、QueryString付きにしてくれるのがgulp-rev-append
だったのだけど、glup-rev-appendはgulp-utilに依存してるのでうーん…とりあえず、gulp-utilのFileライブラリを使ってないのでこれで試したところ、なんかイマイチだった。
というのは、変更したいimg/scriptなどには、?rev=@@hashをつけておく必要があって、「うーん、それを逐一つけるのはなぁ」という感じ。
出力側SCSSを更にCache busterしたところ
上手く動かなかった。 パッと思ったのは、無限ループになってるな…と。出力したCSSをwatchしてCacsh busting処理をする…となるとまたそれがwatch対象になる。2つできてしまうのはイマイチだなー。
ということで、SCSSからのコンパイル時に引っかけるようにした。
これをあまりしたくなかった理由は、gulpのSCSSではなくデザイナーが別途SCSSコンパイラ使ってる時*1に困るなーと思ったから。コンパイル済みのCSSに対してCache Bustingしたかったため。
しかし、どう考えてもうまくいかなかったので、gulpでSCSSコンパイルすることにして、こんな感じのgulpファイルになった。
HTMLのcache buster処理はgulp-rev-appendを参考にした。
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'); var eventstream = require('event-stream'); var postcss = require('gulp-postcss'); var cachebuster = require('postcss-cachebuster'); const crypto = require('crypto'); const fs = require('fs'); const path = require('path'); const cheerio = require('cheerio'); var options = minimist(process.argv.slice(2), { string: 'env', default: { env: 'production' } }); const config = { production: options.env !== 'dev', sourceMaps: options.env === 'dev' }; function htmlrev(){ return eventstream.map(function(file, cb) { var $ = cheerio.load(file.contents.toString(), { decodeEntities: false }); $('link[rel="stylesheet"],script[src],img').each(function(i, ele) { let attrname = ""; let hash; switch (ele.name.toLowerCase()){ case 'link': attrname = 'href'; break; case 'img': case 'script': attrname = 'src'; break; } if (attrname) { let src = $(ele).attr(attrname); if (!src.match(new RegExp('^https?:\/\/'))) { var normPath = path.normalize(src); if (normPath.indexOf(path.sep) === 0) { dependencyPath = path.join(file.base, normPath); } else { dependencyPath = path.resolve(path.dirname(file.path), normPath); } let hasQS = dependencyPath.lastIndexOf("?"); if (hasQS >= 0){ dependencyPath = dependencyPath.substring(0, hasQS); } // replace src => htdocs dependencyPath = dependencyPath.replace( path.sep+'src'+path.sep, path.sep+'htdocs'+path.sep); try { data = fs.readFileSync(dependencyPath); hash = crypto.createHash('md5'); hash.update(data.toString(), 'utf8'); $(ele).attr(attrname, $(ele).attr(attrname) + '?rev=' + hash.digest('hex')); } catch(e) { // fail silently. console.log(e.message); } } } }); file.contents = new Buffer.from($.root().html()); cb(null, file); }); } gulp.task('javascript', function(cb){ gulp.src(['./src/javascripts/vendor/*.js', './src/javascripts/*.js']) .pipe(plumber()) .pipe(gulpif(config.sourceMaps, sourcemaps.init())) .pipe(concat('dest.js')) .pipe(uglify()) .pipe(gulpif(config.sourceMaps, sourcemaps.write())) .pipe(gulp.dest("htdocs/js/")); cb(); }); gulp.task('sass', function (cb) { gulp.src(['./src/scss/vendor/*.scss', './src/scss/*.scss']) .pipe(plumber()) .pipe(gulpif(config.sourceMaps, sourcemaps.init())) .pipe(concat('style.css')) .pipe(sass({ outputStyle: 'compressed', })) .pipe(postcss( [ cachebuster({ type: 'checksum', cssPath : '/htdocs/css', }), ])) .pipe(gulpif(config.sourceMaps, sourcemaps.write())) .pipe(gulp.dest('./htdocs/css/')); cb(); }); gulp.task('html', function (cb) { gulp.src('./src/**/*.html') .pipe(htmlrev()) .pipe(gulp.dest('./htdocs')); cb(); }); gulp.task('watch', function(cb){ // auto compile gulp.watch( ['./src/**/*.html', './src/javascripts/**/*.js', './src/scss/**/*.scss', ], gulp.series('html', 'javascript', 'sass')); // browser live reload 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'));
しかし、これ重くならないかな?
gulp4を使ってCache bustingする方法(その1・環境構築編)
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'));
何か色々ととりあえずだけど。
aitendoのLEDのスペックの読み方
aitendoでLED買うときに、こんな感じで色とかのスペックが分からなくて非常に困る…
ということで、自分なりに解読メモ。
頭の形 | |
---|---|
FL | 平型 |
SH | 弾頭型 |
頭直径 | |
---|---|
5 | 5mm |
3 | 3mm |
Nがある場合 | 頭のちょっと引っかかるところ(ハチマキ)が無い |
発光色 | |
---|---|
R | 赤 |
B | 青 |
Y | 黄色 |
W | 白 |
WW | 暖色の白 |
G | 緑 |
O | オレンジ |
PK | ピンク |
PU | 紫 |
JG | ジェードグリーン(ちょっと白みがかった緑) |
レンズ色 | |
---|---|
WC | 透明 |
D | 曇りガラス |
BWC | 青色透明 |
短足 | |
---|---|
SP | アノード17mm |
(表記無し) | アノード27mm |
参考
平頭型LED(3mm/20個入) - aitendo
平頭型LED(3mm/20個入) - aitendo
平頭型LED(5mm/20個入) - aitendo
砲弾型LED(φ5mm/20個入) - aitendo
砲弾型短足LED(φ3mm/20個入) - aitendo
短足LED(20個入) - aitendo
砲弾型短足LED(φ3mm/20個入) - aitendo
短足LED(20個入) - aitendo
Windowsのコマンドラインからアプリケーションのバージョンを確認する方法
レジストリにアクセスするのはPowershellに任せている。
<?php // IE $ie_cmd = 'reg query "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Internet Explorer" /v "svcVersion"'; $r = shell_exec($ie_cmd); $ie_version = '不明'; if (preg_match('/REG_SZ\s+(.*)/', $r, $m)){ $ie_version = $m[1]; } printf("IE: %s\n", $ie_version); $csv = ""; $output_file = 'apps_output.csv'; $cmd=<<<EOF Get-ItemProperty HKLM:Software\Microsoft\Windows\CurrentVersion\Uninstall\* | Select-Object DisplayName, DisplayVersion | export-csv -NoTypeInformation -Encoding Default Get-ItemProperty HKLM:Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\* | Select-Object DisplayName, DisplayVersion | export-csv -NoTypeInformation -Encoding Default Get-AppxPackage | Sort-Object Name | Select PackageFullName, Version | export-csv -NoTypeInformation -Encoding Default Get-WmiObject -Class Win32_Product -ComputerName . | Select-Object Name, Version | Export-Csv -Encoding Default EOF; foreach (explode("\n", trim($cmd)) as $c){ $c = trim($c); shell_exec(sprintf('powershell.exe "%s %s"', $c, $output_file)); $csv .= "\r\n---------------------\r\n".file_get_contents($output_file); } file_put_contents($output_file, $csv); $data = array(); if (($handle = fopen($output_file, "r"))) { while (($d = fgetcsv($handle, 1000, ","))) { $data[] = $d; } } // この$dataを調べると良い感じで。
Adobe Flash Professional CS3を久しぶりに使おうとしてインストールしたら意外と面倒だった件
久しぶりというか、多分最後
になるじゃないかなーって思うんだけど、6年くらい前に作ったFlashをHTML5にしようと思って、素材を抜き出すために再インストールした。
インストール→Windows10再起動→Flash起動のアクティベーション…ができない。
電話認証もダメ。お客様のお使いの電話番号は現在…
あれー、CS2のアクティベーションサーバーが死んだのは知ってるけど…と思ったらCS3以降ももうだめだったのな。
helpx.adobe.com
ということで、アンインストールして…
こちらの記事に従って、再度インストール helpx.adobe.com
新しいシリアルを発行して、それが有効になるようなインストーラーをくれるみたい。
はー。アクティベーションサーバーの維持の方が大変とは…