Linuxを日常的に使う実験ブログ

Javascript製のテンプレートエンジンPugの基本的な使い方をまとめてみた

 2024-03-06

 2024-03-06

 プログラミング

今回はJavascript製のHTMLテンプレートエンジンであるPugを紹介します。HTMLとCSSでWebサイトを素早く組む必要があり、HTMLを素早く書ける方法を探していたところPugがなかなか使い勝手が良かったのでPugの使い方をまとめてみました。すべての文法を網羅することは出来ませんでしたが、ここに書いたことを知っていれば多くのケースでは対応できるのではないか、という内容を盛り込んであります。ウォークスルーとして読んでいただければ幸いです。

Pugとは?

PugはJavascriptで書かれたテンプレートエンジンです。簡単に言えばHTMLを生成するための言語です。Ruby製のHamlに影響を受けており、HTMLのようにタグを閉じる必要がなく階層をインデントで表現します。以前はJadeという名前でした。記事執筆時点でのPugのバージョンは3.xです。 

百聞は一見にしかずということで、簡単なPugで書かれた例を紹介します。

sample.pug

.sample
  h1 サンプルタイトル
  img(src="/path/to/image/sample.png")/
  p これはサンプルです。
  ul
    li sample01
    

生成されるHTML

<div class="sample">
  <h1>サンプルタイトル</h1>
  <img src="/path/to/image/sample.png"/>
  <p>これはサンプルです。</p>
  <ul>
    <li>sample01</li>
  </ul>
</div>

このように短く書けるだけでなく変数や条件分岐、繰り返しが使えるので無駄な繰り返しを書くことなくHTMLを生成出来ます。Pugのメリットを以下に挙げておきます。

  • 短い記述でHTMLを生成できる
  • 分岐、繰り返しやMixinなどを使用しプログラムで制御できる
  • 部品の挿入や使いまわしが可能なのでコンポーネント毎の開発が可能

デメリット

  • 新規言語の学習コストが生じる

インストールと使い方

今回はpug-cliを用いてpugファイルをHTMLに変換するシンプルな構成で考えます。単純に静的なHTMLを生成したい場合です。

まずはインストールをします。以下コマンドでpugpug-cliをインストールします。

npm i -D pug pug-cli

もしシステム全体でpugを使用したい場合は-gオプションをつけてグローバルでインストールしてください。

pugコマンドの使い方

pug [option] {input file or directory} {output file or directory}

という書き方になります。--watchオプションを使用すればファイルが書き換わるたびに自動で変換することも可能です。CSSファイルのビルドと組み合わせた使い方は別項目で後述します。

Pugの基本的な文法

ここでは基本的なpugの文法を紹介します。文法すべての紹介は出来ませんので筆者がしておくと便利だと思うものを紹介します。ここで紹介するものだけでも順案でパワフルなHTMLコーディングが可能です。

まずは基本的な使い方を見ていきます。基本の書き方はHTMLのタグ名につづけてタグの中身を書くだけです。属性についてはタグ名に()カッコを付けて記載することが可能です。

h1 Some Title
div
  img(src="./img/hoge.png" alt="some image")/
  p sample text
  ol(class="list warning")
    li list1
    li list2
    li list3
    li list4

生成されるHTML

<h1>Some Title</h1>
<div><img src="./img/hoge.png" alt="some image"/>
  <p>sample text</p>
  <ol class="list warning">
    <li>list1</li>
    <li>list2</li>
    <li>list3</li>
    <li>list4</li>
  </ol>
</div>

imghrタグのように自己閉鎖するタグは自動で閉じますが、タグ名の後ろに/を付けて自己閉鎖するタグであることを明示することも出来ます。

クラス名やIDの省略形

属性は()内に記載するのですが、クラスが一つのみの場合はtag.class_nameの形式で省略して書くことが出来ます。

p(class="some-class")
p(id="some-id")

これは以下と同じです。

p.some-class
p#some-id

更にdivタグに関してはdiv.hogehogediv.#some-id.hogehoge, #some-idと省略して書くことが出来ます。省略形を利用して早いコーディングが出来ますね。

ドキュメント宣言

<!DOCTYPE html>等のドキュメントタイプもPugで書くことが出来ます。基本的な書き方は

doctype {ドキュメントの種類}

となります。HTML文書であることを宣言するには行頭に以下を入れます。

doctype html

対応するすべての文書の種類については公式ドキュメント: doctypeをご覧ください。

コメント

PugのコメントはHTMLに出力するコメントと出力しないコメントの両方を書くことが出来ます。例を見てみましょう。

コメントの例

// これは出力するコメントです
//- これは出力しないコメントです
div
  p this is sample

出力されるHTML

<!-- これは出力するコメントです-->
<div>
  <p>this is sample</p>
</div>

//-から始まる場合はHTMLとしては出力ません。//から始まる場合はHTMLとして出力します。

javascriptの使用

pugではjavascriptを使用することが出来ます。書き方は3種類あります。「バッファリングなし」「バッファリングあり」「エスケープなしのバッファリングあり」の三種類です。それぞれ書き方が異なります。

  • バッファリングなし
  • バッファリングされる
  • エスケープなしのバッファリングあり

バッファリングなしのスタイルでコードを書いても何も結果には出力されません。繰り返し処理を行う際などの変数設定などで使用します。

バッファリングありのスタイルで書くとコードの結果はエスケープされた状態で出力されます。

最後にエスケープ処理ありのバッファリングありの場合はコードの結果はエスケープされない状態で出力されます。

//- Unbuffered script
- const name = "Kuro";

//- Buffered script
p= "His name is " + "<strong>" + name + "</strong>"

//- Unescaped Buffered script
p!= "His name is " + "<strong>" + name + "</strong>"
    

出力されるHTML

<p>His name is &lt;strong&gt;Kuro&lt;/strong&gt;</p>
<p>His name is <strong>Kuro</strong></p>

変数の出力

前項では、定義した変数を出力する方法としてバッファリングありのJSコードで出力しましたが、他にも出力の方法があります。#{...}の書き方で変数名を指定することで変数を出力することが可能です。以下に例を示します。=の有無に注意してください。

- const name = "Kate"
p= "Her name is " + name + "."
p Her name is #{name}.

繰り返し

JSのforの他、Pugの構文としてeach, while文が使用できます。

JSのfor文を使用した例

// for loop
ul
  - for (var i=0; i <= 3; i++)
    li= i

出力されるHTML

<!-- for loop-->
<ul>
  <li>0</li>
  <li>1</li>
  <li>2</li>
  <li>3</li>
</ul>

Pugのeach構文を使用した例

// each loop
- let list = ['apple', 'orange', 'grape'];
ol
  each val in list
    ul= val

出力されるHTML

<!-- each loop-->
<ol>
  <ul>apple</ul>
  <ul>orange</ul>
  <ul>grape</ul>
</ol>

Pugにはwhile構文が用意されています。javascriptのfor文を使用しなくても変数がインクリメントするループを書けます。 Pugのwhile構文を使用した例

// while loop
- var n = 0;
ul
  while n < 4
    li= "n= " + n
    - n++;

出力されるHTML

<!-- while loop-->
<ul>
  <li>n= 0</li>
  <li>n= 1</li>
  <li>n= 2</li>
  <li>n= 3</li>
</ul>

条件分岐

変数を用いて条件分岐させ出力結果を変化させることも可能です。条件分岐にはバッファリングなしのJavascriptのif文を使う他にPugのif-else構文やcase構文、unless構文を使うことが出来ます。

// Unbuffered code style
- let flag1 = false;
- let flag2 = true;
.panel
  - if (flag1)
      p hoge
  - else if (flag2)
      p huga
  - else
      p hogehoge

生成されるhtml

<!-- Unbuffered code style-->
<div class="panel">
  <p>huga</p>
</div>

Pubのif-else構文を使用した例

// Pug's conditional syntax
- let flag1 = false;
- let flag2 = true;
.panel
  if flag1
    p hoge
  else if flag2
    p huga
  else
    p hogehoge

生成されるhtml

<!-- Pug's conditional syntax-->
<div class="panel">
  <p>huga</p>
</div>

Pugのcase構文を使用した例 

// case
- const name = "Tora";

case name
  when "Kuro"
    p He is this blog's writer.
  when "Taro"
    p Do you know Taro?
  default
    p His/Her name is #{name}.

生成されるhtml

<!-- case-->
<p>His/Her name is Tora.</p>

Pugのunless構文を使用した例

// unless
- let params = {name: "Mike", age: 20}

unless params.height
  p params doesn't have height infomation.

// unless works like a negated if
if !params.height
  p params doesn't have height infomation.

生成されるhtml

<!-- unless-->
<p>params doesn't have height infomation.</p>
<!-- unless works like a negated if-->
<p>params doesn't have height infomation.</p>

