静的HTMLのためのテンプレートエンジン 第1回 共通部分が多いHTML

共通部分が多いHTMLを効率よく作るには、さまざまな手法が考えられます。このシリーズでは、テンプレートエンジンを使用してHTMLを生成するという手法を取り上げ、解説していきます。

発行

著者 坂巻 翔大郎 フロントエンド・エンジニア
静的HTMLのためのテンプレートエンジン シリーズの記事一覧

はじめに

ピクセルグリッドでは、企業サイトのリニューアル案件やWebアプリケーションのフロント側の制作が主になっています。中でも、リニューアル案件では、ページの量産は行わず、量産のもととなるページのHTMLを作成しています。量産は行いませんが、サイトの規模によっては、それなりの量のHTMLを作成していく必要があります。

最近関わった案件ではPC用40ページとSP用40ページ、合計80ページを作成する必要がありました。それらのHTMLには、ヘッダーやフッターであったり、サイドバーであったりと、共通する部分が多く存在します。筆者はその80ページを1ページ毎に、一からHTMLを書くことはしませんでした。HandlebarsというHTMLテンプレートエンジンを使用したのです。

Handlebarsでは、共通部分を別のファイルに切り出し、それらを各ページで使用することで、すべてのページに同じHTMLが記述されます。また、その共通部分に修正があったときには、その切り出したファイルを修正すればすべてのHTMLに反映されるようにしました。これが筆者の言う「一からHTMLを書くことはしない」ということになります。

昔の私であれば、共通部分をコピペして1ページ1ページ作成していましたし、共通部分に修正があった場合は、すべてのHTMLに対して置換を行っていました。しかし、上述したように、今はHTMLテンプレートエンジンを利用することでその手間を減らすことができています。本記事では、こういった「できるだけHTMLを書かない・書く量を減らすための手法」を紹介します。

注:テンプレートエンジン

JavaScriptからテンプレートエンジンを扱う方法については、CodeGridで過去に記事として取り上げています。併せてご覧ください。

HTMLを作成していてつらいときとは

筆者がHTMLを書いていてつらいと感じるときは、つぎのような場合です。

  • ほとんど同じような内容なのに、1ページ1ページ丁寧にHTMLを作らなくてはいけない
  • 1箇所変更があったら、全ページを修正することもあり、変更漏れや置換ミスが起こる
  • HTMLが複雑になっていき、閉じタグの位置がわかりづらくなり、閉じタグが欠落する

HTML自体にはプログラミング言語でいう変数やfor文などの仕組みがないため、同じような記述を何度もしなくてはいけません。また、閉じタグが欠落していたとしても、ブラウザはよしなに補完し、ある程度の見た目で表示されます。もちろん、バリデーションしていれば、閉じタグがないといったことに気付くことはできます。しかし、もし閉じタグを書かずにHTMLを作成することができれば、閉じタグが欠落するといったミスをなくすことができるかもしれません。

より効率的にHTMLを作成するためにはどのような方法があるかを挙げてみます。

解決するためによくある手法

似たようなページを大量に作る場合や、大量に作ったページすべてに修正があった場合などに、制作を楽にする方法としてよく利用される「インクルード」というものがあります。

インクルードは、ファイルの中に別のファイルを組み込むことを指します。ヘッダーやフッターといった共通部分のHTMLを別々のインクルードファイルに分け、その分けたファイルを各ページで読み込むようにし、最終的にHTMLを生成すると、ひとつにまとまって表示されます。こうしておけば共通部分に変更があったときに、そのインクルードファイルを修正すれば、それらが読み込まれているすべてのページに反映することができます。

HTML自身には異なるHTMLファイルをインクルードする機能がありません。しかし、インクルードするためには、ほかにもいろいろな手段があります。筆者が、過去にその問題を解決するために使用した手段を紹介します。

iframe要素を使用したインクルード

