本記事は、Perl Advent Carendar 2019 21日目の記事です。
#TL; DR
どうしてもPerlだけでWebサイトを作りたいですか?
次の3つ(ないし4つ)のモジュールを使えば何とかなります。
頑張ってください!
メリットは無いと思います!!!
はじめに
複数言語を利用したプロジェクトは、様々な観点から忌み嫌われています。
HTMLを書いてJavascriptを書いてPythonを書いてC++を書いて、なんてやっていると、コーディング規約を間違えたりコンパイルエラー/実行時エラーを頻発する原因になりかねません。
Pythonで行末 ;
を付けない生活を続けた後にJavascriptを書くと、ついつい書き忘れてしまう(けど動いてしまう)みたいなことは、多くの方が経験していることでしょう。
1プロジェクト1言語が理想であるとするならば、それを試してみる価値はありそうです。
Perlでそれが可能であるか挑戦してみます。
実際にやってみた
以下、1プロジェクト1言語をかなえるためのモジュールを説明していきます。
前提として、Mouseモジュールを使っていくことをご了承ください。
Qiitaのシンタックスハイライトが壊れてしまうのは難点ですね......
CGI
今でも現役の超有名モジュールです。
Perlと言えばCGI、CGIと言えばPerlの時代を築き上げた立役者です。
CGI - metacpan.org
https://metacpan.org/pod/CGI
CGIモジュールを使えば、殆どのHTMLをPerlで代用できます。
package lib::Html;
use utf8;
use Mouse;
use CGI;
has q => ( is => "rw", isa => "CGI", default => sub { CGI->new } );
sub print {
my $self = shift;
print $self->q->header( -charset => "utf-8" );
print $self->q->start_html(
-title => "Perl only Web App",
-lang => "ja",
-encoding => "utf-8" );
print $self->q->h1("Welcome");
print $self->q->p("This is sample site.");
print $self->q->end_html;
}
1;
CGI.pmでは、 print
文を用いてHTMLファイルを生成します。
このモジュールを利用するためには、次のCGIファイルを作ります。
#!/usr/bin/perl
use strict;
use warnings;
use utf8;
use FindBin;
use lib $FindBin::Bin;
use lib::Html;
my $html = lib::Html->new;
$html->print;
index.cgi
ファイルおよび Html.pm
ファイルのディレクトリ構造は、以下の通りです。
index.cgi
lib/
|-- Html.pm
以降、新たに登場するモジュールを利用しているモジュールは、 lib/
ディレクトリに入れましょう。
では、CGIを実行できるディレクトリに突っ込んで、ブラウザで index.cgi
を開きます。
シンプルながら、Webページの生成ができました。
CSS::Tiny
HTMLができたら、次はCSSです。
CSS::Tinyモジュールを使えば、PerlでCSS設定を記述できます。
CSS::Tiny - Read/Write .css files with as little code as possible - metacpan.org
https://metacpan.org/pod/CSS::Tiny
package lib::Css;
use utf8;
use Mouse;
use CSS::Tiny;
has css => ( is => "rw", isa => "CSS::Tiny", default => sub { CSS::Tiny->new } );
sub create_style {
my $self = shift;
$self->css->{"h1"} = {
"margin-top" => "1em",
"margin-bottom" => "1em",
"margin-left" => "30px",
"margin-right" => "30px"
};
$self->css->{"p"} = {
"margin-top" => "1em",
"margin-bottom" => "1em",
"margin-left" => "30px",
"margin-right" => "30px"
};
return $self->css->write_string;
}
1;
このモジュールを使うためには、CGIモジュールにおける start_html
サブルーチンの -style
オプションに設定します。
下記は、変更済みの Html.pm
ファイルです。
package lib::Html;
use utf8;
use Mouse;
use CGI;
use lib::Css; # 追加
has q => ( is => "rw", isa => "CGI", default => sub { CGI->new } );
has css => ( is => "rw", isa => "lib::Css", default => sub { lib::Css->new } ); # 追加
sub print {
my $self = shift;
print $self->q->header( -charset => "utf-8" );
print $self->q->start_html(
-title => "Perl only Web App",
-style => { -code => $self->css->create_style }, # 追加
-lang => "ja",
-encoding => "utf-8" );
print $self->q->h1("Welcome");
print $self->q->p("This is sample site.");
print $self->q->end_html;
}
1;
少し変化が分かりづらいですが、タイトルと本文の上下左右に余白ができており、スタイルが適用されていることがわかりますね。
これにより、今どきのモダンなサイトもPerlで作成できます!
え、bootstrapくらい使わせろ? CSS::Tiny->read("bootstrap.min.css")
で妥協してくださいよ。
JavaScript::Code
HTML、CSSと来たら、現代でJavaScript無しにWebサイトは語れませんよね。
ということで、PerlでJavaScriptを書く(もう意味が分からない)モジュールが、JavaScript::Codeです。
JavaScript::Code - A JavaScript Code Framework - metacpan.org
https://metacpan.org/pod/JavaScript::Code
package lib::Javascript;
use utf8;
use Mouse;
use JavaScript::Code;
sub create {
my $self = shift;
my $code = JavaScript::Code->new;
my $alert = JavaScript::Code::Function->new( name => "alert" );
$code->add($alert->call(['"Welcome!!!"'])->as_element);
return $code->output_for_html;
}
1;
使い方は、以下の通りです。
今回は body
要素の最後に、生成した script
要素を入れます。
package lib::Html;
use utf8;
use Mouse;
use CGI;
use lib::Css;
use lib::Javascript; # 追加
has q => ( is => "rw", isa => "CGI", default => sub { CGI->new } );
has css => ( is => "rw", isa => "lib::Css", default => sub { lib::Css->new } );
has script => ( is => "rw", isa => "lib::Javascript", default => sub { lib::Javascript->new } ); # 追加
sub print {
my $self = shift;
print $self->q->header( -charset => "utf-8" );
print $self->q->start_html(
-title => "Perl only Web App",
-style => { -code => $self->css->create_style },
-lang => "ja",
-encoding => "utf-8" );
print $self->q->h1("Welcome");
print $self->q->p("This is sample site.");
print $self->script->create; # 追加
print $self->q->end_html;
}
1;
ページ読込時にアラートを表示します。
だいぶ煩くなってきました。
余談ですが、僕は上記を動かすために、なぜか JavaScript::Code::Accessor
の__args
サブルーチンを args
に書換えるという禁忌を冒しました。
だって、どのメソッド使っても「 args
サブルーチンがねぇぞ」って怒られるんだもん!親クラスを見たら __args
サブルーチンしかなかったんだもん!!
ちなみに、こんなものもあります。
しかし、PerlコードにJavaScriptを書くという、あまりにも訳のわからないモジュールだったので、使いません!(いや何が違うねん、と思った方は、下記を参照してください。)
これがありなら、全部ヒアドキュメント化すればいいじゃない!
JavaScript - Perl extension for executing embedded JavaScript - metacpan.org
https://metacpan.org/pod/JavaScript
HTML::JQuery
もう完璧です、なんでも書けますよ。
......え、今どきVanillaJSなんて書かない?
もっとモダンなJavaScriptは無いのか?
わかりました、jQueryで妥協してください!
HTML::JQuery - Generate jQuery/Javascript code in Perl - metacpan.org
https://metacpan.org/pod/HTML::JQuery
package lib::Jquery;
use utf8;
use Mouse;
use HTML::JQuery;
sub create {
my $j = jquery sub {
onclick "h1" => sub { alert "Welcome!!!!!" };
};
}
1;
これを呼出すためには、以下のように書きます。
JavaScript::Codeモジュールで生成した script
要素周辺に置いておきましょう。
package lib::Html;
use utf8;
use Mouse;
use CGI;
use lib::Css;
use lib::Javascript;
use lib::Jquery; # 追加
has q => ( is => "rw", isa => "CGI", default => sub { CGI->new } );
has css => ( is => "rw", isa => "lib::Css", default => sub { lib::Css->new } );
has script => ( is => "rw", isa => "lib::Javascript", default => sub { lib::Javascript->new } );
has jquery => ( is => "rw", isa => "lib::Jquery", default => sub { lib::Jquery->new } ); # 追加
sub print {
my $self = shift;
print $self->q->header( -type => "text/html", -charset => "utf-8" );
print $self->q->start_html(
-title => "Perl only Web App",
-style => { -code => $self->css->create_style },
-lang => "ja",
-encoding => "utf-8" );
print $self->q->h1("Welcome");
print $self->q->p("This is sample site.");
print $self->script->create;
print $self->q->script( { -src => "https://ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js" }, "" ); # 追加
print $self->jquery->create; # 追加
print $self->q->end_html;
}
1;
タイトルをクリックすると、アラームを表示します。
読込時にもアラートで、やかましいですね。
もう文句は言わせません!
VueとかReactとかNuxt系のモジュールも調べてみましたが、使ったこともないのでよくわかりませんでした!!
1ヶ所だけですが、指摘事項です。
jQueryのソースコードをダウンロードしている箇所(print $self->q->script
関数)では、空要素をタグで囲まないと、正しく動作してくれないようです。
ソースコードおよび出力結果の違いは、以下の通りです。
# ソースコード
print $self->q->script( { -src => "https://ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js" } );
# 出力結果
'<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js" />'
# ソースコード
print $self->q->script( { -src => "https://ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js" }, "" );
# 出力結果
'<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js"></script>'
出力結果の差分は、空要素をタグで囲んでいないか囲んでいるかの違いです。
どうして空要素を囲むと動作するのか、そこまで把握はできませんでした。
Windows 10 の Microsoft Edge 44.18362.449.0 で動作させました。
HTMLタグ/HTMLの基本/空要素 - TAG index
https://www.tagindex.com/html_tag/basic/empty.html
調べてみると、一部のブラウザでは空要素を1つの要素で閉じるだけでは、正常に読んでくれないみたいですね。
2011年の記事ですが、この頃から変わってないのか......?
scriptタグは空要素っぽく閉じると,FirefoxやIEで読んでもらえない ::ハブろぐ
https://havelog.ayumusato.com/develop/html/e216-script_tag_should_not_self-close.html
結果
最終的な標準出力結果は、以下の通りです。
どうです?
ちゃんとCGIでしょう???
Content-Type: text/html; charset=utf-8
<!DOCTYPE html
PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" lang="ja" xml:lang="ja">
<head>
<title>Perl only Web App</title>
<style type="text/css">
<!--/* <![CDATA[ */
p {
margin-bottom: 1em;
margin-left: 30px;
margin-right: 30px;
margin-top: 1em;
}
h1 {
margin-bottom: 1em;
margin-left: 30px;
margin-right: 30px;
margin-top: 1em;
}
/* ]]> */-->
</style>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
</head>
<body>
<h1>Welcome</h1><p>This is sample site.</p><script language="Javascript" type="text/javascript"><!--
alert ( "Welcome!!!" );
// --></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js"></script><script type="text/javascript">
$(document).ready(function() {
if (typeof init == 'function') { init(); }
$('h1').click(function() {
alert("Welcome!!!!!");
});
});
</script>
</body>
</html>
おわりに
結局HTMLもCSSもJavaScriptもjQueryも覚える必要はあるし、それをPerlで書く方法まで覚えなければならないため、2度手間ですね。
紆余曲折ありましたが、なんとかできることを確認できて大満足です。
まさに、TMTOWTDIですね!(これさえ言えば何とかなると思ってる)(違う、そうじゃない)(コーナーで差をつけろ)(1年間温めてたネタだなんて言えない)