WEB掻っ穿じり

WEB備忘録とチャリとカメラ

Gulp 4でテンプレートエンジンのPug(Jade)を使う

  category:web

Gulpのタスクにテンプレートエンジンのpugを使えるようにします。

Pug

今更ですが、PugはもとはJadeという名称でHTMLのJavaScript Templatesの1つとされる。
Node.jsのフレームワーク Express ではhtmlビューはデフォルトでサポートされているらしい。

Ruby on Railsでのhaml/slimのような立ち位置で、ここのサイトも静的サイトジェネレーターであるMiddlemanを使ってhtmlビューはhamlによって書かれている。

Pug導入のメリットは、、、 - 要素や属性の記述が簡略化できる - インデントに沿って記述して、閉じタグの忘れなどもなくなる - 通常のHTMLではできないモジュールの継承や共通化が使える - 変数が使える - mixin(関数のように使いまわせる block を生成) - JavaScriptのようなオブジェクトを作ってhtml側でデータを扱える

みたいなところだと思います。

今回はビルドツールのGulpのタスクにpugコンパイルの処理を加えるのと、pugの使い方も備忘録として残したいと思います。

基本的な記述はこちらなどを参考にしてください。

必要なモジュールのインストールと設定

Gulp 4が既に使えることが前提で進めます Gulp 4サクッと導入(nodenv版)

gulp-pugをインストールします、pugコンパイルに必要なモジュールはこれだけ。

npm i -D gulp-pug

gulpfile.js設定

const { src, dest, watch, series } = require('gulp')
const browser = require('browser-sync').create()
const pug = require('gulp-pug')

const compilePug = () =>
  src([ 'src/pug/**/*.pug', '!src/pug/**/_*.pug' ])
    .pipe(
      pug({
        pretty: false
      })
    )
    .pipe(dest('./dist'));

const watchFiles = () => {
  watch(['src/pug/**/*.pug'], series(compilePug,reload));
}

const server = (cb) => {
  browser.init({
    server: { https: true },
    baseDir: './dist',
    server: "./dist",
    index: "index.html" 
  });
  compilePug()
  cb();
}

const reload = (cb) => {
  browser.reload();
  cb();
}

exports.default = server;

ディレクトリ構成はこのようになります。

├ /dist
│  ├ /hoge
│  │   └ index.html
│  └ index.html
└ /src
   └ /pug
      ├ /data
      │  └ _data.pug
      ├ /hoge
      │  └ index.pug
      ├ /layouts
      │  └ _layout.pug
      ├ /mixin
      │  └ _mixin_.pug
      ├ /modules
      │  ├ _footer.pug
      │  ├ _head.pug
      │  └ _header.pug
      └ index.pug

これでpugファイルがwatchされ、自動でコンパイルされる状態になりました。

npx run gulp

それぞれのpugファイルの記述を見ていきます。

pugファイルの構造

上記のディレクトリ構造は例の1つですが、pugファイル内部でincludeされているファイルは _ の接頭辞がついて、接頭辞がつかないpugファイルがディレクト構造をそのままにhtmlファイルにコンパイルされます。

layouts/_layout.pug

まずは各htmlページの基本的な構造を定義している layouts/_layout.pug を見ていきます。

block value
doctype html
html(lang='ja')

  //- ヘッド
  include ./modules/_head.pug
  body(class=pageClass)

    //- ヘッダー
    include ./modules/_header.pug
    
    //- ページコンテンツ
    block content
    
    //- フッター
    include ./modules/_footer.pug

htmlページのheadタグとheader要素とそのページのコンテンツ、footer要素が大まかに設定されています。

body要素には変数が入る想定でpage単位でclassがつくように指定してますが、変数のextendするpege側で定義されます。

body(class=pageClass)

modules/_head.pug

head要素です。
共通の要素はベタ書きの文字列で、pageによって変えたいコンテンツは変数を使います。

blockごと差し込みたい場合も適当に名を付けてあげます。

head
  meta(charset='UTF-8')
  meta(http-equiv='content-language', content='ja')
  meta(name='viewport', content='width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no')
  title #{title}
  meta(name='description', content=description)
  meta(name='keywords', content=keywords)
  block addCss
  block addJs

modules/_header.pug & modules/_footer.pug

