15
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

PerlAdvent Calendar 2019

Day 21

PerlだけでWebサイトを作る

Posted at

本記事は、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で代用できます。

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ファイルを作ります。

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

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

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

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

CGIモジュールを用いたWebサイト

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

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 ファイルです。

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サイト

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

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 要素を入れます。

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サイト

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


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

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 要素周辺に置いておきましょう。

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サイト

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


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

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

第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" />'
第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でしょう???

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

15
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
15
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?