Mixin

Mixinは一部が改変可能な部品です。Mixinを使うことで同じ処理を何度も書かずに済みます。

単純なMixinの例

// Simple Mixin
//- Declaration
mixin list
  .list
    ul
      li apple
      li orange
      li grape

//- use
.fruits
  +list

生成されるhtml

<!-- Simple Mixin-->
<div class="fruits">
  <div class="list">
    <ul>
      <li>apple</li>
      <li>orange</li>
      <li>grape</li>
    </ul>
  </div>
</div>

Mixinは引数を取ることも可能です。引数はHTML出力に使えるだけでなく属性にも使用可能です。

引数をとるMixinの例

// Mixin with args
//- Declaration
mixin friend(first, last)
  .friends
    p FirstName: #{first}
    p LastName: #{last}

//- use
+friend('Taro', 'Yamada')
+friend('Hanako', 'Tanaka')

生成されるhtml

<!-- Mixin with args-->
<div class="friends">
  <p>FirstName: Taro</p>
  <p>LastName: Yamada</p>
</div>
<div class="friends">
  <p>FirstName: Hanako</p>
  <p>LastName: Tanaka</p>
</div>

ループや条件分岐などと組み合わせると複雑なHTMLを少ない行数で構築することが可能になります。

// eachとMixinを組み合わせた例
-
  const friend_list = [
    {
      first_name: 'Jiro',
      last_name: 'Sasaki'
    },
    {
      first_name: 'Yoshiko',
      last_name: 'Suzuki'
    }
  ]
  
each val in friend_list
  //- 前の例で定義したfriend mixinを使用する
  +friend(val.first_name, val.last_name)

生成されるhtml

<!-- eachとMixinを組み合わせた例-->
<div class="friends">
  <p>FirstName: Jiro</p>
  <p>LastName: Sasaki</p>
</div>
<div class="friends">
  <p>FirstName: Yoshiko</p>
  <p>LastName: Suzuki</p>
</div>

インクルード

Pugは他のPugファイルをインクルードすることが可能です。これによりパーツ毎に開発を行い使いまわすことが可能になります。インクルードの使い方はパーツを適用したい場所でincludeに続けてファイルパスを書くだけです。

includeの使用例

.container
  include './header.pug'

面白いことにpugファイルだけでなくsvgファイルもインクルード出来ます。

svgファイルのインクルード

include './svg/image.svg'

プレーンテキストの扱い

最後はPugが変換を行わないプレーンテキストの扱いについて見ていきます。

インラインタグ

<em><span>等のインラインタグはタグ内にそのまま書き込んでもタグとして生成されます。

p これは<strong>サンプル</strong>です

生成されるHTML

<p>これは<strong>サンプル</strong>です</p>

HTMLタグ

Pugでは<から始まるHTMLタグは平文として扱われ特に処理されません。よってHTMLタグはそのまま記述することが出来ます。以下に例を示します。

<table>
  <tr>
    <td>apple</td>
  </tr>
  <tr>
    <td>apple</td>
  </tr>
</table>

生成されるHTML

<table>
  <tr>
    <td>apple</td>
  </tr>
  <tr>
    <td>apple</td>
  </tr>
</table>

パイプによる平文の入力

長い平分をPタグに入力したい場合などはパイプを使うと便利です。

p
  | これはサンプルテキストです。これはサンプルテキストです。
  | これはサンプルテキストです。これはサンプルテキストです。

生成されるHTML

<p>
  これはサンプルテキストです。これはサンプルテキストです。
  これはサンプルテキストです。これはサンプルテキストです。
</p>

タグ内部のブロック

タグ内部で長いブロックを書きたい場合に使います。特にscriptstyleタグではタグ内部が長文化しがちです。このような場合はタグ名または属性のカッコの直後に.を記して、インデントを一つ下げてブロックに書きます。

タグ内ブロックの例

script().
  let hasId = false;
  if (hasId) {
    console.log('OK. You have ID');
  } else {
    console.log("You don't have ID");
  }

生成されるHTML

<script>
  let hasId = false;
  if (hasId) {
    console.log('OK. You have ID');
  } else {
    console.log("You don't have ID");
  }
</script>

まとめ

軽い気持ちでPugの文法を自分なりに整理しておきたいと書き始めましたが思いがけず分量が増えてしまいました。今回はPugのBlockに関しては扱うことが出来ませんでした。公式ドキュメントを参考にしていただければ幸いです。

参考資料