テンプレートエンジンEJSで使える便利な構文まとめ


※gulp-ejsでのビルドを想定しています。

EJSでできること

  • 数や文字列や配列やオブジェクトの宣言と処理(JavaScriptと同じ)
  • HTMLへの値の展開
  • ループと条件分岐
  • 外部ファイルの読み込みとパラメータの引き渡し

<% %>の種類

<% %>

このタグの内側はJavaScriptワールドになる。HTMLとしては出力されない

<% var myParam = 'Hello! EJS!'; %>

<%= %>

中にあるJS変数の値をエスケープ込みで展開する。<>がエスケープされて出力されるので、HTMLタグ等は<%- %>を使う

demo.ejs
<div><%= myParam %></div>
demo.html
Hello! EJS!

使用例

サイトのメタ情報をオブジェクトに定義し、タグ内に展開する

demo.ejs
<% var meta = { title:'site name', desc:'このサイトの説明' }; %>

<title><%= meta.title %></title>
<meta name="description" content="<%= meta.desc %>" />

<%- %>

中にあるJSの値をエスケープなしで展開する

demo.ejs
<% var para = '<p>Hello EJS!</p>'; %>
<div><%= para %></div>
<div><%- para %></div>
demo.html
<div>&lt;p&gt;Hello EJS!&lt;/p&gt;</div>
<div><p>Hello EJS!</p></div>

<%# %>

内側がコメントとなり、HTMLの出力結果に影響しない。

demo.ejs
<%# EJS Comment! %>
<!-- HTML Comment! -->
demo.html
<!-- HTML Comment! -->

<%% %>

通常は使わないが、Yeoman等でEJS自体をEJSでテンプレート化したい時に<% %>のエスケープとして使用する。

template.ejs
<%% var hoge = 'hogehoge'; %>
<%%- include('./head'); %>
<h1><%= templateProp %></h1>

templatePropに変数を展開してoutput.ejsとして出力した想定

output.ejs
<% var hoge = 'hogehoge'; %>
<%- include('./head'); %>
<h1>Title Text</h1>

include()関数

<%- include('./_partial', {param:'param'}) %>

第一引数に読み込むEJSファイルへの相対パス(自ファイルが基準)、
第二引数にそのEJSに渡すパラメータを指定できる。
パスは拡張子の「.ejs」を省略できる。

似たものにincludeディレクティブがある。

<% include _partial %>

include()関数と違い、

  • パラメータを投げられない
  • パスを変数から作れない

というデメリットがあるので、include()関数を使う方が便利。

変数のスコープと受け渡し

1つのEJSファイルの中でスコープが閉じている。include()したファイルへ変数を渡すには次の2つの方法がある。

  • include()の第2引数で渡す
  • グローバル変数としてvarを付けずに変数宣言する

例:ファイル間でローカル変数の共有は不可

one.ejs
<% var hoge = 'hoge'; %>
<% foo = 'foo'; %>
two.ejs
<%# エラー %>
<%= hoge %>

<%# エラーにならない %>
<%= foo %>

例:include()の第2引数の使い方

オブジェクトのキー名が、子ファイルのローカル変数名と対応する。

one.ejs
<% var myData = {head:'Head text', body:'Body text'}; %>
<% include('two.ejs', {var1:'hoge', data:myData}); %>
two.ejs
<section class="<%= var1 %>">
  <h2><%= data.head %></h2>
  <p><%= data.body %></p>
</section>

子に投げていないキー名をいきなり参照しようとするとエラーになってしまう。
キー名があるかどうか分からない場合は、変数がundefinedの時は初期値をセットするようにするとエラーにならない。

two.ejs
if (typeof var1 === 'undefined') { var var1 = ''; }
if (typeof data === 'undefined') { var data = {head:'default', body:'default'}; }

<section class="<%= var1 %>">
  <h2><%= data.head %></h2>
  <p><%= data.body %></p>
</section>

ループ

for

