あーさーの備忘録

ゆっくり自由に生きてます

FuelPHPでAssetファイルを圧縮・統合する

はじめに

FuelPHPを使ったサイトでPagespeed Insightsで100点を取るシリーズ第1弾として、前回は「FuelPHPでHTMLソースを圧縮する」という記事を書きました。今回第2弾は「FuelPHPでAssetファイルを圧縮・統合する」です。シリーズものにしたはいいものの今回で最後になります。ごめんなさい。

前回と今回の記事を読んだ上で、あとはFuelPHP関係なく下記のことを実行すればPagespeed Insightsで100点出るはずです

  • 画像を圧縮・最適化する
  • ,htaccessにgzip圧縮の記述をする
  • キャッシュの有効期限を10日程度以上に伸ばす

ただし、Google Analyticsを使用していると、どうしても100点がとれず、99点になってしまいます。僕が作っているサイトは基本的にanalyticsを入れているので、残念ながら100点は取れません。Javascriptファイルのキャッシュの有効期限が短いと言われますが、外部サーバーのファイルなのでどうしようもありません。同じGoogleのサービスなのに……。無念。

Cassetの導入

今回は、canton7さんのfuelphp-cassetというパッケージを使って、CSSやJavasciptファイルを圧縮・統合します。Assetクラスに置き換わるもの、という立ち位置ですが、これを使うと各種ファイルを圧縮できるだけでなく、複数のファイルを統合してくれるので、リクエスト数を減らすことができます。前回同様、普通はoilを使うのですが、手動インストールの方法を載せておきます。

  1. GitHub - canton7/fuelphp-cassetにアクセス
  2. Clone or download → Download ZIPでダウンロード&解凍 1, フォルダ名fuelphp-casset-masterをcassetにリネーム
  3. フォルダごとFuelPHPのPKGPATHに入れる
  4. DOCROOT/assetsの中にcacheというフォルダを作る
  5. APPPATH/config/config.phpの262行目以降を以下のように編集する

APPPATH/config/config.php

<?php
return array(
    /* 262行目まで省略 */
    'always_load' => array(
        'package' => array(
            'casset',
        ),
        /* 省略 */
    ),
);

上記のようになるようにコメントアウトを外して編集します。FuelPHP1.8だと262行目だったのですが、他のバージョンだったりもとから弄ってあったりすると分かりません。テキストエディタalways_loadで検索をかけると良いと思います。

これで、Cassetクラスが使えるようになります。

Cassetクラスを使ってみる

<?php
//DOCROOT/assets/css/test1.cssを読み込む
Casset::css('test1.css');
//DOCROOT/assets/css/test2.cssを読み込む
Casset::css('test2.css');
//2つのファイルを圧縮・統合しlinkタグを出力する
echo Casset::render_css();

このように使います。Javascriptの場合はcssの部分をjsにすれば同じように使用できます。Controller側でCasset::css()しておき、Viewのhead内でCasset::render_css()を実行しControllerで指定したファイルを読み込む、という使い方になります。

templateを使うときに困る!

同様に、templateを使う場合は、以下の通りになります。

APPPATH/classes/controller/test.php

<?php
class Controller_Test extends Controller_Template
{
    public function action_index()
    {
        //このページだけで読み込みたいファイル
        Casset::css('test.css');
        $this->template->contents = View::forge('test');
    }
}

APPPATH/views/template.php

<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="utf-8">
    <title>template</title>
    <!-- 常に読み込みたいファイル -->
    <?php Casset::css('normalize.css'); ?>
    <?php Casset::css('template.css'); ?>
    <!-- linkタグを出力する場所 -->
    <?= Casset::render_css(); ?>
</head>
<body>
<?= $contents; ?>
</body>
</html>

APPPATH/views/test.php

<p>てすと</p>

ところが、こうして統合されたCSSファイルの中を見ると、test.cssの方が先に読み込まれてしまいます。CSS設計にもよりますが、後に書いた記述のほうが優先されるため、基本的には各ページで独自に読み込むCSSを後に読み込みたいはずです。

groupをつくる

FuelPHPのAssetクラスもそうですが、各種Assetファイルを読み込むときにgroupを分けることができます。groupを設定することで、テンプレート関係のファイルを先に読み込むことができるようになります。ちなみに、group未指定(今までの記述)の場合はglobalというgroupになります。group名はCasset::css()Casset::js()の第3引数に指定します。Casset::render_css()Casset::render_js()の第1引数にgroupを指定すると、指定したgroupのファイルのコードを出力します。

APPPATH/views/template.php

<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="utf-8">
    <title>template</title>
    <!-- 常に読み込みたいファイル -->
    <?php Casset::css('normalize.css', false, 'template'); ?>
    <?php Casset::css('template.css', false, 'template'); ?>
    <!-- linkタグを出力する場所(template) -->
    <?= Casset::render_css('template'); ?>
    <!-- (各ページで個別に読み込むファイル) -->
    <?= Casset::render_css('global'); ?>
</head>
<body>
<?= $contents; ?>
</body>
</html>

templateファイルを上記のように変更すると、templateに付随するファイルを先に読み込むことができます。

CSSのインライン化

ここまでやればOK……と思いきや。Google Pagespeed Insightsはこんなことを言ってきました。

このページには、ブロッキング CSS リソースが n あります。これが原因で、ページのレンダリングに遅延が発生しています。 以下のリソースの読み込みが終わるまで、このページでスクロールせずに見えるコンテンツを何もレンダリングできませんでした。 レンダリングをブロックするリソースの読み込みを遅延させるか、非同期に読み込むか、これらのリソースの重要部分を HTML 内に直接インライン化してください。

要約すると、ファイルを読み込んでる間レンダリングできないからインラインにするか非同期に読み込んでくれ、とのこと。よほどの量でなければインライン化したほうがよさそうです。

Casset::render_css()の第2引数にtrueを設定すると、統合したCSSファイルをインライン化することができます。jsの場合も同様です。

APPPATH/views/template.php

<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="utf-8">
    <title>template</title>
    <!-- 常に読み込みたいファイル -->
    <?php Casset::css('normalize.css', false, 'template'); ?>
    <?php Casset::css('template.css', false, 'template'); ?>
    <!-- linkタグを出力する場所(template) -->
    <?= Casset::render_css('template', true); ?>
    <!-- (各ページで個別に読み込むファイル) -->
    <?= Casset::render_css('global', true); ?>
</head>
<body>
<?= $contents; ?>
</body>
</html>

ここまですれば問題ないでしょう。Javascriptはインラインにしなくても</body>の直前で出力すれば問題ないかもしれません。

うまくいかない場合

記事が間違っていた故の不具合なら申し訳ないのですが、このパッケージはデフォルト設定のままだと、ファイルパスの書き換えミスが起こる可能性があります。特に、シンボリックリンクを使っている場合は上手くいかないようです。その場合は以下の2つのことを試してください。

url()の中のパスを/から始まる相対パスにする

CSSファイル内で画像などを読み込むときに使うurl()ですが、CSSを統合しインライン化するため、CSSファイルの場所が変わってしまいます。基本はCassetパッケージが自動で書き換えてくれるのですが、上手く行かないこともあります。CSSファイルの場所に依存しないよう、URLは/から始まる相対パスでルートから指定してください。

configを書き換える

URLを書き換える際に。Stephen Clayさんという人が作ったアルゴリズムを使っているそうなのですが、これは、シンボリックリンクを使用している場合は正しく動作しません。そこで、現在テスト中ではあるものの、別のアルゴリズムに変えます。

PKGPATH/casset/config/config.php

<?php
return array(
    /* 186行目まで省略 */
    'css_uri_rewriter' => 'relative',
);

もともとはabosoluteとなっているところをrelativeに変更してください。こうすればおそらくシンボリックリンクを使用していても書き換えによる不具合が起こりません。

FuelPHPでHTMLソースを圧縮する

Pagespeed Insights

