※gulp-ejsでのビルドを想定しています。
EJSでできること
- 数や文字列や配列やオブジェクトの宣言と処理(JavaScriptと同じ)
- HTMLへの値の展開
- ループと条件分岐
- 外部ファイルの読み込みとパラメータの引き渡し
<% %>の種類
<% %>
このタグの内側はJavaScriptワールドになる。HTMLとしては出力されない
<% var myParam = 'Hello! EJS!'; %>
<%= %>
中にあるJS変数の値をエスケープ込みで展開する。<
や>
がエスケープされて出力されるので、HTMLタグ等は<%- %>
を使う
<div><%= myParam %></div>
Hello! EJS!
使用例
サイトのメタ情報をオブジェクトに定義し、タグ内に展開する
<% var meta = { title:'site name', desc:'このサイトの説明' }; %>
<title><%= meta.title %></title>
<meta name="description" content="<%= meta.desc %>" />
<%- %>
中にあるJSの値をエスケープなしで展開する
<% var para = '<p>Hello EJS!</p>'; %>
<div><%= para %></div>
<div><%- para %></div>
<div><p>Hello EJS!</p></div>
<div><p>Hello EJS!</p></div>
<%# %>
内側がコメントとなり、HTMLの出力結果に影響しない。
<%# EJS Comment! %>
<!-- HTML Comment! -->
<!-- HTML Comment! -->
<%% %>
通常は使わないが、Yeoman等でEJS自体をEJSでテンプレート化したい時に<% %>
のエスケープとして使用する。
<%% var hoge = 'hogehoge'; %>
<%%- include('./head'); %>
<h1><%= templateProp %></h1>
↓templateProp
に変数を展開して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
を付けずに変数宣言する
例:ファイル間でローカル変数の共有は不可
<% var hoge = 'hoge'; %>
<% foo = 'foo'; %>
<%# エラー %>
<%= hoge %>
<%# エラーにならない %>
<%= foo %>
例:include()
の第2引数の使い方
オブジェクトのキー名が、子ファイルのローカル変数名と対応する。
<% var myData = {head:'Head text', body:'Body text'}; %>
<% include('two.ejs', {var1:'hoge', data:myData}); %>
<section class="<%= var1 %>">
<h2><%= data.head %></h2>
<p><%= data.body %></p>
</section>
子に投げていないキー名をいきなり参照しようとするとエラーになってしまう。
キー名があるかどうか分からない場合は、変数がundefined
の時は初期値をセットするようにするとエラーにならない。
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テンプレートを関数化して再利用できる。ループや条件分岐を多用するとどんどんネストが深くなってしまうので、ときどき関数にくくり出すと整理しやすい。
<%# 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()- このグローバルなオブジェクトにはサイトの名前とか共通のdescriptionの文言とかを入れる
- もちろんgulp-ejs実行時にnode.js側で渡してもOK (参考:gulpで手軽にEJSテンプレートをHTMLに変換 - Qiita)
- 他のファイルから
include()
をコピペしてもそのまま動くように、ルートまでのパスを変数化して全ファイルで読み込むようにしている - あとで必要なことに気付くと面倒臭いので、例えすぐには使わなくともinclude()先には常に同じ名前でグローバルのdataオブジェクトを渡すようにする
<% getData = function () {
return {
meta: { ... },
navItems: [...]
}
};
<% 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