ページを量産したりチームでサイトを作る時pugという言語を使ってWebサイトを作っています。pug(旧:Jade)の特徴やメリット、基本的な使い方、導入方法(pug-cliとgulp)についてまとめました。とくに記述方法はif(分岐)やfor・each(ループ処理)など解説。テンプレート化したい場合に使う記述方法の解説と具体的なテンプレートの作り方なども紹介しています。
普段webpackやgulpを使っている方なら導入のハードルも低いと思います。
今回はmpmコマンド出始めたい方はpackage.jsonで手軽にすぐ始められます!とても記事は長いので目次を利用して好きなところを読んでください。
かみーゆ/フロントエンドエンジニア
pugとは?
pugとは、Haml(HTMLを抽象化したマークアップ言語)記法に影響を受けたJavaScriptで実装された高機能テンプレートエンジンです。
より短く、キレイかつ簡潔にコードを書くことができます。
The general rendering process of pug is simple. pug.compile() will compile the pug source code into a JavaScript function that takes a data object (called “locals”) as an argument. Call that resultant function with your data, and voilà!, it will return a string of HTML rendered with your data.
The compiled function can be re-used, and called with different sets of data.
公式サイト:pug
昔は、*Jade(ヒスイ)*と呼ばれていましたが、すでに商標登録されていたのでpugに名前が変更されました(Version 2以降)。
似たようなテンプレートエンジンでEJSもあります。参考にしてください。
私の考えるpugでコーディングするデメリットとメリットを紹介します。
導入のメリット
- 大量のページを手分けして量産できる
- 短いコードでキレイかつ簡潔にコードが書ける
- ヘッダーや共通部分とそれ以外を分けて書ける
こちらは私が作っているサイトの実際のpugファイルです。footerやheaderなどを分けてデフォルトで読み込み、主要コンテンツだけ毎度変更しています。
導入のデメリット
- 実務で使うためにはgulpやwebpackのタスクを書いたりコマンドを覚える必要がある
- インデントをミスると意図しないコンパイルが起きる
- コードの書き方など多少の学習コストがかかる
こちらは私が実際に利用しているLaravel mixのwebpackのコードです。タスクを書いたりしないといけないのは少し面倒ですね。
導入方法
pugはnpm経由でインストール可能です。
npmのモジュールを管理できるようにpackage.jsonを作っておきます。
今回はpug-cli(npmスクリプト)とgulpでの導入方法をご紹介します。
メリット | デメリット | |
---|---|---|
gulp | SCSSなどとあわせて複雑なタスクが書ける | モジュールを複数インストールしなければならないことがある。複雑なコードを書く場合がある |
pug-cli | 記述量が少なくてすむ。 | 複雑なタスクには不向き |
HTMLをコンパイルするだけなら pug-cli で十分です。
package.jsonを作成し、npmモジュールを管理しながら、進めていきます。
今回は pug-practice というディレクトリを作り、ディレクトリに移動して以下コマンドを実行します。VS Codeであればディレクトリを開いて、ターミナルを開いて実行すればOKです。
control + shift + @
で開くことができます。npm init
# OR
npm init -y
通常プロジェクト名など、対話式で入力し作成していくのですが-y
のオプションを使うと面倒な入力を省くことができます。
{
"name": "pug-practice",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"pug": "pug ./src --hierarchy -o ./dist -P"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
}
}
package.json ファイルが作成できました。
pug-cliで始める
次にpugをインストールします。インストールでコケる場合はsudo(管理者権限で実行)をつけてみてください。
npm install pug pug-cli -D
pug
とpug-cli
がインストールされます。
"pug": "^3.0.2"
"pug-cli": "^1.0.0-alpha6"
{
"name": "pug-practice",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"pug": "^3.0.2",
"pug-cli": "^1.0.0-alpha6"
}
}
src
と dist
ディレクトリを作成し、空の index.pug
を追加しておきます。
pug-practice/
├ node_modules/
├ package.json
├ src/
| └ index.pug(追加)
└ dist/
src
ディレクトリにある index.pug
を index.html
として dist
ディレクトリに出力します。
pug ./src --hierarchy -o ./dist -P -w
毎度、コマンドを打つのは面倒なので、scriptとして登録しておきます。
{
"name": "pug-practice",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"pug-w": "pug ./src --hierarchy -o ./dist -P -w"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"pug": "^3.0.2",
"pug-cli": "^1.0.0-alpha6"
}
}
すると以下コマンドで実行できるようになります。
npm run pug-w
src
ディレクトリ内のpugファイルを更新するとsrc
ディレクトリ内にHTMLファイルがコンパイルされます。-w
オプションが付いているのでリレンダー(再コンパイル)されます。
※scriptのオプションは記事の最後にあります。
Macであれば Control+C
で実行を抜けることができます。
Gulpで始める
gulp
と gulp-pug
をインストールします。ディレクトリを作成し npm init -y
などで packge.json ファイルを作成した状態からスタートします。pug-cli
と同じディレクトリ名を使っていますが、別々に試したい場合は名前を変えてください。
ライブリロードさせたいので browser-sync
エラーを吐いてもgulpを止めないためにgulp-plumber
をあわせてインストールします。
npm install gulp-pug gulp browser-sync gulp-plumber -D
{
"name": "pug-practice",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"browser-sync": "^2.27.10",
"gulp-plumber": "^1.2.1",
"gulp": "^4.0.2",
"gulp-pug": "^5.0.0"
}
}
src
と dist
ディレクトリを作成し、空の index.pug
を追加しておきます。タスクを記述する gulpfile.js を追加します。
pug-practice/
├ node_modules/
├ package.json
├ gulpfile.js(追加)
├ src/
| └ index.pug(追加)
└ dist/
タスクを以下の通り書きます。
const { src, dest, series, parallel, watch } = require("gulp");
const pug = require('gulp-pug');
const plumber = require("gulp-plumber");
const bs= require("browser-sync");
// browserSync
function bsInit(done) {
bs.init({
server: {
baseDir: "./dist/",
},
reloadDelay: 1000,
open: false,
});
done();
}
// pug
function html(done) {
src('./src/*.pug')
.pipe(plumber())
.pipe(pug({pretty: true}))
.pipe(dest('./dist'))
.pipe(bs.stream())
done();
}
// watch
function watchTask(done) {
watch('./src/*.pug', html);
done();
}
exports.default = series(parallel(bsInit,html),watchTask);
npmスクリプトを変更します。
"scripts": {
"start": "gulp"
},
npm start
基本のコードのご紹介
基本のpugの記述方法です。
基本中の基本・単純なタグと文字列を出力したい場合
pugの記法はタグ名+スペース+コンテンツです。閉じタグは不要です。
p hello,pug!
これでファイルはコンパイルできましたね!!
<p>hello,pug!</p>
htmlを直に書くことができます。
div
<p>hello,pug!</p>
<div><p>hello,pug!</p></div>
=(イコール) でタグ内にコンテンツを内包させることもできます。
p='こんにちは。IT戦士のかみーゆです。'
<p>こんにちは。IT戦士のかみーゆです。</p>
エスケープ処理
出力内容をエスケープしたくないことがありますよね? そんなときは!(エクスクラメーション・マーク)を使うとそのまま出力されます。
p='見出し1は<h1>タグで囲みます'
p!='見出し1は<h1>タグで囲みます'
<p>見出し1は<h1>で囲みます。</p>
<p>見出し1は<h1>で囲みます。</p>
コメント
コメントをpug上には残して、htmlには反映したくない場合は */(スラッシュ)2つ+-(ハイフン)*を文前に追加。
//- ここにコメントをかく
htmlのコメントとして残したい場合は /(スラッシュ)2つです。
// ここにコメントをかく
入れ子(ネスト)構造を書きたい場合
入れ子にしたい場合はインデント(字下げ)を使います。 以下のような書き方をします。
header
h1 hello, pug!
<header>
<h1>hello, pug!</h1>
</header>
もしくは*:(コロン)+スペース*を使います。
div: p test
<div>
<p>test</p>
</div>
idとclassの出力
idは#
、classは.
でつなぐ。
section#hoge
div.piyo
<section id="hoge"></section>
<div class="piyo"></div>
またidやclassを使うとき、divは省略できます。
.piyo
srcやhlefなどの属性の出力方法
属性は()内に属性名=値で記載できます。複数あるときは、,(カンマ)かスペースで区切ります。
img(src="image.jpg", alt="画像", width="300", height="200")
a(href="./" target="_blank") トップページ
<img src="image.jpg" alt="画像" width="300" height="200"/>
<a href="./" target="_blank">トップページ</a>
id、クラスも同様にカッコ内にも書けます。
section(id="hoge")
div(class="piyo")
マルチラインで書くこともできます。
input(
type='checkbox'
name='agreement'
checked
)
<input type="checkbox" name="agreement" checked="checked"/>
`(バッククォート) を使えば、属性の値を複数行にまたいで書くこともできる。
input(data-json=`
{
"very-long": "piece of ",
"data": true
}
`)
<input data-json="
{
"very-long": "piece of ",
"data": true
}
" />
さらに&attributes()
を使って、後から属性を連想配列として追加できます。
a(href="/")&attributes({'style':'color:white','target':'_blank'}) リンク
<a href="/" style="color:white;" target="_blank">リンク</a>
改行追加
インライン扱いされるようなタグは改行されずコンパイル時美しく整形されません。 改行を加えたい場合は |(パイプ)2つを使います。
a(href="/") HOME
|
|
a(href="/blog") Blog
<a href="/">HOME</a>
<a href="/blog">Blog</a>
文字列の中にタグ
文字列の中に文字を入れる方法が2つあります。 1つは *|(パイプ)*を使う方法です。
p
| これは
strong テキスト
| です
もう一つは#[]
で囲む方法です。
p これは#[strong テキスト]です
前述したものに比べてこちらの方がはるかにカンタンなのでよく使います。
<p>これは<strong>テキスト</strong>です</p>
JSやstyleを追加したい場合
headタグ内やbody内にJSやCSSのコードを追加したいときは以下のように書きます。
style.
a {
color: #333;
background: #aaa;
}
script.
let pug = 'Hello, pug!!!';
console.log(pug);
<style>
a {
color: #333;
background: #aaa;
}
</style>
<script>
let pug = 'Hello, pug!!!';
console.log(pug);
</script>
プログラミング的記述方法
分岐や変数、ループの書き方です。
ちょっと難しくなりますが、ムリに使わなくてもpugは十分書けます。
変数
pugでは変数が使えます。何気に便利。コンテンツとして出力したいときは #{}
の中に記入します。
- var name = "aaa"
p #{name}
<p>aaa</p>
属性の中に変数を入れるときはこんな感じで書きます。
//- 変数のみ
input(type="text" value=name)
//- 変数 + 文字列
input(type="text" value=name + 'です')
文字列と混在させるときは変数を ${}
内に記入し `(バッククオート) で囲むと前述したものより可読性が上がります。
//- 変数 + 文字列
input(type="text" value=`${name}です`)
<input type="text" value="aaa"/>
<input type="text" value="aaaです"/>
三項演算子で出力を分けることもできます。
- var toppage = true
h1(class=toppage ? 'is-top' : 'is-lower') 見出し
<h1 class="is-top">見出し</h1>
そのまま変数を出力したい場合は \(バックスラッシュ) を使用します。
p \#{pug}は変数です。
<p>#{pug}は変数です。</p>
&attributes()
を使えば、属性を配列に渡して値を追加可能です。
- var attributes = {};
- attributes.target = '_blank';
a(href="/")&attributes(attributes) リンク
<a href="/" target="_blank">リンク</a>
ifやcaseなどの分岐処理
if文 です。ヘッダーロゴのリンクをつけるつけないなどの判定によく使います。
- var pageId = 'home'
- var pageName = 'my site'
if pageId == 'home'
h1 #{pageName}
else
p: a(href="/") #{pageName}
<h1>my site</h1>
いわゆるswhich文のような書き方もできます。
- var pageTemplate = 'contact'
case pageTemplate
when 'contact'
section.contact
when 'top'
section.top
default
section
<section class="contact"></section>
breakで抜けることもできます。
default
- break
:(コロン) を使うとまとめて書けます。
- var pageTemplate = 'contact'
case pageTemplate
when 'contact': section.contact
when 'top': section.top
default: section
for、each、while文などのループ処理
ループもできます。
まずは for文 から。
ul
- for (var i = 0; i <= 2; i++) {
li.item リスト0#{i}
- }
<ul>
<li class="item">リスト00</li>
<li class="item">リスト01</li>
<li class="item">リスト02</li>
<li class="item">リスト03</li>
</ul>
while文 も使えます。出力結果は先ほどの for文 と一緒です。
- var n = 0;
ul
while n < 3
li.item リスト0#{n++}
each文はより実用的に使えると思います。
ul
each val in [1, 2, 3]
li = val
<ul>
<li>1</li>
<li>2</li>
<li>3</li>
</ul>
インデックスも合わせて利用する場合。
ul
each val, index in ['cat', 'dog', 'rabbit']
li= index + ': ' + val
<ul>
<li>0: 猫</li>
<li>1: 犬</li>
<li>2: うさぎ</li>
</ul>
連想配列パターン。共通の変数として宣言さえしておけばメニューなんかにめっちゃ使える。
三項演算子と組み合わせると、クラスに現在のページというクラスも追加できます。
- var menu = {'cat': '猫', 'dog': '犬', 'rabbit': 'うさぎ'}
- pageId = 'cat'
ul
each val, key in menu
li(class=key == pageId ? 'is-current' : '' ): a(href=`/${key}`)=val
<ul>
<li><a href="/cat">猫</a></li>
<li><a href="/dog">犬</a></li>
<li><a href="/rabbit">うさぎ</a></li>
</ul>
pugはそもそもJSのメソッドが使えるので、値を大文字(アッパーケース)化してそのままタイトルに使う方法もあります。
ul
each val in ['cat', 'dog', 'rabbit']
li: a(href=`/${val}`)=val.toUpperCase()
<ul>
<li><a href="/cat">CAT</a></li>
<li><a href="/dog">DOG</a></li>
<li><a href="/rabbit">RABBIT</a></li>
</ul>
mixin
再利用したいときはmixinに登録しておきましょう。
mixin list
ul
li CAT
li DOG
li RABBIT
+list
<ul>
<li>CAT</li>
<li>DOG</li>
<li>RABBIT</li>
</ul>
引数を使うパターン。下層ページのタイトルやパンくずで使えそう。
mixin pageHeader(pageId, pageName)
header(class=pageId)
//- toUpperCase()でローマ字大文字に
p #{pageId.toUpperCase()}
h1 #{pageName}
- var pageId = 'service'
- var pageName = '私たちのサービス'
//- 引数のデフォルトが出力される
+ pageHeader
//- 値を変更可能
+ pageHeader(pageId, pageName)
<header class="service">
<p>SERVICE</p>
<h1>私たちのサービス</h1>
</header>
引数にはデフォルトも設定しておけます。
mixin pageHeader(pageId='top', pageName='トップページ')
出力結果です。
<header class="top">
<p>TOP</p>
<h1>トップページ</h1>
</header>
スプレッド構文を利用するパターン
mixin list(id, ...items)
ul(id=id)
each item in items
li= item
+list('my-list', 1, 2, 3, 4)
<ul id="my-list">
<li>1</li>
<li>2</li>
<li>3</li>
<li>4</li>
</ul>
function
Mixin 以外にも function (関数)が使えます。たとえば、条件によってクラスをつけたい等は以下のような書き方も可能です。
-
var target = 1;
var items = [1, 2, 3, 4];
function targetNum(i){
if( i === target )
return "current";
else
return "";
}
ul
each item in items
li(class=targetNum(item)) #{item}
<ul>
<li class="current">1</li>
<li>2</li>
<li>3</li>
<li>4</li>
</ul>
ページによって、メニューにクラスをつけてスタイルを変えたいときに便利ですね。
pugでテンプレート化してみよう
ファイルを分けて、テンプレート化していきます。
pug-cliの注意点
このままではコンパイル時に、_(アンダースコア) がついたファイルもコンパイルされてしまうので、pug-cliを「GitHub – pugjs/pug-cli: pug’s CLI interface」から インストールし直す 必要があります。
npm i github:pugjs/pug-cli#master -D
参照:pug-cliで_(アンダースコア)がついたファイルやディレクトもコンパイルされてしまう問題を解決する
{
"name": "pug-practice",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"pug-w": "pug ./src --hierarchy -o ./dist -P -w"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"pug": "^3.0.2",
"pug-cli": "github:pugjs/pug-cli#master"
}
}
gulpの注意点
gulpfileを一部書き換えます。
function html(done) {
src(['src/**/*.pug', '!src/**/_*.pug'])
.pipe(plumber())
.pipe(pug({pretty: true}))
.pipe(dest('./dist'))
.pipe(bs.stream())
done();
}
function watchTask(done) {
watch('./src/**/*.pug', html);
done();
}
include・ファイルの読み込み
ファイルを読み込みたい場合は include+ペース+ファイルパスです。
拡張子(.pug)は省略できます。
include _inc/_header
extend・継承
extendとblockを使って元となるファイルを継承することもできます。 たとえば、_layout.pugというテンプレートを使いたい場合は次のような書き方になります。
extend _layout
append・独自の設定
ページごとに独自の設定をもたせたいときはappendを使います。
//- _layout.pug
block slider
//- home.pug
append slider
script(src="slider.js")
テンプレートサンプル(簡易バージョン)
ディレクトリー構成の例です。超簡易バージョンなので、実務用にするためには少し手を加える必要があります。
/pug-practice
├ node_modules/
├ package.json
├ src/
| ├ _inc/(追加)
| | ├ _layout.pug(追加)
| | ├ _header.pug(追加)
| | └ _footer.pug(追加)
| └ index.pug
└ dist/
└ index.html(勝手に出力されます)
index.pugをテンプレート出力するためには以下のように書くと良いと思います。
layout.pug・レイアウト用テンプレート
//- Setting
block variables
append siteInfo
- var siteName = 'My Site'
- var menu = {'home': 'トップページ', 'service': 'サービス', 'about': '私たちについて', 'contact':'お問い合わせ'}
//- メニューの出力
mixin list(current='', list=menu)
ul
each val, key in list
- var key = key === 'home' ? '' : key
if current !== ''
li(class=val === current ? 'is-current' : '' ): a(href=`/${key}`)=val
else
li: a(href=`/${key}`)=val
//- コンテンツ
<!DOCTYPE html>
html
head
title=pageName
meta(name="description" content=pageDescription)
body
include _header.pug
main
block content
include _footer.pug
header.pug・ヘッダー用テンプレのコード
//- setting
block siteInfo
block variables
//- コンテンツ
header
if pageId === 'index'
h1 #{siteName}
else
p: a(href="/") #{siteName}
nav
+list(pageName)
footer.pug・フッター用テンプレのコード
//- setting
block siteInfo
//- コンテンツ
footer
+list
p: small (c) #{siteName}
index.pug・コンテンツ用テンプレのコード
//- setting
extend _layout
append variables
- var pageId = 'index' //- Name of the page
- var pageName = 'トップページ'
- var pageDescription = 'ページの説明'
//- コンテンツ
append content
h1 #{pageName}
p test
出力結果
<!DOCTYPE html>
<html>
<head>
<title>トップページ</title>
<meta name="description" content="ページの説明"/>
</head>
<body>
<header>
<h1>My Site</h1>
</header>
<nav>
<ul>
<li class="is-current"><a href="/">トップページ</a></li>
<li><a href="/service">サービス</a></li>
<li><a href="/about">私たちについて</a></li>
<li><a href="/contact">お問い合わせ</a></li>
</ul>
</nav>
<main>
<h1>トップページ</h1>
<p>test</p>
</main>
<footer>
<ul>
<li><a href="/">トップページ</a></li>
<li><a href="/service">サービス</a></li>
<li><a href="/about">私たちについて</a></li>
<li><a href="/contact">お問い合わせ</a></li>
</ul>
<p><small>(c) My Site</small></p>
</footer>
</body>
</html>
まとめ
いかがでしたか?
npmでcliで読み込むと、webpackやgulpどちらにでもカンタンに組み込めるので便利かと思います!
今回はよく忘れる変数やループ処理などの使い方をかなりディープにまとめました。
pugの公式サイトを結構頑張って網羅してる(つもり.. )のでよかったら参考にしていただけると嬉しいです。
次回はもう少し実務で使えるテンプレのレシピをまとめようと思います。
最後までお読みいただきありがとうございました。
おまけ・pug-cli の npmスクリプトのオプション
pug-cliのscriptのオプションです。途中まで訳しましたが、ちょっと自信なし。。。参考までに!
オプション名 | 詳細 |
---|---|
-h, —help | ヘルプオプションの出力 |
-V, —version | バージョンを調べる |
-O, —obj `<str | path>` |
-o, —out <dir> | HTMLやJSを<dir> (指定した名前のディレクトリ)に出力 |
-p, —path <path> | filename used to resolve includes |
-P, —pretty | インデントなど整えて出力 |
-c, —client | クライアントようにコンパイル?(compile function for client-side runtime.js) |
-n, —name <str> | コンパイルされたテンプレートの名前(-cオプションが必要) |
-D, —no-debug | デバッグなしでコンパイルする |
-w, —watch | ファイルを監視し、更新されたらリロードする |
-E, —extension <ext> | 出力ファイルの拡張子を指定 |
-s, —silent | ログを出力しない |
—name-after-file | name the template after the last section of the file path (requires —client and overriden by —name) |
—doctype <str> | specify the doctype on the command line (useful if it is not specified by the template) |
—hierarchy | 階層構造を維持したまま出力 |
FAQ
- pug での連想配列の書き方がわからない
- 当記事ではpug での連想配列の書き方もご紹介しています。
- pug での function(関数) の使い方を使いたい。
- pug でも function(関数) を使うことができます。大抵のことはMixinで解決できますが、JavaScriptっぽい記法がいい方は、function で書き換えることもできます。使用方法については、当ブログで紹介しているのでぜひ参考にしてください。
- pug でも for if each文 は使えますか?
- もちろんpug でも for if each文 は使えます。少しコツが要りますが、当ブログで使い方も紹介しています。
- Gulp じゃなくても pug は使えますか?
- pug cli を使えば gulp や webpack を使わなくても気軽に使うことができます。