Web開発者なら自分のサイトをGooglePagespeed Insightsに突っ込んで点数を確認したことがあると思います。何も考えずにサイトを作るとかなり点数が低く出てびっくりしますよね。中には1点台のサイトもちらほらとありますから、そこまで気にすることはないんでしょうが…。せっかくだし、自分の作るサイトは100点目指したい!

ということで、FuelPHPを使ったサイトでPagespeed Insightsで100点を取るシリーズ第1弾「FuelPHPでHTMLソースを圧縮する」です。

html-minifierの導入

viewのファイルを圧縮してもよかったのですが、面倒だしtemplateもあるし…ということで、ライブラリを入れてみます。

zaininnariさんのhtml-minifierを使用します。普通はcomposerでインストール…というところですが、僕の開発環境の関係上、手動でのインストール方法を載せておきます。

  1. GitHub - zaininnari/html-minifierにアクセス
  2. Clone or download → Download ZIPでダウンロード&解凍
  3. srcの中にあるものをFuelPHPのAPPPATH/vendor/の中に入れる
  4. APPPATH/bootstrap.phpに以下のように追記

APPPATH/bootstrap.php

<?php
// Bootstrap the framework DO NOT edit this
require COREPATH.'bootstrap.php';

\Autoloader::add_classes(array(
    // Add classes you want to override here
    // Example: 'View' => APPPATH.'classes/view.php',
    /* ↓ここに追記↓ */
    'zz\Html\HtmlMinify' => APPPATH.'vendor/zz/Html/HTMLMinify.php',
    'zz\Html\HtmlNames' => APPPATH.'vendor/zz/Html/HTMLNames.php',
    'zz\Html\HtmlToken' => APPPATH.'vendor/zz/Html/HTMLToken.php',
    'zz\Html\HtmlTokenizer' => APPPATH.'vendor/zz/Html/HTMLTokenizer.php',
    'zz\Html\SegmentedString' => APPPATH.'vendor/zz/Html/SegmentedString.php',
));

これで、HtmlMinifyクラスのメソッドがControllerなどで使えるようになります。Packageにしとけばよかったかなぁ……。でも人が作ったものをPackageにして再配布っていうのもアレなのでPackageにしたい人は各自頑張ってください。

HTMLMinifyクラスを使う

圧縮したいページのControllerに追記します。

APPPATH/classes/controller/minifytest.php

<?php
class Controller_Minifytest extends Controller
{
    public function action_index()
    {
        return View::forge('minifytest');
    }

    public function after($response)
    {
        $response = parent::after($response);
        $response->body(zz\Html\HTMLMinify::minify($response->body(), array(
            'doctype' => 'html5',
            'optimizationLeval' => 1,
        )));
    }
}

これで、出力するHTMLが圧縮されます。Controller_Templateを使う場合も同様です。それなりのスペックのサーバーなら、この処理時間を考えても早く軽くなるかなぁと思います。

すべてのページに適応したい

これをすべてのControllerでいちいち書くのは大変なので、CoreのControllerを拡張します。

APPPATH/classes/controller.php

<?php
class Controller_Template extends Fuel\Core\Controller
{
    public $minify = false;

    public function after($response)
    {
        $response = parent::after($response);
        if ($this->minify === true)
        {
            $response->body(zz\Html\HTMLMinify::minify($response->body(), array(
                'doctype' => 'html5',
                'optimizationLevel' => 1,
            )));
        }
        return $response;
    }
}

これだけだと、まだCoreの方のControllerが呼び出されてしまうので、APPPATH/bootstrap.phpのオートローダの設定を編集します。

APPPATH/bootstrap.php

<?php
// Bootstrap the framework DO NOT edit this
require COREPATH.'bootstrap.php';

