ブラウザのキャッシュ活用とファイル名にMD5フィンガープリント追加方法 – WordPress高速化2(前編)

前回の投稿記事『WordPress高速化1 – HTML、CSS、JSファイルの縮小/圧縮編』に引き続き、WordPress高速化2として「ブラウザのキャッシュを活用」することにトライしました。キャッシュさせること自体は簡単でしたが、その後の諸々の対応が意外と大変だったりしました。

Google PageSpeed Insightsの結果

Webサイトのパフォーマンスを計測するツールとしてGoogleの提供する「PageSpeed Insights」が有名です。このツールを通して得られる問題点をクリアしていくことが、パフォーマンスを重視するGoogleに評価されるひとつの指標ともなっています。

前回の投稿で行った「HTML、CSS、JSファイルの圧縮」の後も、PageSpeed Insightsの結果として警告され続けていた問題点として「ブラウザのキャッシュを利用してください」というものがあります。

Google 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]コマンドを叩くと、app.cssファイルとapp.jsファイルが、それぞれのコンテンツ内容を元にMD5ハッシュ値が算出され、ファイル名に挿入された状態でassetsディレクトリ内に新たにファイルが作られます。

[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>
HTMLファイルの上記の[ファイル名の部分]を置き換える必要がある

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)をつける」方法となります。興味があったらお試しください。

後編では、別のやり方を紹介します。

コメント一覧

  • 必須

コメント