前回の投稿記事『WordPress高速化1 – HTML、CSS、JSファイルの縮小/圧縮編』に引き続き、WordPress高速化2として「ブラウザのキャッシュを活用」することにトライしました。キャッシュさせること自体は簡単でしたが、その後の諸々の対応が意外と大変だったりしました。
Google PageSpeed Insightsの結果
Webサイトのパフォーマンスを計測するツールとしてGoogleの提供する「PageSpeed Insights」が有名です。このツールを通して得られる問題点をクリアしていくことが、パフォーマンスを重視するGoogleに評価されるひとつの指標ともなっています。
前回の投稿で行った「HTML、CSS、JSファイルの圧縮」の後も、PageSpeed Insightsの結果として警告され続けていた問題点として「ブラウザのキャッシュを利用してください」というものがあります。

GoogleのPageSpeed Insightsの概要によると、静的なリソースをブラウザでキャッシュすると、ユーザーがサイトに複数回アクセスした場合に時間を節約できます。
ということです。
.htaccessでmod_expires設定
ブラウザでキャッシュさせるには、Apacheのmod_expiresモジュールを使います。.htaccessファイルに「MIMEタイプ」と「有効期限(アクセスしてからどれくらい保存させるか)」を以下のように記載します。
<IfModule mod_expires.c>
ExpiresActive On
ExpiresByType image/gif "access plus 1 month"
ExpiresByType image/jpg "access plus 1 month"
ExpiresByType image/jpeg "access plus 1 month"
ExpiresByType image/png "access plus 1 month"
ExpiresByType image/x-icon "access plus 1 month"
ExpiresByType text/css "access plus 1 month"
ExpiresByType text/javascript "access plus 1 month"
ExpiresByType application/x-javascript "access plus 1 month"
</IfModule>
書き方のルールなどは以下を参考にしてください。
設定だけならこれで終わりとなりますが、実はここからいろいろやる必要が出てきます…
ファイル更新時の対策(ブラウザのキャッシュをクリアさせるには?)
諸刃の剣??
上記のように「.htaccessファイル」でキャッシュの設定を行うと、設定した期間内はブラウザのキャッシュを読み込むようになり、ページのパフォーマンスも向上します。
ただし問題もあり、キャッシュの期間内であれば常にキャッシュファイルが読み込まれるため、いくらJavaScriptやCSSのファイルを更新したとしても、新しい内容がページに反映されないということが生じてしまいます。
Google先生の見解
この事象について、GoogleのPageSpeed Insightsの「ブラウザのキャッシュを活用する」の項目では以下のように説明しています。
ときどき変更されるリソースの場合、サーバー上でリソースが変更され、サーバーがブラウザに新しいバージョンが提供されたことを通知するまで、ブラウザでリソースをキャッシュしておくことができます。リソースの各バージョンに一意のURLを指定すると、この方法を実現できます。
フィンガープリントを使うようにと続きます。たとえば、「my_stylesheet.css」というリソースがあるとします。このファイル名を「my_stylesheet_fingerprint.css」に変更できます。リソースが変更されると、フィンガープリントも変更されるため、URL も変わります。URL が変更されるとすぐに、ブラウザはリソースの再取得を強制されます。フィンガープリントを使用すると、頻繁に変更されるリソースでも、有効期限をそれより先の方の日付に設定できるようになります。
いわゆる「MD5」の使用を推奨しています。フィンガープリントの一般的な方法として、ファイルのコンテンツのハッシュをコード化した128ビットの16進数が使用されます。
解決方法は「ファイル名にフィンガープリント(MD5)をつける」
以上のように「更新したファイルのファイル名にフィンガープリントをつけて、ブラウザに新しいファイルだと認識させるようにしろ」とGoogle先生が仰っているので、ものは試せということでファイル名にMD5の付与対応をやってみることにしました。
説明に進む前に
これからファイル名にMD5を付与する方法を説明していきますが、Gruntを使うことを前提としています。Gruntのインストールの仕方などは以前書いた以下の記事を参考にしてください。
それから以下のような構成となっている2つのファイルを例として説明していきます。
<MD5付与前> css/ ┗ app.css js/ ┗ app.js <MD5付与後> assets/ ┣ app-98f2b4256f620be0496ff18f157f863a.css ┗ app-28e80765706f3b2f9a96c2219c42dec6.js
MD5ハッシュ値の算出 – grunt-md5
Gruntでコンテンツ内容からMD5取得
「md5 grunt」でググると見事にありました。「grunt-md5」というGruntプラグインです。こいつを使ってみることにしました。
以下のコマンドでインストールします。
$ npm install grunt-md5 --save-dev
Gruntfile.jsには以下のように書き込みます。
module.exports = function (grunt) {
grunt.initConfig({
// md5タスクの定義
md5: {
// cssターゲットを定義
css: {
files: {
// app.cssのファイル名にMD5を付与したファイルをassets内に作成
'assets/': 'css/app.css'
}
},
// jsターゲットを定義
js: {
files: {
// app.jsのファイル名にMD5を付与したファイルをassets内に作成
'assets/': 'js/app.js'
}
}
}
});
// grunt-md5を読み込む
grunt.loadNpmTasks('grunt-md5');
// gruntコマンドでmd5タスクを実行
grunt.registerTask('default', ['md5']);
};
[grunt]コマンドを実行するとそれぞれ、以下のファイルがassetsディレクトリ内に作られます。
- app-98f2b4256f620be0496ff18f157f863a.css
- app-28e80765706f3b2f9a96c2219c42dec6.js
これでファイル名にMD5を付与したファイルを作ることができました。
HTMLファイルの書き換え – grunt-text-replace
ファイルの準備ができたら、次はこれらのファイルを読み込めるように、HTMLファイルの中のファイルを読み込んでいる部分を変更しなければいけません。ただ毎回手動でやるのは面倒くさいです。そこで再びGruntの登場ですが、これが意外と大変でした。
<!-- cssファイルの読み込み部分(header.php)-->
<link rel="stylesheet" href="/wp-content/themes/blog/css/[app-98f2b4256f620be0496ff18f157f863a.css]">
<!-- jsファイルの読み込み部分(footer.php)-->
<script src="/wp-content/themes/blog/js/[app-28e80765706f3b2f9a96c2219c42dec6.js]"></script>
grunt-text-replaceを使う
まず、HTMLファイルの内容を置換させるのは「grunt-text-replace」というGruntプラグインを使えば実行可能です。
以下のコマンでインストールします。
$ npm install grunt-text-replace --save-dev
Gruntfile.jsの書き方
Gruntfile.jsファイルの書き方は以下の通りです。まずプラグインを読み込みます。
grunt.loadNpmTasks('grunt-text-replace');
実行するタスク部分(基本スタイル)
replace: {
another_example: {
src: ['build/*.html'], // 対象となるファイルパス
overwrite: true, // マッチしたソースファイルを上書きする場合は「true」
replacements: [{
from: /[置換前のファイル名]/, // 置換前(正規表現)
to: "[置換させるファイル名]" //置換後
}]
}
}
どうやってMD5を付与したファイル名を取得するのか?
Gruntfile.jsでの設定において、置換前の部分は正規表現で書けます。置換させるファイル名、これはgrunt-md5で自動的にMD5がファイル名に付与されるので、変更された後のファイル名を動的に取得できるようにしておかなければなりません。
なんかGruntでのうまいやり方が見つからなかったので、Node.jsのfsモジュールを使って取得するようにしました。以下のようなfileオブジェクを作って、Gruntfile.jsに追加しました。
// fileオブジェクトを定義
file: {
// matchメソッドを定義
match: function (type) {
// fsモジュールを読み込む
var fs = require("fs");
// cssファイル名を取得するための正規表現
var regexp_css = /^app\-[0-9a-z]{32}\.css$/;
// jsファイル名を取得するための正規表現
var regexp_js = /^app\-[0-9a-z]{32}\.js$/;
// assetsディレクトリ内のファイル名を取得
var list = fs.readdirSync("assets/");
// regexp変数を設定
var regexp = (type === "css") ? regexp_css : regexp_js;
// assetsディレクトリ内のファイルに対してループ処理
for (var i = 0, l = list.length; i < l; i++) {
// 正規表現にマッチした場合
if (list[i].match(regexp)) {
// ファイル名を戻り値として設定し、処理終了
return list[i].match(regexp).input;
break;
}
}
}
}
上記の処理をファイル名を置換させるタスクの部分で実行させるようにします。
replacements: [{
// 置換前のファイル名(正規表現)
from: /\/wp-content\/themes\/blog\/assets\/app\-[0-9a-z]{32}\.css/g,
// 置換させる文字列
to: '/wp-content/themes/blog/assets/<%= file.match("css") %>'
}]
再びGruntfile.jsの書き方
上記を踏まえて、Gruntfile.jsを書くと以下のようになります。
module.exports = function (grunt) {
grunt.initConfig({
// fileオブジェクトを定義
file: {
// matchメソッドを定義
match: function (type) {
// fsモジュールを読み込む
var fs = require("fs");
// cssファイル名を取得するための正規表現
var regexp_css = /^app\-[0-9a-z]{32}\.css$/;
// jsファイル名を取得するための正規表現
var regexp_js = /^app\-[0-9a-z]{32}\.js$/;
// assetsディレクトリ内のファイル名を取得
var list = fs.readdirSync("assets/");
// regexp変数を設定
var regexp = (type === "css") ? regexp_css : regexp_js;
// assetsディレクトリ内のファイルに対してループ処理
for (var i = 0, l = list.length; i < l; i++) {
// 正規表現にマッチした場合
if (list[i].match(regexp)) {
// ファイル名を戻り値として設定し、処理終了
return list[i].match(regexp).input;
break;
}
}
}
},
// replaceタスクを定義
replace: {
// cssターゲットを定義
css: {
src: ['header.php'], // 対象となるファイルパス
overwrite: true, // マッチしたソースファイルを上書きする場合は「true」
replacements: [{
// 置換前のファイル名(正規表現)
from: /\/wp-content\/themes\/blog\/assets\/app\-[0-9a-z]{32}\.css/g,
// 置換させる文字列
to: '/wp-content/themes/blog/assets/<%= file.match("css") %>'
}]
},
// jsターゲットを定義
js: {
src: ['footer.php'], // 対象となるファイルパス
overwrite: true, // マッチしたソースファイルを上書きする場合は「true」
replacements: [{
// 置換前のファイル名(正規表現)
from: /\/wp-content\/themes\/blog\/assets\/app\-[0-9a-z]{32}\.js/g,
// 置換させる文字列
to: '/wp-content/themes/blog/assets/<%= file.match("js") %>'
}]
}
}
});
// grunt-text-replaceを読み込む
grunt.loadNpmTasks('grunt-text-replace');
// gruntコマンドでreplaceタスクを実行
grunt.registerTask('default', ['replace']);
}
先ほどのgrunt-md5のタスクと組み合わせると、1.ファイル名にMD5を付与し、2.HTMLファイルの読み込み部分を書き換えるタスクが完成します。
旧ファイルの削除 - grunt-contrib-clean
ところが、grunt-md5をタスクを繰り返し行っていると、assetsディレクトリの中に同じ形式のファイル名を持ったファイルがどんどん増えていきます。上記のgrunt-text-replaceを使ったタスクで置換させるファイル名を取得する際に、最新のファイルのファイル名が取得できないという問題が発生します。
これを解決する簡単な方法として、grunt-md5のタスクを実行する前に、古いファイルは全て削除するようにしました。
grunt-contrib-cleanを使う
任意のファイルを削除するには、「grunt-contrib-clean」というGruntプラグインを使います。
以下のコマンでインストールします。
$ npm install grunt-contrib-clean --save-dev
Gruntfile.jsの書き方
こちらはだいぶシンプルに書けます。
module.exports = function (grunt) {
grunt.initConfig({
// cleanタスクを定義
clean: {
css: ['assets/*.css'], // assets以下のcssファイルを削除
js: ['assets/*.js'] // assets以下のjsファイルを削除
}
});
// grunt-contrib-cleanを読み込む
grunt.loadNpmTasks('grunt-contrib-clean');
// gruntコマンドでcleanタスクを実行
grunt.registerTask('default', ['clean']);
}
これで古いファイルは全て削除されるようになります。
一連の作業の自動化(完了)
行うべきタスクはすべて出揃ったので、後は対象のファイルが更新されるたびにすべてのタスクが自動で実行されるようにしてみます。
ファイル状態の監視
ファイル状態を監視するには、おなじみ「grunt-contrib-watch」というGruntプラグインを使います。
以下のコマンでインストールします。
$ npm install grunt-contrib-watch --save-dev
Gruntfile.jsの書き方(完成版)
以下は、grunt-contrib-watchの書き方とともに上記のタスクも全て含めたGruntfile.jsの書き方となります。
module.exports = function (grunt) {
grunt.initConfig({
// cleanタスクを定義
clean: {
css: ['assets/*.css'], // assets以下のcssファイルを削除
js: ['assets/*.js'] // assets以下のjsファイルを削除
},
// md5タスクを定義
md5: {
// cssターゲットを定義
css: {
files: {
// app.cssのファイル名にMD5を付与したファイルをassets内に作成
'assets/': 'css/app.css'
}
},
// jsターゲットを定義
js: {
files: {
// app.jsのファイル名にMD5を付与したファイルをassets内に作成
'assets/': 'js/app.js'
}
}
},
// fileオブジェクトを定義
file: {
// matchメソッドを定義
match: function (type) {
// fsモジュールを読み込む
var fs = require("fs");
// cssファイル名を取得するための正規表現
var regexp_css = /^app\-[0-9a-z]{32}\.css$/;
// jsファイル名を取得するための正規表現
var regexp_js = /^app\-[0-9a-z]{32}\.js$/;
// assetsディレクトリ内のファイル名を取得
var list = fs.readdirSync("assets/");
// regexp変数を設定
var regexp = (type === "css") ? regexp_css : regexp_js;
// assetsディレクトリ内のファイルに対してループ処理
for (var i = 0, l = list.length; i < l; i++) {
// 正規表現にマッチした場合
if (list[i].match(regexp)) {
// ファイル名を戻り値として設定し、処理終了
return list[i].match(regexp).input;
break;
}
}
}
},
// replaceタスクを定義
replace: {
// cssターゲットを定義
css: {
src: ['header.php'], // 対象となるファイルパス
overwrite: true, // マッチしたソースファイルを上書きする場合は「true」
replacements: [{
// 置換前のファイル名(正規表現)
from: /\/wp-content\/themes\/blog\/assets\/app\-[0-9a-z]{32}\.css/g,
// 置換させる文字列
to: '/wp-content/themes/blog/assets/<%= file.match("css") %>'
}]
},
// jsターゲットを定義
js: {
src: ['footer.php'], // 対象となるファイルパス
overwrite: true, // マッチしたソースファイルを上書きする場合は「true」
replacements: [{
// 置換前のファイル名(正規表現)
from: /\/wp-content\/themes\/blog\/assets\/app\-[0-9a-z]{32}\.js/g,
// 置換させる文字列
to: '/wp-content/themes/blog/assets/<%= file.match("js") %>'
}]
}
},
// watchタスクを定義
watch: {
// cssターゲットを定義
css: {
// 更新を監視するファイル
files: ['css/app.css'],
// 変更を感知した時に実行するタスク
tasks: ['clean:css', 'md5:css', 'replace:css']
},
// jsターゲットを定義
js: {
// 更新を監視するファイル
files: ['js/app.js'],
// 変更を感知した時に実行するタスク
tasks: ['clean:js', 'md5:js', 'replace:js']
}
}
});
// 使用するgruntプラグインを読み込む
grunt.loadNpmTasks('grunt-contrib-watch');
grunt.loadNpmTasks('grunt-contrib-clean');
grunt.loadNpmTasks('grunt-md5');
grunt.loadNpmTasks('grunt-text-replace');
}
だいぶ長くなって何の話か忘れそうになりますが、以上がGruntを使った「ブラウザのキャッシュをクリアさせるために、ファイル名にフィンガープリント(MD5)をつける」方法となります。興味があったらお試しください。
後編では、別のやり方を紹介します。
コメント