<% for (var i = 0; i < 10; i++) { %>
<p>このループは<%= i+1 %>回目です。</p>
<% } %>

古典的なループその1。

while

<% var counter = 1; %>
<% while (counter <= 10) { %>
<p>このループは<%= counter %>回目です。</p>
<% counter++; %>
<% } %>

古典的なループその2。

array.forEach()

<% var ary = ['アイテム1', 'アイテム2', 'アイテム3']; %>
<% ary.forEach(function (value, key) { %>
<p><%= key %>: <%= value %></p>
<% }); %>

配列の中身を順番に取り出すならこれが一番使いやすい。

for...in, for...of

<% var ary = ['アイテム1', 'アイテム2', 'アイテム3']; %>

<% for (var key in ary) { %>
<p><%= ary[key] %></p>
<% } %>

<% for (var item of ary) { %>
<p><%= item %></p>
<% } %>

for...inやES6のfor...ofも使用できる。for..inは順番が不確定なので、テンプレートエンジンでは使わない方が良いかも。

条件分岐

if

<% if (data.type === 'type1') { %>
<p class="type1">This template is for type1.</p>

<% } else if (data.type === 'type2') { %>
<p class="type2">This template is for type2.</p>

<% } else { %}
<!-- else -->
<% } %>

JavaScriptのif/elseを使うことができる。

switch

2015年11月現在、<% %>で分断したswitch文は実装されていないので注意。

例:これは動く

<% var text = ''; %>
<% var state = 0; %>
<% switch ( state ) {
  case 0:
    text = 'case0';
    break;
  case 1:
    text = 'case1';
    break;
} %>
<p><%= text %></p>

例:これは動きそうだけど動かない

<% var text = ''; %>
<% var state = 0; %>
<% switch ( state ) { %>
<% case 0: %>
  <p>case0</p>
<% break; %>
<% case 1: %>
  <p>case1</p>
<% break; %>
<% } %>

関数

ひとまとまりのHTMLテンプレートを関数化して再利用できる。ループや条件分岐を多用するとどんどんネストが深くなってしまうので、ときどき関数にくくり出すと整理しやすい。

demo.ejs
<%# generateItem関数を定義 %>
<% var generateItem = function(name, dataList){ %>
  <ul class="<%= name %>">
    <% dataList.forEach(function (dataItem, index) { %>
      <li class="<%= name + '__' + index %>"><%= dataItem %></li>
    <% }); %>
  </ul>
<% }; %>

<%# ここで定義した関数を実行 %>
<%- generateItem('item-list', ['ホーム', '新着情報', '会社概要']); %>

自分がWeb制作でよくやるEJSの運用

JekyllやHugoで言うfront-matterっぽいものが欲しいので、以下のような機能をテンプレート化してコーディングしている。

  • グローバルで参照できるオブジェクトを全ファイルで使えるように、毎回先頭で_data.ejsをinclude()
  • 他のファイルからinclude()をコピペしてもそのまま動くように、ルートまでのパスを変数化して全ファイルで読み込むようにしている
  • あとで必要なことに気付くと面倒臭いので、例えすぐには使わなくともinclude()先には常に同じ名前でグローバルのdataオブジェクトを渡すようにする
data/_data.ejs
<% getData = function () {
    return {
      meta: { ... },
      navItems: [...]
    }
};
index.ejs
<% var ejsRoot = './'; %>
<% include(ejsRoot + 'data/_data'); var data = getData(); %>
<% data.root = './'; %>
<%# parts/_header %> <%- include(ejsRoot + 'parts/_header', {data:data}); %>
<%# ここに何か書く %>
<%# parts/_footer %> <%- include(ejsRoot + 'parts/_footer', {data:data}); %>

EJSを使ったHTMLコンポーネントの設計手法

拙作のスライドで詳しく紹介している。

コンポーネント単位で考えるWeb制作
http://media-massage.net/works/docs/componentweb/

EJS参考ページ

http://www.embeddedjs.com/
https://www.npmjs.com/package/ejs