Posted at

MojoliciousでMiddleman的(静的HTML書き出し)なことをする

More than 5 years have passed since last update.

mojoliciousのサーバー機能とテンプレート機能を組み合わせて静的HTMLを生成するためのTipsです。

ある種Middlemanより簡単です。

HTMLを簡単に生成するには「URL」と「テキスト」に構造化し、テキストを「本文」、「本文を補助するテキスト(本文途中にでるコラム、用語集、広告、レコメンド)」、「ナビゲーションなどのリンクを説明するテキスト(ナビ)」の3パターンに分類できるようにします。3パターンの「テキスト+URL」にします。このパターンを管理出来るようにMojoliciousを使っていく準備です。


ブラウザで確認できるようにする

まず静的ファイルを生成するsitemap.yamlを用意します

YAMLを使っているのは誰でも簡単に入力できるからというのとMarkdownとも相性がいいからです。sitemap.yamlは作らなくてもできると思いますが、これくらいのメタデータは必ずいるものなので作成したほうが確実で早いです。

必須はファイル名のみであとはオプションと任意です。


sitemap.yaml

index.html: 

title: headのtitleを記入
description: meta description相当を入力
keywords: meta keywords相当を入力
script:
- top.js #共通js以外を入力
h1: タイトル ← 本文のタイトル
ref: p1 #url_forで使うname urlを省略できるようにする
company/index.html:
title: 会社案内 | サイト名
description: meta description相当を入力
keywords: meta keywords相当を入力
h1: タイトル ← 本文のタイトル
ref: p2 #url_forで使うname
company/ceo.html:
title: 挨拶 | サイト名
description: CEOからのありがたいメッセージ「投資」
keywords: meta keywords相当を入力
h1: 挨拶
path: [p2] #topic pathを生成するのときに利用 「home > 会社案内 > 挨拶」など生成できる
ref: p3 #url_forで使うname

titleと見だしタイトルが別なのは経験上、静的ファイルでは名前付けが自動化できない任意のタイトルも多いからです。

あとはmiddlemanのようにyamlディレクトリなど個別に取得できるデータを一括ロードするようにして

sitemapとyamlデータとサイト用の configなどがあればそれも取得してroutesに登録します。

    my $sitemap = YAML::Syck::LoadFile( $app->home->rel_file('./sitemap.yaml') );

$app->defaults(
korpokkur => { %{$config}, sitemap => $sitemap, },
yaml => $yaml,
handler => $config->{handler} // 'ep'
);

#routing
for my $url ( keys %{$sitemap} ) {
my $template;
if ( $url =~ /\/?(.*)(\.html)/ ) {
$template = $1;
}
else {
$template = $url;
}

my $default = $sitemap->{$url};
$app->routes->get(
$url,
{ %{$default}, template => $template },
$sitemap->{$url}->{ref}
);
}

これでtemplatesフォルダにサイトマップに記載したファイル名.ep(使用するテンプレートごとに拡張子を変える、xslateなら.tx)を作成してmojoliciousのテストサーバーを立ち上げればそのままみることが出来るようになります。titleなどのsitemap.yamlで作成したデータもバインドしてあるのでテンプレート内の変数として使えます。通常の静的ファイルはpublicにいれます。


出力する

このままではブラウザで確認できるようになっただけなので出力をしないといけません。

静的出力するための参考にしたのはTest::Mojoです。Mojo::UserAgentを作成してそこにMojolicious Appを渡すと「/で始まるURL」をMojolicious Appのレスポンスを返してくれるようになります。このUAで各URLを巡回してテキストをとって保存できます。Plackでも同じようなことが出来ると思います。

どこに実装するかなんですが、コマンド処理で静的ファイルを生成するのでMojolicios Commandを拡張すると上手くいきます。

server立ち上げ時などに利用するMojoliciousの実行スクリプトはmojolicious Appを作成後、各種MojoliciousコマンドにAppを渡しているという仕組みなので実行スクリプトにcommand渡してやればApp作って対象commandに投げてくれるんじゃないかという感じです

package Mojolicious::Command::publish;

use Mojo::Base 'Mojolicious::Command';
use strict;
use warnings;
use utf8;
use Mojo::IOLoop;
use Mojo::Server;
use Mojo::UserAgent;
use Path::Tiny;

has ua => sub { Mojo::UserAgent->new->ioloop( Mojo::IOLoop->singleton ) };
sub run{
my $self = shift;
# has uaと↓がtest::mojo的
$self->ua->server->app( $self->app );
my $mode = $ENV{MOJO_MODE};
$self->app->log->level('info')
if ( !defined $mode || $mode ne 'development' );
my $defaults = $self->app->defaults;

my $sitemap = $defaults->{korpokkur}->{sitemap};
my $dir = path($self->app->home->rel_file('./www/'));
$dir->remove_tree;
$dir->mkpath;
for my $url ( keys %{$sitemap} ) {
my $page = $sitemap->{$url};
next if ( $url =~ /^http/ || $page->{no_publish} );
my $tx
= ( $url =~ /^\// )
? $self->ua->get($url)
: $self->ua->get( '/' . $url );
my ( $err, $code ) = $tx->error;
if ( !$err ) {
$dir->child($url)->touchpath->spew_utf8( $tx->res->text );
}
else {
say '[Fail]'. "$err: $url";
}
}
}

app側で設定したものはそのまま取得できるのでそれを利用して巡回してます。適当にPath::Tinyでディレクトリオブジェクトを作成して取得ページにエラーがでていなければ同じパスでファイルを生成するということをしています。

script/mojo_app publish

これで作成したMojolicious Appにpublishで実行で出来るようになります。

URLごとにアクセスできるget Commandが最初から用意されてるのでそのgetを繰り返してもいいと思います。


相対パスで出力したいんだけど?

Mojoliciousで使うMojo::URLではto_relから相対パスを作成出来ます。

ただページのURLと比較対象のURLをabsoluteにしないといけないのでhelperを作っておきます。

    $app->helper(

url_rel => sub {
my $c = shift;
$c->url_for( shift, @_ )
->to_abs->to_rel( $c->req->url->clone->to_abs );
}
);

これでテンプレ内でurl_rel('p2')などやると相対パスを出力してくれるようになります。


まとめ

こんな感じで静的ファイルの管理と出力ができるようになります。

Mojolicious Commandがわりと便利なんですね。

URLとテキストをなるべく分けるて構造化することと、テンプレート内でのテクニックは直接YAMLを使うXslateのテンプレ内でYAMLを使うとか利用すればいいです。

mojo templateならcontent部分にyamlを書いてlayouts側でいろいろ処理できるようにすれば便利に使えます。

advent calendarあいていたので書いてみました。