PerlだけでWebサイトを作る

本記事は、[Perl Advent Carendar 2019](https://qiita.com/advent-calendar/2019/perl) 21日目の記事です。



#TL; DR

どうしてもPerlだけでWebサイトを作りたいですか？
次の3つ（ないし4つ）のモジュールを使えば何とかなります。
頑張ってください！
メリットは無いと思います！！！

- [CGI](https://metacpan.org/pod/CGI)
- [CSS::Tiny](https://metacpan.org/pod/CSS::Tiny)
- [JavaScript::Code](https://metacpan.org/pod/JavaScript::Code)
- [HTML::JQuery](https://metacpan.org/pod/HTML::JQuery)



# はじめに

複数言語を利用したプロジェクトは、様々な観点から忌み嫌われています。

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で代用できます。

```pl:lib/Html.pm
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ファイルを作ります。

```pl:index.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` ファイルのディレクトリ構造は、以下の通りです。

```txt:ディレクトリ構造
index.cgi
lib/
  |-- Html.pm
```

以降、新たに登場するモジュールを利用しているモジュールは、 `lib/` ディレクトリに入れましょう。

では、CGIを実行できるディレクトリに突っ込んで、ブラウザで `index.cgi` を開きます。

![CGIモジュールを用いたWebサイト](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/216416/ccc8c336-b975-1794-8eab-8a444851a102.png)

シンプルながら、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

```pl:lib/Css.pm
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` ファイルです。

```pl:lib/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;
```

![CSS::Tinyモジュールを用いたWebサイト](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/216416/4c7be5a1-4d55-cecd-9b92-8a9c733757d4.png)


少し変化が分かりづらいですが、タイトルと本文の上下左右に余白ができており、スタイルが適用されていることがわかりますね。
これにより、今どきのモダンなサイトも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

```pl:lib/Javascript.pm
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` 要素を入れます。

```pl:lib/Html.pm
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モジュールを用いたWebサイト](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/216416/719fc961-959c-ae01-6838-7d464c1ccf33.gif)

ページ読込時にアラートを表示します。
だいぶ煩くなってきました。

---

余談ですが、僕は上記を動かすために、なぜか `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

```pl:lib/Jquery.pm
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` 要素周辺に置いておきましょう。

```pl:lib/Html.pm
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;
```

![HTML::JQueryモジュールを用いたWebサイト](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/216416/69f2633a-96b6-8792-2dac-9ea0e3c45dce.gif)

タイトルをクリックすると、アラームを表示します。
読込時にもアラートで、やかましいですね。
もう文句は言わせません！
~~VueとかReactとかNuxt系のモジュールも調べてみましたが、使ったこともないのでよくわかりませんでした！！~~

---

1ヶ所だけですが、指摘事項です。
jQueryのソースコードをダウンロードしている箇所（`print $self->q->script` 関数）では、空要素をタグで囲まないと、正しく動作してくれないようです。

ソースコードおよび出力結果の違いは、以下の通りです。

```pl:第2引数に空文字無し版
# ソースコード
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" />'
```
```pl:第2引数に空文字有り版
# ソースコード
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でしょう？？？

```html:標準出力結果（改行位置などそのまま）
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年間温めてたネタだなんて言えない）