誰がなんと言おうと私はPugを使い続ける、という強い決意をこめて、ここにPugのチートシートを記す。

Pugの基本的な使い方

Pugは、独自の記法で書いたテキストをHTMLに変換するツールである。この記法は、インデントを活用してHTMLのマークアップを超シンプルに表現できる。APIを使って、単体で文字列をHTMLに変換することも可能。ただ、バンドルツールに組み込む形で使われるケースが多い。

ここでは、webpackを例に基本的な使い方をまとめる。まずnpmコマンドで必要なパッケージをインストールする。

bash
npm i -D webpack webpack-cli pug pug-plugin html-webpack-plugin

ちなみにwebpackの公式サイトではpug-loaderが紹介されているが、このローダは最新のPug3には対応していない。代替として、pug-pluginというローダを使う。

このプラグインを使うと、Pugファイルをエントリポイントにすることもできるらしい。でも、調べるのがめんどうだ。今回は馴染みのある設定を踏襲して、ただpug-loaderと置き換える形で使う。

以下、webpack.config.jsファイルの設定である。

js
const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const PugPlugin = require("pug-plugin");

module.exports = {
  mode: "development",
  entry: "./src/index.js",
  output: {
    path: path.resolve(__dirname, "dist"),
    filename: "bundle.js",
  },
  module: {
    rules: [
      {
        test: /\.pug$/,
        loader: PugPlugin.loader,
      },
    ],
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: "./src/template.pug",
    }),
  ],
  resolve: {
    fallback: {
      path: false,
      os: false,
      assert: false,
      fs: false,
    },
  },
};

srcディレクトリ下にindex.jstemplate.pugを置き、npx webpackを実行すれば、distディレクトリ下にJavaScriptファイルとHTMLファイルが出力されるはずである。

タグを指定する

Pugではタグ名 中身のテキストと言う形でHTMLを記述できる。

pug
p 打つのも読むのもめっちゃ楽
br/
p セルフクロージングタグはお尻にスラッシュをつけるだけ。

属性値を指定する

タグ名のすぐ後ろに(属性名1="属性値1" 属性名2="属性値2")のような形で指定する。文字列のよう思えるが、実際にはJavaScriptの式。なので動的に値を指定することも可能。また、style属性は文字列のほかオブジェクトも指定できる。

デフォルトでは属性値はすべてエスケープされるため注意が必要。エスケープを回避するには、!=とすれば良い。

pug
// 属性値が多いときは改行しても良い
input(
  type='checkbox'
  name='agreement'
  checked
)

// 式を使う例(冒頭にハイフンをつけるとJSの式が使える)
- let isDay = false;
div(class= isDay ? "light-mode" : "night-mode")

// エスケープさせないようにする例
a(href!="<?php echo home_url(); ?>") さぁ見つけるんだ自分だけのHome

// style属性の例
p(style={color:"red","font-size":"32px"}) ハイフンの含まれる属性名は""で囲う
p(style="color:red;font-size:32px;") ふつうに文字列を指定してもOK

変数をテキストに埋め込む

属性値と同じく、テキストにも変数(というか式)を埋め込むことができる。タグ名=式、あるいはタグ名 #{変数名}のように書く。

pug
- const text = "冒頭に<em>ハイフン</em>をつけるとJSコードが書ける";
//以下の2文は、属性値と同じくエスケープされた文章が出力される
p= text
p #{text}

//エスケープさせたくないときは`!=`を使う
p!= text

HTMLタグをそのまま表示する

HTMLタグはそのまま表示されるので、そのまま書けば良い。

pug
p <em>強調</em>してみたり、<br/>改行してみたり

複数行のテキストを表示する

複数行のテキストを表示する方法には、以下の2つがある。

pug
p
  | インデントして先頭をパイプでつなぐと、
  | その文字列は該当のタグのテキストコンテンツになる。
  | ただし、パイプは改行ではないので注意が必要。
  br
  | 改行したい場合は、このように`br`を間に挟む必要がある
  |(もちろんインラインで直接HTMLタグを書いても良い)。

p.
  ピリオドの直後に改行してインデント。
  空白が入ると機能しないので注意。
  `script`タグとか`style`タグとかでよく使う。
  ちなみに改行直後に半角スペースが挿入される(元が英語用だから)ので、
  日本語には向かない。

idとclassの指定方法

idとclassは、セレクタと同じように指定することもできる。

pug
p#id-name.class-name クラスを複数指定したいときは.を繋げていけば良い。
#id-name ちなみにタグ名を省略すると、divタグとして解釈される。

コメントを書く

コメントの書き方は以下の通り。

pug
// コメントはスラッシュ2つ。
//
  複数行のコメントは、
  改行してインデントする。
//-
  HTMLソースに出力しないコメントは
  スラッシュ2つにマイナスを付ける。

外部ファイルを読み込む

include ./xx/yy/zz.pugで別のテンプレートファイルを読み込める。また、Pug以外のファイル形式を読み込むときはフィルタ(後述)を使う。

pug
// pugファイルの読み込み
include header.pug
// MDファイルの読み込み
include:markdown-it README.md

テンプレートを継承する

外部ファイルの読み込みと似ているが、Pugには元のテンプレートを継承・拡張する仕組みがある。

Pugで言う継承とは、例えばテンプレートAにプレースホルダ(置き換え用のブロック)を指定しておき、テンプレートAを読み込んだテンプレートBで、そのプレースホルダを置き換えるような仕組みを指す。

プレースホルダはblock 任意のブロック名という形で指定する。