iframe要素を使用すれば、異なるHTMLファイルを表示することができます。しかし、iframe要素は異なるHTMLをiframe要素の枠の中に表示しているだけで、インクルードファイルのHTMLの記述が、それをインクルードしているHTMLの記述として挿入されるわけではありません。iframe要素によるインクルードは、ブログパーツや広告の挿入でよく使われています。

SSIを使用したインクルード

次に、SSI(Server Side Include)です。SSIはWebサーバーの機能の一つです。HTML内にサーバーで実行するコマンドを記述し、その結果を画面に反映します。

SSIに対応したWebサーバーであれば、HTML中にSSIを記述することができます。まず、ヘッダーのみの記述があるHTMLファイル(header.html)を作成します。

<!-- ▼▼/common/inc/header.htmlの内容▼▼ -->
<div class="header">ヘッダーです</div>
<!-- ▲▲/common/inc/header.htmlの内容▲▲ -->

そして、このheader.htmlが、index.htmlで表示されるようにしたい場合は、以下のようにします。

<!--#include virtual="/common/inc/header.html" -->
<div class="contents">
  ここはトップページです。(index.html)
</div>

このindex.htmlを、SSIが動作するサーバー上に置くことで、ブラウザに表示されるHTMLは、次のようになります。

<!-- ▼▼/common/inc/header.htmlの内容▼▼ -->
<div class="header">ヘッダーです</div>
<!-- ▲▲/common/inc/header.htmlの内容▲▲ -->
<div class="contents">
  ここはトップページです。(index.html)
</div>

SSIを使用することで、共通する部分を別のファイルにして管理できるので、多少なりともHTMLの作成が楽になります。

しかし、SSIを利用するためには、サーバーの設定ファイルを変更する必要がありますし、場合によっては使用できないこともあります。またサーバー側の負荷や、セキュリティ面で問題があることもあるので、使用する際は、サーバー側の都合と相談して使用したほうがよいと考えます。

JavaScriptを使用したインクルード

次に、JavaScriptを使用したインクルードです。手段はいろいろありますが、古くからある方法では以下のような形です。

document.write('<div class="header">ヘッダーです</div>');
<script src="header.js"></script>
<div class="contents">
  ここはindex.htmlです。
</div>

広告や計測用の挿入タグが、こういった手法をとっていることが多くあります。document.write()以外にも、XMLHttpRequestを使用しHTMLファイルを取得して、その内容を挿入するという方法もあります。注意すべきこととしては、JavaScriptが無効なときに何も表示されないことや、場合によっては表示が遅くなることもあるので、使用する際はいろいろと工夫しなくてはいけないことでしょう。

サーバーサイドでテンプレートエンジンを使う

また、サーバーサイドでテンプレートエンジンを使うこともできます。筆者は、Smartyという、PHPで利用できるテンプレートエンジンを使用しました。Smartyでは、サーバーサイドで動的にHTMLを出力します。Smartyのテンプレートでは、SSIのように共通部分を分離したりすることもできますし、インクルードに値を渡すこともできます。

<!-- header.tpl -->
<div class="header">{$text}</div>

Smarty用のインクルードファイルであるheader.tplでは$textという値を表示するようにしておきます。

<!-- index.tpl -->
{include file='header.tpl' text='ヘッダー'}
<div class="contents">
  ここはindex.htmlです。
</div>

index.tplでは、header.tplに対して、text='ヘッダー'という形でインクルードファイルに値を渡しています。これがSmartyによって処理されると最終的には次のようなHTMLになります。

<div class="header">ヘッダー</div>
<div class="contents">
  ここはindex.htmlです。
</div>

上記のSmartyのファイルを動作させるための実際のコードは、今回の本筋とそれるため省略します。

Smartyを使う場合には、サーバー側の環境設定や設計に大きく関係してくること、作業者のローカルで動作させるための準備が多くなることが考えられるため、手軽に導入できるものではないと、筆者は思います。

ここまで紹介した手段のどれかを使ったことがある方もいるでしょう。どれも最終的にブラウザで表示されたときに、それぞれのインクルードが展開されます。しかし、もし「サーバーの都合でSSIが使えない」「サーバー側のテンプレートエンジンが使えない」「納品はSSIなどを使わないHTMLでほしい」といった場合にはこれらの手段は使えず、1ページ1ページHTMLを作らなくてはいけません。

