こんにちは。今回は現実逃避を兼ねて 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"></script>
</body>
</html>
さてではこれを Jade で書くとどうなるでしょう:
doctype html
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>
宣言はdoctype html
で置換します。- 先程紹介したように、要素の
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 テンプレートを以下のように書く事ができます:
doctype html
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= 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
:
doctype html
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=description)
meta(name='keywords', content=keywords)
meta(name='robots', content=robots)
body
block body
少し見覚えのある Jade 記法になりましたね。
block title
という 5 行目の記法に注目して下さい。先程の index.jade
にも同じ記法が 2 行目にあり、続く 3 行目にインデントして実際の title= 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 ファイルから読み込む形になります。図にすると以下のような関係といえるでしょう:
index.jade
がそれぞれの _inc_*.jade
ファイルを include
文の箇所でインクルードしている構図が分かるでしょうか。
_inc_meta_facebook.jade
の中身をのぞいてみましょう。
_inc_meta_facebook.jade
:
// Facebook: Open Graph //
meta(property='og:title', content=title)
meta(property='og:type', content='website')
meta(property='og:description', content=description)
meta(property='og:url', content=homepage)
meta(property='og:locale', content='ja_JP')
// // Facebook
おっと //
記法について説明していませんでしたね。でもお察しの通り、これはコメント行を表わす記法です。中身は Facebook Open Graph の meta
要素がならんでいますね。
この手の <meta>
要素を、毎回ページ毎に書くのは非常に冗長でつまらない作業です。面倒な事はモチベーションも下がりますし、ミスが発生しやすくなります。
しかし、こうしてインクルード用のパーツ化しておけば、別プロジェクトでの使い回しも容易ですし、複数のページに同じ <meta>
タグを書く作業も、この _inc_meta_facebook.jade
をインクルードしてやるだけでよくなります。
index.jade
ではこの方法で、Facebook と Twitter の <meta>
タグと、Facebook、Twitter、LINE の埋め込みタグをインクルードしているのが理解できるでしょう。
これで無駄な作業と無駄に長いコードを省略できますね。Jade って素晴らしい...///
以上がコンポーネント化の紹介です。少し難しいでしょうか。しかし HTML のパーツ化は SSI の利用をしていればその利便性は既にご存知のはずです。Jade ではこのパーツ化をより構造的に実践できます。うまく活用すれば、分業や HTML の再利用性がぐっと向上するはずです!
さて Jade の基本を通して Jade の素晴らしさをお伝えしたのですが、まだ腑に落ちない方もいるのではないでしょうか。するどい貴方はこんな疑問を持つはずです:
SSI でパーツ化ならできているのに、わざわざ新しい記法を覚えてまで Jade を使う必要性ってあるのかな。
その通りですね。既にあるものをわざわざ別のものに置き換える必要があるのでしょうか。
もちろん、あります。それは Jade を利用する事で、SSI の欠点を補える事です。それでは SSI の欠点とは何でしょうか。
こんなものが挙げられると思います:
- サーバー実装 (Apache や Nginx 等) に依存するため、確認にはこれらのサーバー環境が必要 (= ローカルで手軽に確認できない)。
- SSI はテンプレートエンジンではないので、インクルードするパーツの文字列に変数は使用できない。
- インクルードはできるが、継承のような構造的な使い回しはできない。
Jade であれば、これら全てが可能な上、閉じタグを書く必要もありません。つまり Jade を使えば;
- サーバー実装に依存せず、簡単にローカルで確認ができ、
- テンプレートエンジンである利点を生かし、インクルードパーツの文字列にも変数を使用し、
- インクルードに限らず、継承を利用して構造的な使い回しも可能!
と言った感じで、Jade を学ぶメリットはあるのではないでしょうか。
さて Jade のメリットを確認したところで、実践のための環境を用意しましょう。これは Grunt を使えば簡単に用意できます。
Grunt は Node.js 界隈で定番のビルドツールです。Node.js と NPM が導入済の環境で、Grunt を使用した事がない場合は、以下のコマンドでインストールしてすぐに使い始められます:
npm install -g grunt-cli
今回は Grunt 導入方法は割愛しますが、便利なので使い方を覚えておく事をおすすめします。
さて Grunt が使用可能になったら、Jade 用のプロジェクトサンプルをダウンロードしましょう。以下のリンクからダウンロードして下さい:
Git が使える環境でしたら以下のコマンドで grunt-jade-sample
ディレクトリが作成されファイルがダウンロードされます:
git clone git://github.com/japboy/grunt-jade-sample.git
つづいて grunt-jade-sample
ディレクトリ内で以下のコマンドを実行して必要な Node.js モジュールをダウンロードします:
npm install
これで下図のようなファイル構成が展開されるはずです:
確認できたら grunt-jade-sample
の中で以下のコマンドを実行してみましょう。
grunt clean
上記のコマンドを実行すると dist
というディレクトリが削除されると思います。消えてしまいますが、心配は入りません。次のコマンドを実行しましょう:
grunt
そうすると Grunt が src
ディレクトリ内の *.jade
ファイルをコンパイルして dist
ディレクトリの中に index.html
というファイルを作成します。ついでに src/css
の中身も dist
にコピーしてくれます。
src
の中の _
から始まる *.jade
ファイルは dist
にコピーされません。何故ならこれらのファイルは前項で説明したように、継承やインクルードによって index.html
を構成するためのパーツだからです。このサンプルでは _
から始まる *.jade
ファイルは Jade が処理するパーツとして判断して、dist
にはコピーされないよう設定しています。
それでは次に以下のコマンドを実行してみましょう:
grunt watch
上記コマンドは grunt-jade-sample
ディレクトリ内のファイルの変更監視を開始するコマンドです。同時にローカルサーバーも立ち上がります。以下の URL に web ブラウザでアクセスしてみて下さい:
http://localhost:50000/
「最高にクールなホームページ」が表示されましたでしょうか。
試しにこの状態で、src
ディレクトリ内の index.jade
を編集してみましょう。
p #{title} にようこそ。
index.jade
に上記のような行を見つけたら;
p Welcome to #{title}
と書き換えて保存してみましょう。
先程開いた http://localhost:5000/
のページが更新され;
最高にクールなホームページ へようこそ。
という表記が;
Welcome to 最高にクールなホームページ
に変化しているはずです。
このように、Grunt 等と合わせて Jade を使用してやる事で、Jade の編集内容がリアルタイムで HTML に変換され、ローカル環境で即座に確認ができます。
その他にも、この例では HTML の description
や keywords
等の <meta>
要素の値を Jade のテンプレート機能を用いて、Grunt から変数として渡しています。
そしてこれら変数の元の値は、package.json
という Node.js でプロジェクト単位のファイルを管理する時の JSON ファイルから取得しています。
こうする事で PHP のようなテンプレート言語を用いる事なく、複数の HTML ページで共通して指定する値を一箇所で管理する事も可能になります。
いかかでしょう。Jade の素晴らしさが伝わったでしょうか。
Jade は素晴らしい言語だと思います。過去に存在した優秀な言語やエンジンの良い部分を継承し、一つにまとめ、今も尚、改良が続けられています。
もちろん日進月歩の技術の中で、Jade が一生モノのツールだとは言いません。しかし、Grunt を始めとする Node.js プラットフォームの急速な成長と普及の中で、今 web 開発をする上では、とても便利なツールの一つである事は間違いないと思います。
Jade を使う事で、その特徴であるテンプレート継承やインクルードを活用する機会ができるでしょう。この中で HTML ページをコンポーネントとしてとらえ、再利用性を意識するコーディングというものができる体制が作れるかも知れません。そうなれば、そこで培った考え方は、今後の技術力として普遍的な価値を持つと思います。
個人的には、フロントエンド JavaScript における Backbone.js と同じく、Jade は HTML において、普遍的価値のある考え方でコーディングを実践できる枠組みを提供してくれるツールだと思います。
Jade が流行れば良いと思います。