\Autoloader::add_classes(array(
    // Add classes you want to override here
    // Example: 'View' => APPPATH.'classes/view.php',
    'zz\Html\HtmlMinify' => APPPATH.'vendor/zz/Html/HTMLMinify.php',
    'zz\Html\HtmlNames' => APPPATH.'vendor/zz/Html/HTMLNames.php',
    'zz\Html\HtmlToken' => APPPATH.'vendor/zz/Html/HTMLToken.php',
    'zz\Html\HtmlTokenizer' => APPPATH.'vendor/zz/Html/HTMLTokenizer.php',
    'zz\Html\SegmentedString' => APPPATH.'vendor/zz/Html/SegmentedString.php',
    /* ↓追記↓ */
    'Controller' => APPPATH.'classes/controller.php',
));

これで、Controllerを拡張することができました。

ここまでできたら、Controllerで、

APPPATH/classes/controller/minifytest.php

<?php
class Controller_Minifytest extends Controller
{
    public $minify = true; //trueだと圧縮、falseや未定義だと非圧縮

    public function action_index()
    {
        return View::forge('minifytest');
    }
}

とすると、after()にいろいろ書かずに圧縮できます。public $minify = true;と書くだけ、簡単です。

ちなみに、Controller_Templateを拡張する場合は、APPPATH/classes/controller/template.phpを作成して同様に編集し、オートローダで読み込むようにするとできます。

次回予告

次回は、CSSJavascriptを圧縮・統合していきます。それでは。

FuelPHPの404でちゃんとステータスコード404を返す

FuelPHPで404の時のページを自作する

まずは、FuelPHPで404の際にデフォルトの404ページ(welcome/404)が表示されないようにします。

APPPATH/config/routes.php

<?php
return array(
    '_root_'  => 'index',  // The default route
    '_404_'   => '404',    // The main 404 route
    
    'hello(/:name)?' => array('welcome/hello', 'name' => 'hello'),
);

welcome/404となっていたころをを404にしました。これで404のときには、http://hoge.com/404とアクセスしたときに表示されるページが表示されるようになります。 (URLはもとのまま)

ControllerとViewを作ります。templateを使う場合も同様にできるはずです。

APPPATH/classes/controller/404.php

<?php
class Controller_404 extends Controller
{
    public function action_index()
    {
        return View::forge('404');
    }
}

APPPATH/views/404.php

<h1>404 Not Found.</h1>
<p>お探しのページは見つかりません。残念でした。</p>

これで、404の場合にはviews/404.phpの内容が表示されるはずです。

ステータスコードが普通に200だった

ところが、この方法だとステータスコードが404にならないわけです。実際にブラウザのコンソールでヘッダを見ると分かりますが、Error Handling - General - FuelPHP Documentationを見ると、

Note that Fuel doesn’t set the 404 status, your response must set this. Return Response::forge(Presenter::forge(‘welcome/404’), 404); in order to send the correct status header.

FuelPHPでは404ステータスを設定してないことに注意して、と言ってます。見た目上は404ページなのですが、レスポンスが200なのでブラウザやクローラーにはページが存在しないことは分かりません。

ステータスコードを404にする

それなら自分で404に設定しましょう。

APPPATH/classes/controller/404.php

<?php
class Controller_404 extends Controller
{
    public function action_index()
    {
        return View::forge('404');
    }

    //以下追記
    public function after($response)
    {
        $response = parent::after($response);
        $response->status = 404;
        return $response;
    }
}

これでステータスコードがちゃんと404になります。やったぜ。

ちなみにwelcome/404の方はちゃんと404返すようになってて、action_index()内でResponseオブジェクト作ってそのときにコードを指定しています。

APPPATH/classes/controller/welcome.php

<?php
public function action_404()
{
    return Response::forge(Presenter::forge('welcome/404'), 404);
}

あれ、こっちのほうが100倍ぐらいシンプルでは…。Response::forge()の第2引数にステータスコードぶち込めるらしい。

まぁでもあっちの方が404スーパークラス作って継承できるんで便利ですね。いや404ページそんなにたくさん作らないけど。それにactionの中は普段と同じ書き方できるんでtemplateとか使う場合も混乱せずに済みます。

技術系ブログ書くの初めてだけどこんな感じでいいのかな。