ローカルでHTMLを生成する

このように、筆者がこれまで利用してきた手段には、いろいろな制限がある場合があります。納品自体は静的なHTMLファイルにする必要があるが、SSIなどが使えない。でも共通した部分はうまいこと管理したい。そういったときには、HandlebarsやECT、Jadeといったテンプレートエンジンを使用してHTMLを生成すると、作業の効率化が図れるかもしれません。

これは、Sassのように変数を使ったりファイルを分割したりといったことをHTMLでも行うイメージです。具体的な手法を解説していく前に、これから紹介するどのテンプレートエンジンにも共通した考えを解説しましょう。それはインクルードとテンプレートの継承です。

静的なHTMLを生成する時のインクルード

前述のとおり、インクルードは共通部分を別ファイルにして、ページごとに読み込む方法です。

SSIやSmartyと、ローカルでテンプレートエンジンを使用することの違いは、サーバー側で動的にHTMLを生成するか、それとも静的なHTMLを生成するか、という生成するタイミングの違いになります。

SSIのようにヘッダとフッタの内容を別ファイルに分け、それぞれのページではその分けたファイルを読み込むことを記述しておきます。最終的にHTMLを生成すると、それらの内容が展開された状態のHTMLを作ることができます。

テンプレートの継承

次に、テンプレートの継承です。継承と聞くと難しそうに思えますが、要は、そのサイトで共通で使われる要素の入った雛形です。その雛形の中には、子のテンプレート(雛形を継承するテンプレート)から、上書きや挿入ができるブロックを定義することができます。

各ページのHTML(図中のsrc/page1.htmlpage2.html)では、雛形にどのHTMLを使うかを指定します。共通する部分は雛形のHTML(src/partial/layout.html)に書いておきます。

そして、テンプレートの継承では、上書きだけでなく、そのブロック内の冒頭に追記する方法や、末尾に追加する方法もあります。

この図では、layout.htmlheadbodyというブロックを用意してあります。それぞれのブロック内には、継承されるページすべてに記述されるコードが書いてあるとします。そのlayout.htmlを継承するpage1.htmlでは、bodyブロックを上書きします。page2.htmlでは、headブロックにprepend、つまり先頭に挿入したいコードを記述します。そしてbodyブロックにappend、つまり末尾に挿入したいコードを記述します。

そうすることで、同じ雛形を使いつつ、ページによって異なる記述をすることができます。最終的にHTMLを生成すると、雛形に各ページの内容が反映された状態のHTMLが生成されます(htdocs/page1.htmlhtdocs/page2.html)。

次回から紹介するテンプレートエンジンでは、こういった形で、各ページから雛形の特定の箇所を上書きしたり、その箇所の先頭に追加、末尾に追加するといったことができます。例えば、特定のページだけCSSを<head>の末尾に追加するということに使います。

インクルードの機能のみでは、必要なページすべてにインクルードの記述を書く必要がありますが、テンプレートの継承ができると継承元のテンプレートに必要なインクルードを記述するだけでよくなり、HTMLの管理がしやすくなるのです。

ここまでのまとめ

今回は、HTMLを分割して作っていくために過去に筆者がとった手段と、テンプレートエンジンを使用してHTMLを生成するための基礎知識的な部分にフォーカスしました。

SSIやJavaScriptによるインクルードといった手段を使うこともあるかもしれません。ですが、場合によってはそれらが使用できないこともあります。そういったときにも、効率よく制作していくために、テンプレートエンジンを使用してHTMLを生成するという方法も考えられます。

次回は、インクルードやテンプレートの継承といった機能を持ったテンプレートエンジンの中でも、筆者が利用したことのある、Handlebars、ECT、Jadeというテンプレートエンジンのそれぞれの特徴と、gulpを使用したHTML生成の方法について解説していきます。