こんにちは。今回は現実逃避を兼ねて Jade の素晴らしさをお伝えしたいと思います。
Jade は JST (JavaScript Templates) の一つであり、HTML を書くための軽量マークアップ言語 である Haml に影響を受けた JavaScript テンプレートエンジンでもあります。
ちなみにこの Haml は近年爆発的に普及をみせる CSS プリプロセッサ Sass の記法の元にもなっています (Sass の中でも普及率が高いのは SCSS 記法の方ですが)。
一般的に JST というと以下のような記法を用います:
<section class="message">
<p id="greeting">こんにちは。私の名前は <%= name %> よ。よろしくね。</p>
</section>これは Underscore.js の JST 記法ですが、このように HTML の中の動的に出力を変更したい部分を、特定のテンプレート記法で置き換え、そこを JavaScript の変数から出力してやるものです。
var name = '名無しさん';例えば JavaScript で name 変数に上記のような値を代入してやると、以下のような出力が得られます:
<section class="message">
<p id="greeting">こんにちは。私の名前は 名無しさん よ。よろしくね。</p>
</section>サーバーサイドプログラミングの経験のある人なら PHP 等のテンプレートを思い浮かべるでしょう。もしくは WordPress 等の CMS のテンプレートを思い浮かべるかも知れません。
Jade はそれらの JavaScript 版の一つです。サーバーサイド版 JavaScript の Node.js で普及率の高い web フレームワークである express.js でも標準で採用されている事もあり、サーバーサイド JavaScript ではそれなりの普及度と知名度をもっています。
ただ、Jade の特徴を紹介する上では、単なるテンプレートエンジンとしての紹介では事足りないでしょう。Jade の特徴はその記法にあります。
前述の Haml について「軽量マークアップ言語」と述べたように、Jade も単なるテンプレート言語ではありません。最初の Underscore.js の JST 記法を Jade で書くと以下のようになります:
section.message
p#greeting こんにちは。私の名前は #{name} よ。よろしくね。もちろんこの出力結果は name に 名無しさん と代入すれば、先程と同様、以下のようになります:
<section class="message">
<p id="#greeting">こんにちは。私の名前は 名無しさん よ。よろしくね。</p>
</section>さて、では Jade の記法における違いは何でしょうか。以下が挙げられると思います:
- HTML 要素に閉じタグがない
classとid属性の記法が CSS セレクタの記法と同じ
また補足として Jade では インデントが必須 です。閉じタグを省略できる代わりに、DOM の入れ子構造に沿って適切にインデントを使用してやる必要があります。インデントに使用するのは、統一されていればタブでもスペース 4 つでもかまいません。
これが Haml から受け継がれた Jade の特徴です。
Python の経験がある方であれば、インデントを強制する言語構造にも理解があるのではないでしょうか。しかしこのインデント強制型記法は、否定的に捉えられる事も多々あるため、今回は、Jade の他の素晴らしさについて紹介できればと思います。
Jade はテンプレート言語ですので、PHP などでみられる簡単な処理も可能です。まずは Jade の記法と合わせて、その基本機能を紹介したいと思います。
まずは基本となる HTML レイアウトです。以下は一般的な HTML 5 による HTML の雛形です:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>最高にクールなホームページ</title>
<link rel="stylesheet" href="./css/app.css">
</head>
<body>
<h1>最高にクールなホームページ</h1>
<p>最高にクールなホームページへようこそ。</p>
<script src="./js/app.js" charset="UTF-8">
</body>
</html>さてではこれを Jade で書くとどうなるでしょう:
!!! 5
html
head
meta(charset='UTF-8')
title 最高にクールなホームページ
link(rel='stylesheet', href='./css/app.css')
body
h1 最高にクールなホームページ
p 最高にクールなホームページへようこそ。
script(src='./js/app.js', charset='UTF-8')行数が圧倒的に短かくなりました。これでキーボードのタイプ数が減り、ハードウェアの寿命が延び、資源の節約、ひいては地球にやさしい開発が可能ですね。
では HTML との違いを挙げてみましょう:
<!DOCTYPE html>宣言は!!! 5で置換します。- 先程紹介したように、要素の
classとid属性は CSS セレクタ同様の記法が可能です。 - その他の属性は
()で囲い、,で属性ごとに区切ります。
もちろん;
- 閉じタグは不要です。
- インデントは適切に挿入する必要があります。
これだけです。簡単ですね。
では以下の Jade 記法はどのような HTML になるでしょうか:
section#example-01.example
p JavaScript でハローワールド。
pre
| (function () {
| console.log('Hello, world.');
| })();答えは以下の通りです:
<section id="example-01" class="example">
<p>JavaScript でハローワールド。</p>
<pre>
(function () {
console.log('Hello, world.');
})();
</pre>
</section><pre> の中は読みやすくするため、インデントしていますが、実際の出力では HTML で余計な空白が入らないよう、Jade が調整して出力してくれます。
ここで新しい記号は | でしょう。
複数行に渡る内容を要素の中に記入したい時や、1 行でも改行して内容を書きたい時に重宝する記法です。
<br> 要素が間に入る場合などは以下のように書けます:
p
| 馬並みに
br
| 陽は沈むもちろん HTML 出力結果は以下のようになります:
<p>馬並みに<br>陽は沈む</p>簡単ですね。
さて、他によくある要望としては、SNS サイト等の HTML スニペットを挿入する場合の例です。
以下は Facebook の Like ボタン HTML スニペットです:
<div id="fb-root"></div>
<script>
(function(d, s, id) {
var js, fjs = d.getElementsByTagName(s)[0];
if (d.getElementById(id)) return;
js = d.createElement(s); js.id = id;
js.src = "//connect.facebook.net/ja_JP/all.js#xfbml=1";
fjs.parentNode.insertBefore(js, fjs);
}(document, 'script', 'facebook-jssdk'));
</script>
<div class="fb-like" data-send="false" data-layout="button_count" data-width="450" data-show-faces="false"></div>長いですね。こうゆう後付のコードをイチイチ Jade の記法に書き直して記入するのは面倒です。ささっとコピペで済ましたいところです。
そういった時は以下のように書けます:
div#facebook.
<div id="fb-root"></div>
<script>
(function(d, s, id) {
var js, fjs = d.getElementsByTagName(s)[0];
if (d.getElementById(id)) return;
js = d.createElement(s); js.id = id;
js.src = "//connect.facebook.net/ja_JP/all.js#xfbml=1";
fjs.parentNode.insertBefore(js, fjs);
}(document, 'script', 'facebook-jssdk'));
</script>
<div class="fb-like" data-send="false" data-layout="button_count" data-width="450" data-show-faces="false"></div>違いが分かりますでしょうか。div#facebook という要素でスニペットをラップし、最後に . を付け、続く行でインデントした上で、スニペット HTML をそのまま記入しています。
Jade はこのように、HTML をそのまま挿入する事もできるのです。
簡単ですね。
以上が Jade 記法の基本になります。これだけ抑えておけば、HTML を Jade で書くのに困る事はないと思います。
次はテンプレート機能の紹介をしましょう。
Jade はテンプレートエンジンなので、JavaScript の変数によって、出力される HTML の文字列を動的に差し替える事が可能です。例を見ていきましょう。
以下のような JavaScript オブジェクトを定義します:
var package = {
title: '最高にクールなホームページ',
description: '最高にクールなホームページです。見ないと損です。',
keywords: [
'最高',
'クール',
'世界一',
'天才'
],
robots: [
'INDEX',
'FOLLOW',
'NOODP',
'NOYDIR',
'NOARCHIVE'
]
};この package オブジェクトを Jade に渡してやると、Jade テンプレートを以下のように書く事ができます:
!!! 5
html
head
meta(charset='UTF-8')
title= package.title
meta(name='description', content=package.description)
meta(name='keywords', keywords=package.keywords)
meta(name='robots', keywords=package.robots)
body
h1= package.title
p #{package.title} にようこそ。一つずつ見ていきましょう。
title要素の記法がtitle 最高にクールなホームページと先程していたところがtitle= package.titleになっています。descriptionのcontent属性の値の指定がpackage.descriptionとなっており、packageオブジェクトのそれと合致しています。keywordsやrobotsのcontentに渡している値は配列ですが、そのまま渡せるようです。p要素は文字列とpackageオブジェクトの値を#{...}記法を用いて混在して記入しています。
HTML として出力された時は以下のようになります:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>最高にクールなホームページ</title>
<meta name="description" content="最高にクールなホームページです。見ないと損です。">
<meta name="keywords" content="最高,クール,世界一,天才">
<meta name="robots" content="INDEX,FOLLOW,NOODP,NOYDIR,NOARCHIVE">
</head>
<body>
<h1>最高にクールなホームページ</h1>
<p>最高にクールなホームページ にようこそ。</p>
</body>
</html>複数ページで毎回指定する文字情報などは、このテンプレート機能を活用して指定してやると、楽ですね。
テンプレート機能としては、この他に if 文による値の評価と分岐や、for ループによる配列からの <li> 要素生成なども可能ですが、今回は割愛します。
さて、ここからは Jade 独自の記法を紹介したいと思います。ここで紹介する記法を活用すると、HTML の使い回しが格段に楽になります。素晴らしい機能です。
以下のようなページがあったとします。
index.html:
<!DOCTYPE html>
<html xmlns:og="http://ogp.me/ns#" xmlns:fb="https://www.facebook.com/2008/fbml" class="no-js">
<head>
<meta charset="UTF-8">
<title>ソーシャルネットワーク</title>
<meta name="description" content="えすえぬえす!えすえぬえす!えすえぬえす!">
<meta name="keywords" content="SNS,social,Facebook,Twitter,LINE">
<meta name="robots" content="INDEX,FOLLOW,NOODP,NOYDIR,NOARCHIVE">
<!-- Facebook: Open Graph //-->
<meta property="og:title" content="ソーシャルネットワーク">
<meta property="og:type" content="website">
<meta property="og:description" content="えすえぬえす!えすえぬえす!えすえぬえす!">
<meta property="og:url" content="http://example.com/">
<meta property="og:locale" content="ja_JP">
<!-- // Facebook-->
<!-- Twitter: Summary Card //-->
<meta name="twitter:card" content="summary">
<meta name="twitter:url" content="http://example.com/">
<meta name="twitter:title" content="ソーシャルネットワーク">
<meta name="twitter:description" content="えすえぬえす!えすえぬえす!えすえぬえす!">
<!-- // Twitter-->
</head>
<body>
<section id="sns">
<div id="sns-twitter">
<!-- Twitter-->
<div>
<a href="https://twitter.com/share" class="twitter-share-button" data-lang="ja" data-hashtags="highapps">ツイート</a>
<script>!function(d,s,id){var js,fjs=d.getElementsByTagName(s)[0];if(!d.getElementById(id)){js=d.createElement(s);js.id=id;js.src="//platform.twitter.com/widgets.js";fjs.parentNode.insertBefore(js,fjs);}}(document,"script","twitter-wjs");</script>
</div>
<!-- // Twitter-->
</div>
<div id="sns-facebook">
<!-- Facebook //-->
<div>
<div id="fb-root"></div>
<script>
(function(d, s, id) {
var js, fjs = d.getElementsByTagName(s)[0];
if (d.getElementById(id)) return;
js = d.createElement(s); js.id = id;
js.src = "//connect.facebook.net/ja_JP/all.js#xfbml=1";
fjs.parentNode.insertBefore(js, fjs);
}(document, 'script', 'facebook-jssdk'));
</script>
<div class="fb-like" data-send="false" data-layout="button_count" data-width="450" data-show-faces="false"></div>
</div>
<!-- // Facebook-->
</div>
<div id="sns-line">
<!-- LINE //-->
<div>
<script type="text/javascript" src="http://media.line.naver.jp/js/line-button.js" ></script>
<script type="text/javascript">new jp.naver.line.media.LineButton({"pc":false,"lang":"ja","type":"a"});</script>
</div>
<!-- // LINE-->
</div>
</section>
</body>
</html>やたら長い割に、SNS の埋め込みタグしかありませんね。
そして SNS のメタ要素ごとに指定しているタイトル等の文字情報が重複しています。こういった情報は一箇所で管理して、一箇所変更すれば、全てに反映される方が楽ですね。
そもそもこういった定型タグを案件毎に SNS の公式ページを調べて埋め込むのは面倒です。予め用意しておいて、使用する時は include するだけ... など簡単に利用出来る状態にしておきたいところです。ページが複数あれば尚更で、コピペで済むハナシで片付けてしまうと、後で文字修正があった時の労力が大きいですよね。ミスも発生しやすくなります。
さて、これを Jade でゴニョゴニョすると、ページの Jade ファイルは以下のようにできます:
index.jade:
extends _layout
block title
title= package.title
block append meta
include _inc_meta_facebook
include _inc_meta_twitter
block body
section#sns
div#sns-twitter
include _inc_embed_twitter
div#sns-facebook
include _inc_embed_facebook
div#sns-line
include _inc_embed_line脅威的に短かくなりますね。HTML らしさが全然感じられませんか。安心して下さい。順に解説していきます。
そもそも SNS の埋め込みタグはコンテンツとは直接関係のない付加要素です。それだけのために非常に長い HTML を毎ページ用意するというのは、編集のためにページを見る側の労力も計り知れません。
余計なものは極力排除して、コンテンツに集中できる方がミスも減らせるんじゃないでしょうか。
では、このカラクリを紐解いていきましょう。
1 行目 extends _layout とは何でしょうか。
これは Jade のテンプレート継承 の仕組みを使用しています。意味は index.jade は _layout.jade の内容を継承するよーというモノです。
では _layout.jade とはどういった内容でしょうか。以下がその答えです。
_layout.jade:
!!! 5
html.no-js(xmlns:og='http://ogp.me/ns#', xmlns:fb='https://www.facebook.com/2008/fbml')
head
meta(charset='UTF-8')
block title
block meta
meta(name='description', content=package.description)
meta(name='keywords', content=package.keywords)
meta(name='robots', content=package.robots)
body
block body少し見覚えのある Jade 記法になりましたね。
block title という 5 行目の記法に注目して下さい。先程の index.jade にも同じ記法が 2 行目にあり、続く 3 行目にインデントして実際の title= package.title という表記があります。この block で始まる行が _layout.jade と index.jade で相互に対応しているのに気付きますか。
図にすると以下のような関係になります:
つまりは index.jade は _layout.jade の内容を引き継いでいるのです。_layout.jade でページレイアウトの基本構造を定義し、各ページで変化する部分を実際の各ページで定義しているのです。
block 記法はそういったお互いの関係を紐付けるための記法です。
block append についても解説しましょう。_layout.jade の block meta では、続く 6 行目からインデントして meta 要素の定義をしています。そして index.jade 4 行目で block append meta が _layout.jade の block meta を読み込んでいます。
append の違いは 追記が置換かにあります。append とすると継承元の内容に追加する形で内容を引き継ぎます。append がなければ内容が置換されます。
ちなみに prepend というのもあって、これは継承元の内容の前に追記をします。
これが Jade のテンプレート継承という機能です。HTML の構造上の使い回しが効く便利な機能です。
さて、index.jade にはもう一つ見慣れない記法があります。include _inc_meta_facebook という 5 行目の記法は何を示しているのでしょう。
これは Jade のインクルード文 です。SSI という仕組みをご存知の方もいるでしょう。これは Jade における SSI 機能そのものです。SSI で記述するとしたら、以下のようになるでしょう:
<!-- #include virtual="./_inc_meta_facebook.jade" -->これは前述の継承とは対照的に、部分的な Jade のコードを他の Jade ファイルから読み込む形になります。図にすると以下のような関係といえるでしょう:

