静的サイトの作成に使用するHTMLのテンプレートエンジンをPugからEJS・Nunjucksを試してEJSにした
ここ最近、静的なWebサイトを制作する際にNuxtのgenerate機能を使用してNuxtを静的サイトジェネレーターとして使用しています。
しかし、Nuxtほどの機能が求められていなかったりする場合、独自に作った静的サイトジェネレーターを使用しています。
そこで使用していたHTMLのテンプレートエンジンとして「Jade」という名前のときから「Pug」を利用してきましたが、ここ最近ちょこちょこと辛く感じることもあり、EJS、Nunjucksを試して、最終的にEJSへ変更しました。
なぜPugから変更したのか?
Pugは閉じタグが不要、入れ子はインデントで表現するという独特の記法で記述量が少なく、"タグの閉じ忘れ"が無いというメリットがあります。
しかし、
- 別のプロジェクトなどに流用しにくい
- 入れ子が深くなってくると見にくくなってくる
といった理由から、Pugがちょっと辛いなーと感じるようになってきました。
Pugで書かれたものを別のプロジェクトに持っていきたいときとか、別のフレームワークを導入、となると ちょっと面倒です。(あまりこういう場面は多くないですが)
HTMLで書かれている方が、色々と汎用的に使えます。
あと単純にインデントが深くなると、見にくいと感じました。まぁそこまで深い状態にしてる事自体が間違いだと思いますが。
エディタも閉じタグ補完など行ってくれるので、Pugを採用するメリットが薄くなっていると感じてきました。
また、VueのSFC内の<template>にeslintやVeturのチェックが効かないので、ここ最近はHTMLで記述してるというのと、独特な書き方のため新規参入者の人がちょっと戸惑うこともあったので、Pugから変更することを決めました。
素のHTMLで良いんじゃね?ってなりそうですが、例えばグローバルヘッダーなど全てのページで共通のものは一元管理したいとか、レイアウトはほぼ同じなのに、中のテキストだけが違うなどのようなものを使い回せるように利用したい、といったことは素のHTMLだと出来ません。
SSIを利用すると共通部分の一元管理も出来ますが、サーバーの状況によって左右されるようにはしたくないと思っています。
他のHTMLのテンプレートエンジンの候補
まず、HTMLをそのまま書けるテンプレートエンジンを探しました。
色々ありますが、多くても試すのが大変なので、EJSとNunjucksの2つに絞りました。
EJSは <% と %> で囲った中に JSを書けるので、かなり自由にあれこれできます。変数の展開も<%= 変数名 %> といった感じで展開できます。
Nunjucksは{% と %}で囲った中にNunjucksというよりjinja独自の構文を書いていきます。(公式ドキュメントを見ると、jinja という名前は本当に神社っぽい)変数の展開はMastache記法で展開されます。
EJS・Nunjucksとも別のファイルを読み込む include 機能や繰り返し処理など、欲しい機能は揃っている感じです。
Nunjucksを試してみて
Nunjucksは聞いたことがあったものの、今まで一度も使用したことがありませんでした。ですので、使ってみたいと思ってたHTMLテンプレートエンジンの一つでもあります。
EJSにはないNunjucksの機能としてextends(layout)機能があります。基本的にはlayoutのファイルを拡張してコンテンツを作り上げていくということになると思います。このextends機能はPugにもあり、よく使う機能だったのでとても便利に感じました。
EJSを試してみて
EJSは以前に使用したことがあり、Pugから変更する際にもすぐに変更先の候補として思いついたテンプレートエンジンです。
extends機能は無いのですが、変数や関数の定義の方法はJSそのものになりますし、自由にJSを記述出来る点がとても便利に感じました。
最終的にEJSを選択した理由
EJSとNunjucksを試してみて、最終的にEJSを採用することに決めました。
主な理由は以下になります。
- Nunjucks(jinja)独特の構文を学ぶ必要がある
- NunjucksのMastacheとVueのMastacheが被る
- EJSでもLayout機能っぽいことが出来る
Nunjucks(jinja)独特の構文を学ぶ必要がある
NunjucksにはEJSや他のHTMLテンプレートエンジンと同じように変数の宣言だったり、HTMLの一部を部品として別で管理する機能がありますが、その宣言の仕方が独特です。
変数の宣言は
{% set hoge = 'hoge' %}
といったように、「set 変数名 = 値」という風に変数を定義します。
HTMLの一部を部品として扱う場合は「macro」という、JSでいう関数宣言っぽい書き方をします。
{% macro field(name, value='', type='text') %}
<div class="field">
<input type="{{ type }}" name="{{ name }}"
value="{{ value | escape }}" />
</div>
{% endmacro %}
また、個人的に最もハマったところが、filter機能になります。
filter機能とは出力する値を加工する際に使用するものになります。
Nunjucksにはbuilt-in filterとして既に便利なfilterが多くあります。
このfilterの名前はJSでも使用されてるものだったりするので、馴染みがあるのですが、例えば「replace」のfilterで以下のように正規表現を使用して文字列の差し替えをしようとするとエラーとなってしまいました。
{% set TITLE = 'TITLE' %}
{{ TITLE | replace(/T/, 'ttt') }}
// or
{{ TITLE.replace(/T/, 'ttt') }}
また、正規表現オブジェクトを事前に変数としてreplaceしようと試みましたが、変数宣言の時点でエラーとなってしまいました。
{% set reg = new RegExp('T') %}
or
{% set reg = /T/ %}
Nunjucksで正規表現を扱うには「r」というプレフィックスを付与する必要があります。
{% set TITLE = 'TITLE' %}
{{ TITLE | replace(r/T/, 'ttt') }}
// or
{{ TITLE.replace(r/T/, 'ttt') }}
このようにJSっぽいけど、微妙に違った独自の構文を使用するということに少し戸惑いを感じました。
NunjucksのMastacheとVueのMastacheが被る
Nunjucksに限った話ではないのですが、Nunjucksでは変数などの値の展開にはMastache記法({{ と }} で囲うやつ)が使われます。このMastache記法は他のテンプレートエンジンでも採用されてる変数などの展開方法の一つです。
Vueのテンプレートでも同じようにデータなどの展開はこのMastache記法を用いています。
Vueのテンプレートを記述したHTMLをNunjucksに通すと(当たり前ですが)Vueのテンプレートで使用しているMastacheもNunjucksがコンパイルしてしまいます。
これを避けるためVueのテンプレートを記述したい部分に {% raw %} というブロックで囲う必要があります。
例)↓
{% raw %}
// この部分は記述したまま出力される
{% endraw %}
また、別の方法としてシンタックスをカスタマイズする方法もあります。
ただ、これをしてしまうと独自ルールの色が強すぎてしまうのかなーと感じてしまいました。
EJSでもextends機能っぽいことが出来る
EJSにはextends機能自体は無いのですが、それっぽいことが可能だと判明しました。基本的には include で別ファイルを読み込むのですが、その読むこむファイルの一部を使う側から変更することが出来ます。
Vueで言うscoped slot みたいな感じです。
Nunjucksでもextends機能を使用する際は予め、どの部分を差し替えるかを以下のようにして決めておく必要があります。
// layout.html
{% block header %}
This is the default content
{% endblock %}
<section class="left">
{% block left %}{% endblock %}
</section>
<section class="right">
{% block right %}
This is more content
{% endblock %}
</section>
// index.html
{% extends "layout.html" %}
{% block left %}
This is the left side!
{% endblock %}
{% block right %}
This is the right side!
{% endblock %}
// output HTML
This is the default content
<section class="left">
This is the left side!
</section>
<section class="right">
This is the right side!
</section>
似たようなことをEJSでやるには以下のような感じで実現できました。
// layout.ejs
<% if (typeof header !== 'undefined') { %>
<%- header(__append) %>
<% } else { %>
This is the default content
<% } %>
<section class="left">
<%- typeof left !== 'undefined' ? left(__append) : null %>
</section>
<section class="right">
<%- typeof right !== 'undefined' ? right(__append) : 'This is more content' %>
</section>
// index.ejs
<% function left(__append) { %>
This is the left side!
<% } %>
<% function right(__append) { %>
This is the right side!
<% } %>
<%-
include('/layout.ejs', {
left,
right
})
%>
// output HTML
This is the default content
<section class="left"> This is the left side! </section>
<section class="right"> This is the right side! </section>
関数を実行する際に「__append」という変数を引数として渡さないと、任意の場所に出力しないです。
コンテンツ自身をfunctionで囲ってやれば、extends機能として使えそうですが、コンテンツのfunctionがどこからどこまで囲ってあるのか わからなくなりそうなので、やめました。
ですので、コンテンツよりも上部のHTML要素とコンテンツよりも下のHTML要素を includeする方法をとりました。
自分にはEJSが合ってると思った
EJS、Nunjucksを試してみて、自由にJSでアレコレ出来るので自分にはEJSのほうがやりやすいと思ったのと、ちょっとアクロバティック的な普段やらないようなことをする必要があった場合もJSで強引になんとか出来ることもあったので、EJSを採用することにしました。
まとめ
Firebase Authenticationといったサービスもあり、静的ページでやりたいことの大半は出来るようになってきました。また、Jamstackと呼ばれる構成も普及しつつあるっぽいので、静的サイト制作の需要もまだまだありそうです。
静的サイトジェネレーターと呼ばれるものもたくさんあるので、自分に合ったものをこれからも探っていきたいと思います。