pug
//継承されるテンプレート layout.pug
main
  block content
    div.before
      p 跡形もなく置き換えられるデフォルトのブロック。
      p 継承する側で何も指定しない場合は、そのままここが表示される。
      p デフォルトで特に何も表示したくなければ、block文だけでもいい

継承する側は、extendsキーワードを使ってテンプレートを読み込み、継承元で指定したblock 任意のプロック名の中身を書く。

pug
//継承するテンプレート pageA.pug
extends layout.pug
block content
  div.after
    p ここが丸っと置き換わる。
    p `extends`文も`block`文もトップレベルに書かないとエラーが出るので注意。

ちなみに継承元のブロックに要素を追加したい場合は、継承側でblock prependあるいはblock appendとすると、継承元のブロックの前、あるいは後ろに任意のブロックを挿入できる。

テンプレートの部品を関数的に呼び出す

Pugにはテンプレートの部品を動的に扱える、ミキシンという仕組みがある。Pugで言うミキシンは、テンプレートの部品を返す関数のようなものである。includeblockと働きが似ているが、もうちょっとちんまい、汎用的なテンプレート部品を作るのに向いている。

外部テンプレートを読み込むincludeでページのレイアウトパターンを組み、それらを継承する子コンプレートにblockmixinを書いて各ページを作っていくのが、Pugのコーディングの基本的な流儀である。

pug
// mixinを定義するテンプレート mixins.pug
mixin titleAndDesc(title, description)
  head(title, description)
  title #{title}
  meta(name="description" content!=description)

ちなみに複数のミキシンを1つのテンプレートにまとめるか個々に分けるかはプロジェクトの規模やチームの方針による。

pug
// mixinを使うテンプレート mixin-user.pug

//まずファイルをインクルードする
include mixins.pug
//`+ミキシン名(引数)`で挿入箇所を指定する
+titleAndDesc('ページタイトル','ディスクリプション')

テンプレート内で別の言語を使う

フィルタは、Pugテンプレートの中で他の言語を扱える仕組み。インラインにも、外部ファイルの読み込みにも対応している。

Pugのフィルタは、すべてのJSTransformersに対応している。JSTransformersは、数多あるテンプレート・エンジンやらコンパイラやらのために書かれた言語を、一貫して変換できるように提唱されたAPI(たぶん)。

Pugテンプレート内でフィルタを使う場合、使いたい言語のJSTransformerモジュールをまずインストールする必要がある。

sh
// Markdownを使いたい場合
npm i -D jstransformer-markdown-it

あとは、ふつうのタグと同じように:フィルタ名とつけて、以降のブロックにテキストを書けばいい。外部からファイルを読み込みたい場合は、include:フィルタ名 ファイルパスという形で宣言する。

pug
// インラインの場合
:markdown-it
  ## 主なフィルタの例
  - :markdown-it
  - :babel
  - :uglify-js
  - :scss

// 外部からファイルを読み込む場合
include:markdown-it ../README.md

インラインでJavaScriptを使う(反復)

Pugのテンプレートでは、インラインでJavaScriptのコードが使える(ただしAPIはサーバサイドのものに限定)。目的は簡単な変数の操作でテンプレートの制御を柔軟にすること。そのため複雑なロジックや長い計算は、サーバサイドのコードで行い、その結果をテンプレートに渡すことが推奨されている。

JavaScriptの変数は、Pug独自の反復構文や条件文で使うことができる。まず反復構文にはeachwhileがある。

pug
//
  先頭にハイフンをつけるとJavaScriptコードが使える。
  コメントと同じように、複数行書きたいときはハイフンの直後で改行してインデントすればいい。
-
  const months = [
    "睦月","如月","弥生", "卯月",
    "皐月","水無月","文月","葉月",
    "長月","神無月","霜月","師走"
  ];
// each
ul
  each m in months
    li= m

  //添字を取得したいときはカンマで区切る
  each m,i in months
    li= m+': '+i

  //オブジェクトでも書き方は同じ
  each v, k in months.reduce((a,m,i)=>{a[i]=m; return a},{})
    li= k + ': ' + v

// while
ul
  - let i=0
  while i < months.length
    li= months[i++]

インラインでJavaScriptを使う(条件)

条件文には、ifelse ifelseのコンボが使える。条件式の()とブロックを示す{}がない以外はJavaScriptと同じ。

pug
- const difficultyLevel = 'normal'
if difficultyLevel === 'easy'
  p 達磨草
else if difficultyLevel === 'normal'
  p 福寿草
else if difficultyLevel === 'hard'
  p 山茶花
else
  p 彼岸花

既存ツール(Googleタグマネージャとか)のスクリプトを埋め込む

PugにはHTMLを埋め込めるが、<script>タグで任意のJavaScriptコードを埋め込もうとすると、特殊記号系で引っ掛かってコンパイルが通らない。コピペでは使えないので、Pugの構文に直す必要がある。

具体的には、scirpt.のように、scriptの後ろにドットをつけて改行し、使用したい<script>タグの中身をそのブロックに含める。以下のようなイメージである。

pug
// Google Tag Manager
script.
  (function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':
  new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],
  j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
  'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);
  })(window,document,'script','dataLayer','GTM-XXXX');
// End Google Tag Manager

// Google Tag Manager (noscript)
noscript
  iframe(src='https://www.googletagmanager.com/ns.html?id=GTM-XXXX', height='0', width='0', style='display:none;visibility:hidden')
// End Google Tag Manager (noscript)

参考にした資料