$shibayu36->blog;

クラスター株式会社のソフトウェアエンジニアです。エンジニアリングや読書などについて書いています。

Xslateのpre_process_handlerを使ってCSSのインライン化をする

 この前 XslateのテンプレートをCSSインライン化する - $shibayu36->blog;という記事をかいたのだけど、その時にgfxさんにコメントで

gfx
BKっぽい気もしますが他にいい方法は思いつかないし、最近 pre_process_handler という機能も足したので、まあ許容範囲じゃないすかね。

と言われた。

 pre_process_handlerというものがあったことを知らなかったので、試しに利用してみた。

pre_process_handlerを使ってtemplateに対してCSSインライン化する

 さてやりたかったことを振り返ると

  • メールを一通一通CSSインライン化するのではなく、先にtemplateをインライン化しておいて、それを利用してrenderしたい

ということでした。

 例えば以下のようなtemplateを用意します。

<html>
  <head>
    <style type="text/css">body { margin: 0px; } #a { padding: 0px; }</style>
  </head>
  <body>
    [% IF visible %]
    <p id="a">hoge</p>
    [% END # IF visible %]
    <p id="b">[% text %]</p>
  </body>
</html>

 そして次のようにpre_process_handlerを使ってファイルロード時にCSSインライン化されるようにしてみます。

my $counter = 0;
my $xslate = Text::Xslate->new(
    syntax => 'TTerse',
    pre_process_handler => sub {
        my $text = shift;
        $counter++;
        my $inlined_text = inline_css($text);
        return $inlined_text;
    },
);

print $xslate->render('./template.html', {
    visible => 1,
    text => 'hoge',
});
print $xslate->render('./template.html', {
    visible => 0,
    text => 'fuga',
});

print "inline count: " . $counter;


sub inline_css {
    my ($template) = @_;
    # [% %]のXslateのsyntaxを[mail_template_compiler_placeholder:1]
    # のようなプレースホルダーに置き換える
    my $placeholder_to_syntax = {};
    my $placeholder_count     = 0;
    $template =~ s{(\[%.+?%\])}{
        my $syntax = $1;
        my $placeholder = "[mail_template_compiler_placeholder:$placeholder_count]";
        $placeholder_to_syntax->{$placeholder} = $syntax;
        $placeholder_count++;
        $placeholder;
    }gse;

    # CSSインライン化
    my $inliner = CSS::Inliner->new;
    $inliner->read({ html => $template });
    $template = $inliner->inlinify;

    # placeholderをXslateのsyntaxに戻す
    $template =~ s{(\[mail_template_compiler_placeholder:\d+\])}{
        my $placeholder = $1;
        my $syntax = $placeholder_to_syntax->{$placeholder};
    }gse;

    return $template;
}

 これを実行してみると、以下のようになり、二回renderしているけどインライン化はただ一回しか行われていません。さらにrenderの結果もうまくいっているようです。

<html>
 <head> </head>
 <body style="margin: 0px;">  <p id="a" style="padding: 0px;">hoge</p>  <p id="b">hoge</p> </body>   </html>
<html>
 <head> </head>
 <body style="margin: 0px;">  <p id="b">fuga</p> </body>   </html>
inline count: 1

 このようにすれば、最初に一度だけメールテンプレートのコンパイルが行えるので、キャッシュが有効な限りは、一度だけのCSSインライン化で何通もメールを送ることができます。やりたいことが出来てますね。便利。

キャッシュとの関係

 ちょっとだけsourceを読んでみたら、pre_process_handlerは_load_sourceというメソッドで実行されているようです。
 また_load_sourceはload_fileするときにコンパイルされたものが無ければ呼ばれるようです。

 と言うことはキャッシュが有効でないと「一度だけインライン化する」というのはできなそうです。試しに以下のようにキャッシュオフにすると、

my $xslate = Text::Xslate->new(
    cache  => 0,
    syntax => 'TTerse',
    pre_process_handler => sub {
        my $text = shift;
        $counter++;
        my $inlined_text = inline_css($text);
        return $inlined_text;
    },
);
inline count: 2

となり、やはりrenderごとにインライン化が呼ばれています。

 この点は一応気をつけておきたいですね。

まとめ

前回のCSSインライン化の話をXslateのpre_process_handlerの機能を使って実装してみました。少しハマりどころもありそうだけど、便利に使えそうですね。