Jadeの名前がPugに変わったみたいですね
学生から「Express(Node.js)始めたいっす!」と言われて,ちょろっと説明した後,「Expressならいろいろテンプレートエンジンあるから好きなもん使えばいいと思うよ。サンプルはJadeだったと思うけど」みたいな事を言って別れた数日後,学生から「JadeってPugって名前に変わったみたいですよ!商標登録とかの問題で」と言われたので,Express with Pug の導入に関して書こうと思いました。比率は Express < Pug 。
到達目標
- Node.js をインストールする
- Express をインストールする
- Pug を使用する
- Pug について知る
環境
- Windows10
- Node.js 9.9.1
- npm 4.0.2
そもそも Node.js ってなに?
とりあえず日本語版公式ページがあるので,このトップページ見るのが速いと思う。この前書いたComposerに影響を与えた「npm」というパッケージ管理ツールがついてくる,便利な非同期通信大好きJavaScript環境です。フロント側のJavaScriptとは違い,サーバサイドのプログラミングができるし,ローカルのファイルにもアクセスできるから超お世話になってます。
なお,最近はフロントエンドの開発環境の整備などに超便利なので,サーバサイド書かない人のPCにも結構入っていたりします。
Expressってなに?
Node.js向けの「Webアプリケーションフレームワーク」です。HTMLを楽に生成する様々な「テンプレートエンジン」に対応していて,Expressでサーバサイドを作り,フロントは好きなテンプレートエンジンを利用し記述していくということになります。
HTMLのテンプレートエンジンって?
正直なところ私はHTMLは苦手です。タグの入れ子構造が際限なく続き,読みにくいし,変数使えないしなどなど,めんどくさいが先に立ってしまいます。世の中そんな考えの人が多かったのか,より快適にHTMLを作成しようという動きが出てきました。そんな結果,テンプレートエンジンという存在が生まれました。決められた書き方で作られたファイルがHTMLに変換されます。パターンとしては,
- HTMLに近い書き方のもの
- プログラミング言語に近い書き方のもの
ざっくり言うとこんな感じです。Pugは後者でタグは書かず,インデントでHTML要素をネストしていきます。
まずはNode.jsのインストールから
Node.jsからインストールできます。WindowsならChocolateyといったパッケージマネージャーを使うという選択肢もあります(Macではnodebrewという選択肢もあります。バージョンの管理もできて便利です)が,今回はざっとインストーラーをダウンロードして,どーんとインストールしてやってください。これをインストールするとnpmがついてきます。
コマンドプロンプトで以下のコマンドを実行し,きっちりとインストールされているか確認しておきましょう。もし動かなければ,ちゃんと入っていませんので,全力でリカバリーしましょう。
>node -v
v6.9.1
>npm -v
v4.0.2
npm init でプロジェクトで使用するnpmの初期化
Expressの公式ページに手順は載ってます。
ここからはコマンドプロンプトでnpmと主に戦っていきます。まずは好きなところに自分の作りたいプロジェクトのフォルダを作ってそのフォルダを開きます。
>mkdir test_pug
>cd test_pug
ここでおもむろにnpmを使用します。
>npm init
プログラミングをしていると良く出会う init はinitializeとかの略で初期化するっていう意味です。今回は「npmを利用してプロジェクトの情報を初期化して,依存関係の管理を始めるよ」って意味合いと思っておけばよいでしょう。
先ほどのコマンドを実行すると,初期化の作業が始まります。順に,
- name(プロジェクト名【必須】:URLに変換できないような文字列は拒否されます)
- version(アプリケーションのバージョン【必須】)
- description(説明:「npm search」コマンドで表示されるので簡潔に)
- entry point(起動するファイルの選択)
- test command:(テストコードの指定)
- git repository: (このプロジェクトを管理するGitリポジトリ)
- keywords: (説明:「npm search」コマンドで表示される)
- author: (制作者名)
- license: (このプロジェクトのライセンス)
って感じで項目を聞かれます。それぞれ「()」で閉じられているものはデフォルト値です。EnterキーはデフォルトでYesの設定なのでEnterをポンポン押しているとどんどん初期値で設定されていきます。ただし,押しすぎて設定したかった項目をすっ飛ばさないように注意してください。ちなみにnameの初期値は,initしようとしているディレクトリ名です。
>name: (test_pug)
>version: (1.0.0)
>description: sample: Expless with Pug.
>entry point: (index.js)
>test command:
>git repository:
>keywords:
>author: 自分の名前を設定
>license: (ISC)
こんな感じで,試しにdescriptionとauthorを指定してみた結果,最後に「この情報でいい?」って感じで聞かれるので「大儀である」と労いつつ,Enterキーを押しましょう。フォルダ内に「package.json」が作られますが,まだ何もインストールされません。なお。package.jsonはこのようになりました。
{
"name": "test_pug",
"version": "1.0.0",
"description": "sample: Expless with Pug.",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "zenno04",
"license": "ISC"
}
npm install [package name] --save でインストール
では,Expressのインストールを行います。npm でのインストールも,基本的には管理下に置かれたスペースでのみ使用できるようになります。グローバルにインストールしたい場合は「-g」オプションを使用します。
さて,今回のExpress のインストールは以下のコマンドで行いましょう。
npm install express --save
「--save」オプションを利用してインストールすると,package.jsonに依存関係(このプロジェクトにはこのパッケージが必要だよ情報)が保存されるので,おすすめです。依存関係を保存しておけば,今後はこのpackage.jsonがあるディレクトリ内で「npm install」すると,依存関係を解決するために記録されているパッケージをインストールしてもらえます。
Expressのインストールを確認するためにバージョンを確認しておきましょう。
>express version
今回は「4.14.0」と表示されました。万歳。さらにどんなものがインストールされたのか確認します。
>npm ls
これでずらっとインストールされたものが出てきます。でも,どうやらPugはなさそうです。残念。
ちなみに,npmにももっとたくさんのコマンドが存在します。ぜひ,遊んでみてください。
express-generator でWebアプリケーションの雛形を作る
「Hello Worldのページ」に行けば,公式の Hello World を確認できます。
ただしやっていることが,Expressを呼び出してきて,3000番ポートで起動。ルートに来たら「Hello World」の文字列を投げ返すというだけなので,すっ飛ばします。
Express-apprication-generaterにあるように,Expressでは,コマンドを実行することでWebアプリケーションを作る際の雛形を作成することができます。
Express を使用するかはプロジェクトごとの判断ですが,Expressを使っていたらexpress-generaterを使っての雛形作成は非常にお世話になるので,まずはグローバルにexpress-generaterをインストールします。グローバルな領域へのインストールは「-g」のオプションを付けます。
>npm install express-generator -g
そして,おもむろに
express -h
でExpress のヘルプを出します。
「--pug」 の部分に「add pug engine support」の文字が。先頭にある「ejs」もテンプレートエンジンですが,こちらはHTML的な書き方をするテンプレートエンジンです。.phpファイルの中でHTML使うような感じの書き方になります。ヘルプを参考にして,view(HTMLの画面)をどのテンプレートエンジンで作るかを指定しつつアプリケーションの雛形を作成したいと思います。
express --view=pug [新規作成するWebアプリケーションのディレクトリ名]
私はカレーが食べたかったので,以下のように入力しました。
>express --view=pug curry
dir コマンドで確認すると,「curryディレクトリ」が出来上がりました。curryフォルダに移動し,現在どうなっているかtree コマンドで確認します。
>cd curry
>tree /f
結果はこう。
C:.
│ app.js
│ package.json
│
├─bin
│ www
│
├─public
│ ├─images
│ ├─javascripts
│ └─stylesheets
│ style.css
│
├─routes
│ index.js
│ users.js
│
└─views
error.pug
index.pug
layout.pug
ずらっと情報が出てきて楽しいですが,まだ必要なものがそろっていません。そう,package.jsonがあるということは,ここでもnpmで必要なものをそろえる必要があるのです。curryフォルダの中で改めてインストールを実行します。
>npm install
ここまでくれば,あとはサーバを起動します。
>npm start
で,サーバを実行できます。Expressのデフォルトのポート番号は「3000」です。つまり,接続のためのURLは
http://localhost:3000
となるわけです。接続できることが確認できたらいったん喜びの舞を踊り,「Ctrlキー + Cキー」の後に「Yキー」でReturnです。たーんっ!見事にサーバが停止します。
なお,公式サイトではデバッグ実行しています。アプリの開発時なんかは公式のページに従っておいたほうがいいと思います。
野生のPugが現れた!!!!!!!!!!
というわけで,Expressのルーティングには全く触れず,本日はPugトークを展開します。
Pugのファイルの拡張子は「.pug」です。可愛いですね。
ここからはエディタの出番です。とりあえず,おもむろに作成した「curryフォルダ(適宜自分のフォルダ名に読み替えてください)」の中の,「views」フォルダ内に .pug ファイルが3つ並んでいます。可愛さ3倍です。
さて,この3つのファイルを開いて内容を確認していきましょう。
まず「layout.pug」を見てみるとみんな大好き「html」の文字が目に飛び込んできます。HTMLファイルもタグをネストした際はインデントを下げて階層構造を表現しますが,タグがなくなっただけでやってることは同じです。閉じタグがないことを気持ち悪いと思った時期もありましたが,すぐに慣れます。ほーら怖くないよ。噛みつかないよ。
doctype html
html
head
title= title
link(rel='stylesheet', href='/stylesheets/style.css')
body
block content
このファイルはDOCTYPE宣言をしてhtmlタグを開始,html内にはheadタグが存在し,titleタグは「title」という情報を表示します。この
title= title
という書き方は変数titleの値をtitleタグの情報として代入しているようなものです。そしてcssへのリンクがあり,bodyタグが始まります。headタグと同じ階層にあるということを表現するためにbodyタグのインデントは1段戻します。非常にシンプルでわかりやすいです。
なお,pugにおいてはidやclassの指定はcssと同じ表現でOKです。linkの()の中は属性の指定です。カンマで区切って記述していきます。そして今まさに気付いたのですが,QiitaのMarkDown,コードの言語の指定でpugは効かないんですね。とりあえず,jadeを指定してお茶を濁しておきます。
"block content" という謎の表現と継承
layout.pug において,HTMLのタグには存在しない「block content」というものが最後に出現しています。それ以降に記述はありません。これがpugの素敵ポイント,継承の概念です。extends(継承)の概念を持つことによって,pugはHTMLを拡張することができます。例えば共通のヘッダー部分のみを作成し,その後,必要な時に必要なところだけ足していけます。今回作成された「index.pug」と「error.pug」は素材となる「layout.pug」を拡張して使います。その確認のために,まずはどのようにpugファイルが読み込まれるかを確認しましょう。
そもそもPugはPugの形式で書かれたコードをコンパイルして.htmlの文字列を生成するようになっています。この裏側の処理はExpressに隠されて我々からは普段見えないところで動いていますが,Node.jsであれば,Expressと関係ないプロジェクトでPugのみを使用することも可能です。ちなみに,公式ページに載ってます。
では拡張の処理を見に行く前に,もう一個見ておくべきファイルがあります。「routesフォルダ」内のindex.jsです。ここに行きつく処理としては,app.jsから処理が始まり,ユーザのHTTPリクエストを分割して「/(ルート:要はトップページへのアクセス)」であるならば,
app.use('/', index);
の記述のおかげで,「routes/index.js」が動くからです。この中での注目は,「/」へのアクセスであれば,
router.get('/', function(req, res, next) {
res.render('index', { title: 'Express' });
});
が起動し,「views/index.pug に Express という文字列を title というキーで渡して,index.html に変換して送り返せ」とかいう感じで,views/index.pug が呼び出されるわけです。どうして「index」という指定だけで views/index.pug が選択されるかはExpressのお話なので今回は割愛。言いたかったことは,index.pug が呼ばれているのだということです。今までのHTMLオンリーの考え方は,別途作った.htmlファイルを他のHTMLに読み込んできて表示する。という考え方でしたが,Pugは埋め込むのではなく,そもそも作成しておいた共通部分を拡張してページとして表示したいファイルを作成します。要は積み木のように読み込みたいファイルを作成してお区感じです。では今度は「index.pug」を見てみます。
extends layout
block content
h1= title
p Welcome to #{title}
先頭行で「extends layout」という記載があります。これで,このファイルはlayout.pugを拡張していきますよという表現になります。
そして,layout.pug に記載されていた謎ワード,「block content」が再び出現します。ちなみに「error.pug」も似たような内容です。
キーワードは「block」です。blockの後にblockの名前を設定し,「extends 〇〇」としている側でその詳細を記載します。extendsされる側のファイルでは,blockキーワードとその名前でこの後に何がつながるかを指定するわけです。どちらかの「content」を「conten」に変更すると,コンパイルが通らなくなります。Pugはコンパイル時に継承関係を解決して1つのHTMLファイルを作成します。どちらも「conten」にすればコンパイルが通ります。つまり,適切な名前のblockを作成して,きれいに管理しましょうねってことです。では,Pugのblockで遊んでみましょう。
curry.pugを増やして,それぞれのファイルの内容を変更します。
extends layout
block curry
p#message.poor-writing 今日の晩御飯は...
block content
p 全然辛くなかった。
doctype html
html
head
title= title
link(rel='stylesheet', href='/stylesheets/style.css')
body
block curry
extends curry
block content
h1= title
p 業務スーパーで買った辛口の #{title}
素敵なところは,curry.pugのように真ん中の一部をcontent.pugで実装するなんてことができる点。後ろに付け足すしかできなかったら不便でしょうがないけど,ヘッダーとフッターを定義しておいてコンテンツだけ各ページで違うなんて時も楽勝ですね。
改めて
npm start
ってして見事に画面が表示できれば完璧です。念のためどのようなHTMLに変換されたかは以下の通り。
<!DOCTYPE html>
<html>
<head>
<title>Express</title>
<link rel="stylesheet" href="/stylesheets/style.css">
</head>
<body>
<p class="poor-writing" id="message">今日の晩御飯は...</p>
<h1>Express</h1>
<p>業務スーパーで買った辛口の Express</p>
<p>全然辛くなかった。</p>
</body>
</html>
嬉しいのは「block」で指定したものがdivなどの要素として変化されるわけではないという点。書いたものがそのまま埋め込まれるので,変にdivが増えるなんてこともありません。JadeからPugに変わって,よりシンプルに書けるところも増えたし。大満足です。ただし,この変更をしてしまうと,
ただし,晩御飯はExpressといわれても,困るので,ここを何とかしましょう。
2種類のコメント 「//」と「//-」
コードの修正などでコメントを使いことも多々あると思われるので,先にコメントについてお伝えしておきます。
コメントは2種類で概要は以下の通り。
- 「//」Pugでコンパイルされた際にHTMLのコメント「」になる
- 「//-」Pugそのもののコメントで,コンパイルされた際にHTMLとして何も出力しない
PugはHTMLを生成することを目的にしているので多くの言語でコメント記述時に指定する「//」を「HTMLのコメントを生成する」という発送は個人的には非常に好印象です。
1つ気を付けないことがコメントにはあり,「//-」は問題ないのですが,今回のindex.pugのように,contentブロックを作成した際に,ブロックの外側(インデントを戻した状態)で「//」コメントを書くと,curryブロックに差し込まれる設定になっていないHTMLのコメントが生成されることになりコンパイルが通らなくなります。
PugはJavaScriptも書ける
そもそも,Pugの中にはコードが書けます。HTMLにもscriptタグがあるのですが,PugではPugでforループを使用し,liタグを複数記述するなんてことが可能です。また,そのループ時の記述に必要なデータも変数として宣言することができるので,そのあたりを見ていきましょう。公式のドキュメントでは「Unbuffered code」と表現されています。まずは配列の宣言です。index.pugを変更してサーバを立ち上げなおします。
extends curry
block content
- var tastes = ['甘口', '中辛', '辛口', '激辛']
h1= title
p 業務スーパーで買った#{tastes[1]}の#{title}
// test
//- test2
画面の表記が中辛になったら大成功。Pug内での「Unbufferd code」の使用は,先頭に「- 」を付けます。これもコメントと同じく,block内に変数宣言をしなければコンパイルが通りません。
では特に意味はないですが,辛さ一覧をループを使って表示してみます。
extends curry
block content
- var tastes = ['甘口', '中辛', '辛口', '激辛']
h1= title
p 業務スーパーで買った#{tastes[1]}の#{title}
// 特に意味はないけど辛さを一覧表示してみる
ul
- for (var i = 0; i < tastes.length; i++)
li= tastes[i]
よし,この場所に辛さ一覧があることに違和感しかありませんが表示はされましたね。みんな大好きforループですが,このような一覧表示の場合は each のほうが便利な気がするので,再度書き換えます。ついでに,liタグの中にaタグを入れるようにしておきましょう。
extends curry
block content
- var tastes = ['甘口', '中辛', '辛口', '激辛']
h1= title
p 業務スーパーで買った#{tastes[1]}の#{title}
// 特に意味はないけど辛さを一覧表示してみる
ul
each taste in tastes
li: a(href='#')= taste
びっくりするところは,each には「- 」がいらないところ。シンプル。でも,ループした後のliのとこなんかはインデントを下げる必要があるということを忘れないようにしなきゃいけません。また,今回のサンプルで「li:」としましたが,Pugでは改行せずにタグを記載したい場合は「:」でつなぎます。
先ほど,「Unbufferd code」について少し記述しましたが,評価後のJavaScript(Bufferd code)は「= 」で記述します。両者の違いは,アウトプットされるかどうかです。今までさんざんタグの中身を「=」で出力していましたが,この仕組みが生かされているわけです。
- p 文字列でござる
- p= '文字列' + 'でござる'
文字列連結で試してみましょう。
extends curry
block content
- var tastes = ['甘口', '中辛', '辛口', '激辛']
h1= title
p 業務スーパーで買った#{tastes[1]}の#{title}
// 特に意味はないけど辛さを一覧表示してみる
ul
each taste in tastes
li: a(href='#')= taste
p 文字列でござる
p= 'やっぱり' + '文字列でござる'
p- 'こっちは' + '出力されないでござる'
該当部分のソースを表示すると,以下のようになります
<p>文字列でござる</p>
<p>やっぱり文字列でござる</p>
<p></p>
pタグは3つともPugによって作成されますが,その中身が「- 」のものは表示されていません。このような感じで,HTMLの生成に関する制御と,HTMLとして表示するものの制御を,同じJavaScriptによっていい感じに処理しています。超素敵です。
サーバの処理結果をViewにお届けする
ここまで長々とPugに関して書いてきました。もっと素敵な機能とかいっぱいあるのですが,この記事はこの辺でけりを付けたいと思います。
ExpressはWebアプリケーションのフレームワークです。PugはHTMLを生成するためのテンプレートエンジンです。つまり,ユーザに何かを提供するのであれば,両者の間でデータを受け渡しする仕組みが何よりも重要です。その仕組みが,一度軽く触れた以下のコードになります。
router.get('/', function(req, res, next) {
res.render('index', { title: 'Express' });
});
titleというキーでExpressからPugにデータを渡し,そのデータをもとにPugがワンワン吠えながらHTMLを作ってくれるわけです。微笑ましいですね。
というわけで,最後に「routes/index.js」とその他諸々,コードを修正していきたいと思います。結構変更しちゃった。
var express = require('express');
var router = express.Router();
/* GET home page. */
router.get('/', function(req, res, next) {
res.render('index', { title: 'Curry', taste: 2, message: '全然辛くなかった' });
});
module.exports = router;
extends layout
block curry
p#message.poor-writing 今日の晩御飯は...
- var tastes = ['甘口', '中辛', '辛口', '激辛']
block content
p= message
p ちなみに,カレーの辛さは以下の4つ
ul
each taste in tastes
li: a(href='#')= taste
p 今回選択した辛さは#{taste}でした。
extends curry
block content
h1= title
p 業務スーパーで買った#{tastes[taste]}の#{title}
感動的なのはextensしたファイルの中で定義した配列にアクセスできるところ。Expressから送られてきたキーとeachで利用する変数名が同じでもきれいに動いていること。
このあたりがとても素敵だなと思います。Pug可愛い。
このほかにも,条件分岐ができたりMixinがあったりするのですが,そのあたりの機能は機会があればまた後日に記事にできればと思っています。
結論
- express-generator使っておけばいいと思う
- HTMLのテンプレートエンジン使おうが,HTMLの知識は必要
- Pug可愛い
心に誓ったこと
- 授業のどっかでNode.jsやりたい
- Pugはじめ,便利グッズは使っていく
- どっかでHTMLテンプレートエンジン自作したい。
それでは皆様,お疲れさまでした。