相対パスで外部ファイルを読み込む場合、それをかくページで共有するときなどは相対パスの階層が変化してくるので、ページ内でpathという変数を定義してあげると、それぞれのページで正しいディレクトリ指定ができるようになります。

そしてES6のようなテンプレートリテラルも使えるので、わざわざ連結してあげる必要もないです。

Date.now()のようなJavaScriptメソッドも使えるので、たとえばよく修正のかかるファイルの読み込みにはタムスタンプをパラメータに付けてあげて、コンパイル毎にキャッシュ対策もできる。

//- ヘッダー
header
  .headerContainer
    h1 
      a(hred="#")
        img(src=`${path}assets/images/sample.png?ver=${dateNow}`, alt="sample")


`${path}assets/js/main.js?ver=` + dateNow
//- フッター
footer 
  .footerContainer
    p フッター

data/_data.pug

pugテンプレートの中で配列データを持つことができます。

- 
  var manufacturing1ListData = [{
      index: '0',
      img: 'sample0.png',
      ttl: 'タイトル',
      desc: '説明'
    }, {
      index: '1',
      img: 'sample1.png',
      ttl: 'タイトル',
      desc: '説明'
    }, {
      index: '2',
      img: 'sample2.png',
      ttl: 'タイトル',
      desc: '説明'
    }, {
      index: '3',
      img: 'sample3.png',
      ttl: 'タイトル',
      desc: '説明'
    }, {
      index: '4',
      img: 'sample4.png',
      ttl: 'タイトル',
      desc: '説明'
    }, {
      index: '5',
      img: 'sample5.png',
      ttl: 'タイトル',
      desc: '説明'
    }];

mixin/_mixin.pug

eachを使ったmixiも作成できます。 引数で _data.pug でオブジェクトと、そのページで定義している変数も渡しています。

mixin dataList(arr, path)
  each value, index in arr
    li
      figure
        img(src=`${path}assets/images/${value.img}`)
      .contents
        h3 !{value.ttl}
        p #{value.desc}

index.pug

topページに当たるpugファイルです。
このファイルのディレクトリ構成がそのままurlとなります。

extend layouts/_layout
append value
  - var title= 'タイトル';
  - var description= 'トップページディスクリプション';
  - var keywords = 'キーワード1,キーワード2,キーワード3';
  - var path = '';
  - var dateNow = Date.now();
  - var pageClass = 'index'
append addCss
  link(rel="stylesheet" href=`${path}assets/css/style.css?ver=${dateNow}`)
append addJs
  script(src=`${path}assets/js/main.js?ver=${dateNow}`)

block content
  main
    section
    h2 
      | ページコンテンツ
    p 
      | テキストテキスト
      br
      | テキストテキストテキストテキスト
    include data/_data.pug
    include mixin/_mixin.pug
    ul
      +dataList(data1, path)

上からextend layouts/_layout をまず読み込み。

append value でincludeで使われる変数の定義をしてます。
文字列だけの変数定義は、仮に記載するのを忘れてしまっても nullとして描画されerrorで警告が出ないので、見落とさないようにしてあげます。

block contentlayouts/_layout.pug で読み込まれている位置に入ることになります。 継承や共有パーツのincludeの仕方はいくつかありますが、このようにpugは柔軟に対応してくれます。

  include data/_data.pug
  include mixin/_mixin.pug
  ul
  +dataList(data1, path)

layoutからのmodulesの読み込みと同じように、dataとmixinのpugファイルを読み込んでおくと、その変数名と関数名がこのtopページの領域で呼び起こすことができます。

コンパイルされるhtmlソース

以上のpugファイル構成をコンパイルしてみると、以下のようになります。

<!DOCTYPE html>

  
    
    
    
    
    
    
    
    
  
  
    

sample

ページコンテンツ

テキストテキスト
テキストテキストテキストテキスト

  • タイトル

    説明

  • タイトル

    説明

  • タイトル

    説明

  • タイトル

    説明

  • タイトル

    説明

  • タイトル

    説明

フッター

1ページだけではありますが、デザインシステムを取り入れたstyleやコンポーネントと同じようにhtmlを構造化することができることがわかると思います。

最後に

静的htmlで表現しなければならない場面はたくさんあるかと思います。
コンパクトで軽量、気軽に導入できるのが何よりのメリットかと思います。