ejsでextendっぽいことをする方法

More than 5 years have passed since last update.

最近よく使っているテンプレートエンジンのejs、気に入っているんですが、

extendがつかえないというところがちょっと不満でした。

これをなんとかする方法を見つけたので、紹介します。


テンプレートのextend

まず、extendというのがどういうことか。

例えば別のテンプレートエンジン・jadeでは、


  • ヘッダーとフッターは共通で

  • 中身のコンテンツだけ書く

  • ただし、jsライブラリもひとつ追加で入れたい

というとき、以下のように書けます。


content.jade

extends ./layout

block scripts
script(src="/underscore.js")

block content
h1 きじたいとる!
p 本文



layout.jade

!!! 5

html
head
title サイトタイトル
block scripts
script(src="/jquery.js")
body
section#content
block content

つまり、content.jadeはlayout.jadeを継承(extend)して、

必要な部分だけ追記しているわけです。

これによって、content.jadeは必要な情報だけ書けばよいことになるので、

ファイルが短くなり、変な場所をイジる危険・ミスの可能性も減ります。


ejsの場合

このextendが、ejsだとできない。

決まった別のテンプレートをincludeすることはできるんですが、

提供されている機能としてはそれだけです。

↑と同じことをやろうとした場合、こんなかんじです。


content.ejs

<% include header %>

<script src="/underscore.js">
<h1>きじたいとる!</h1>
<p>本文</p>
<% include footer%>


header.ejs

<!DOCTYPE html>

<html>
<head>
<title>サイトタイトル</title>
<script src="/jquery.js"></script>
</head>
<body>
<section id="content">


footer.ejs

</section>

</body>
</html>

headerとfooterを分ける必要があり、

また追加のscript読み込みを、headタグの中に入れるのが無理です。

ぬー。


サンプルを見たらなんかあった

何とかならないのかなーと思いつつ、公式のサンプルを見ていたら、

いいものがありました。

https://github.com/visionmedia/ejs/blob/master/examples/functions.ejs

<h1>Users</h1>

<% function user(user) { %>
<li><strong><%= user.name %></strong> is a <%= user.age %> year old <%= user.species %>.</li>
<% } %>

<ul>
<% users.map(user) %>
</ul>

おーーー、functionで囲めば、再利用可能な部分テンプレートを、テンプレート内で定義できる!

これを応用すれば、extendに近いことできそう!

てなわけで、いろいろ試した結果、以下の形でうまく動きました。


content.ejs

<% function scripts (buf) { %>

<script src="/underscore.js">

<% }; function content (buf) { %>

<h1>きじたいとる!</h1>
<p>本文</p>

<% }; include layout %>



layout.ejs

<!DOCTYPE html>

<html>
<head>
<title>サイトタイトル</title>
<script src="/jquery.js"></script>
<% scripts(buf) %>
</head>
<body>
<section id="content">
<% content(buf) %>
</section>
</body>
</html>


bufってなにさ

これを使うときのポイントがひとつ。

関数定義・呼び出しの部分で、bufっていう引数が入ってますね。

これは、ejsのテンプレートファイルのコンテキストみたいなものが入ってる変数です。

(ejsのコード読んで判明。そういう内部変数みたいやなつは、ほんとは_bufとかそれっぽい名前にしてほしいけど…)

includeを使ってテンプレートが別ファイルになったとき、

このコンテキストが変わってしまうようで。

そのままでは、content.ejsで定義した関数は、常にcontent.ejsの範囲に出力される、というわけです。

実際、このbufを渡さない形でテンプレートを実行すると、以下のような悲しい結果が返ってきます。


<script src="/underscore.js">

<h1>きじたいとる!</h1>
<p>本文</p>

<!DOCTYPE html>
<html>
<head>
<title>サイトタイトル</title>
<script src="/jquery.js"></script>

</head>
<body>
<section id="content">

</section>
</body>
</html>

これはejsのバグって言ってもいいような、仕様って言ってもいいような。。


まとめ

ejsでもextendできるってことがわかったので、

不満だいぶ減った。

ついでに、includeするときの基準パスをカスタマイズする方法も見つけたので

そちらもまたそのうち。