Nunjucks (JSテンプレートエンジン) とGulpでHTML開発環境を効率化

こんにちは、Quidamです。

前回の記事「「JSテンプレートエンジンとは?」まとめると大変だった基礎知識」から考察が進み、JSテンプレートエンジン「Nunjucks」を社内標準として使用していくことになりました。ちなみにNunjucksの読み方は「ナンジャックス」です。日本人には何とも言いにくい…。「ナンジャックス」と真ん中にアクセントを置きそうですが、おそらくアクセントは先頭の「ナ」で「ンジャックス」のよう。…どうでもいいですね。

改めて、今回はNunjucksとGulpでHTML開発環境を効率化する具体的な設定例をご紹介します。

ゴールと前提情報

この記事では、GulpとNunjucksで次のような環境を作る事をゴールとします。

  • Nunjucksを使って、ヘッダー/フッター/モーダルコンテンツなどの共通HTMLパーツを外部ファイル化し、各ページに合わせてアレンジできるようにする。
  • サイト名やmeta情報の初期値など、サイト内共通で使用する変数を外部ファイル化して、それらをNunjucksで使えるようにする。
  • 出力されたHTMLが自動で整形されるようにする。
  • HTMLファイルが更新されたら、ブラウザを自動でリロードする。

※Node.jsやGulpのインストール・設定については割愛します。これらの環境がない場合は、こちらの記事「Gulpを初めて使ってみた!導入からタスクお試し(gulp-cssmin, browser-sync)までの手順」が参考になると思います。

※Node.jsのモジュールは全てローカルインストールを前提として記述しています。グローバルインストールする場合は、コマンドが一部異なりますのでご注意ください。

※各コマンドはプロジェクト用のディレクトリに移動してから実行されるものとします。

例)プロジェクトディレクトリが C:varwwwtest.local の場合
cd C:varwwwtest.local

モジュール/プラグインのインストール

まずは上記のゴールを達成するために必要なNode.jsのモジュールとGulpのプラグインをインストールします。

  • gulp-nunjucks-render: GulpでNunjucksのレンダリングを使う
  • gulp-data: サイト共通変数を使えるようにする
  • gulp-beautify: HTMLを整形する
  • browser-sync: ブラウザを自動リロードする

コマンドは次です。

npm install --save-dev gulp-nunjucks-render gulp-data gulp-beautify browser-sync

準備ができたので、さっそくgulpfile.jsを編集していくのですが、その前にプロジェクト内のディレクトリ構造を検討しておきましょう。

ディレクトリ構造

グランフェアズでは、プロジェクトディレクトリ直下の構造を次のようにしています。Git用のファイルなどありますが、この記事で大事なのは、生成後のHTMLが配置されるhtml/とNunjucksのテンプレートファイルを設置するsrc/template/です。

Nunjucksを採用した際のディレクトリ構造例:プロジェクトディレクトリ直下

この2つは、src/template/に設置したテンプレートを編集すると、html/配下にHTMLファイルが自動で配置されるという関係性で、html/に配置されたHTMLファイルをブラウザで確認しながら作業を進めていくというイメージです。

src/template/配下の構造

Nunjucksのテンプレートファイルを設置するsrc/template/配下は次のようになっています。

Nunjucksを採用した際のディレクトリ構造例:テンプレートディレクトリ配下

.njkという拡張子になっているファイルがNunjucksのテンプレートファイルです。
拡張子は何でも良く.htmlでも問題ありません。

※ファイル名の接頭に_アンダースコアを付与していますが、これは人が見た目で「ページ用ファイルか否か」を識別しやすいようにしているだけで、アンダースコアの有無による動作の違いはありません。

template/直下は次の用途で分けています。

ディレクトリ 用途
data サイト共通変数を指定するJSONファイルを設置
layout HTMLの一番外側の骨格を指定するテンプレートを設置
page 各ページのコンテンツHTMLを指定するテンプレートを設置
ここにあるファイルがHTMLファイルとして生成されhtml/配下に設置されます
partial ヘッダー/フッターなどサイト共通HTMLパーツを設置

テンプレートの種類と出力イメージ

上図のうち、layout/partial/のファイルをpage/index.njkに継承してHTMLコードを返すまでがNunjucksの仕事で、page/index.njkの更新を検知しhtml/にHTMLファイルを生成するのがGulpの仕事です。

次に、具体的なGulpのタスクをgulpfile.jsで見ていきます。

gulpfile.js

全体は次のようになっています。
冒頭で必要なモジュールを読み込み、パスなどの変数を設定。Nunjucksのタスク、ブラウザリロードのタスク、watchタスク、defaultタスク、という構成です。

Nunjucksのタスク

純粋にNunjucks(gulp-nunjucks-render)だけを使った場合、タスクは次のようになります。

3行目でHTMLファイルの生成対象となるテンプレートファイルsrc/template/page/**/*.njkを指定。7行目で生成先のディレクトリhtml/を指定しています。
間の4~6行目は、テンプレートフォルダの基点src/template/を指定しています。これにより、テンプレートファイルの階層を気にせず、同じパス記述でテンプレートファイルをincludeしたりextendすることができるようになります。

たったこれだけ。このnunjucksタスクをwatchさせれば、src/template/配下で操作したテンプレートファイルの変更がhtml/配下のHTMLファイルに反映されるようになります。

サイト共通変数をテンプレートで使えるようにする

冒頭から出ている「サイト共通変数」ですが、具体的には次のようなものが考えられます。

  • サイト名(<title>タグなどで頻繁に使用)
  • ページ個別で指定が無い場合に挿入するmeta description, meta keywordsの初期値
  • ページ個別で指定が無い場合に挿入するOGタグ, twitterカードタグの初期値

これらを.jsonファイルにまとめて管理します。(src/template/data/site.json

これをNunjucksテンプレートで扱えるようにすると、記述もれの防止や、同じ値を繰り返して書く手間を省くことができます。
これを実現するモジュールがgulp-dataで、gulpfile.jsのNunjucksのタスク内、次の4行目~6行目がその指定になります。

!JSONが読み込めない?

上のコードの5行目のパス指定return require(paths.src.json);となっているところ、変数を使わずに記述すると次になります。

return require('./src/template/data/site.json');

この部分を、カレントディレクトリの明示./がない記述にすると

return require('src/template/data/site.json');

エラーとなってJSONファイルの読み込みが出来ません。
このため、gulpfile.js冒頭でパスを変数に入れているところでjsonのパスだけカレントディレクトリを明示しています。

'html' : 'src/template/page/', # カレントディレクトリの明示なし
'json' : './src/template/data/site.json' # カレントディレクトリの明示あり

このJSONを使って、各ページのTDK(<title>,meta description, meta keywords)を一括管理するという方法もありますが、ページが追加変更になった場合の対応漏れを防ぐためにも、TDKは各ページで設定するようにしています。(後述)

生成後のHTMLを整形する

この辺りはGulpの定番ですね。gulp-beautifyで、生成されたHTMLコードを整形します。整形方法のオプションを指定し、15行目を追加したらNunjucksのタスク設定は完成!

後は、browser-sync用のタスクと、監視用のwatchタスク、標準のdefaultタスクを設定すればgulpfile.jsの準備も完了です。

テンプレートファイル

いよいよNunjucksのテンプレートのお話です。とっつきやすく機能も豊富なのでもっと活用シーンはありそうですが、ここでは初期ファイルセットとして使っている3つのファイルを例に、基本的なNunjucksの利用方法を紹介したいと思います。

レイアウトファイル layout/_default.njk

レイアウトファイルは、HTMLの一番外側の骨格を指定するテンプレートです。

Nunjucksでは、関数やマクロの呼び出しに{%~%}を使います。(変更することもできます)

目的の1つ目「共通パーツの外部ファイル化とインクルード」を実現するのが、includeタグです。 ここでは<head>内のコードを制御するpartial/site/_dochead.njkと、ヘッダー/フッター用のpartial/site/_header.njkpartial/site/_footer.njkを読み込んでいます。

もう一つ、block ~ endblockというタグが2か所あります。これは、このレイアウトファイルが継承された先のテンプレートファイル内で、上書きが可能なブロックを定義しています。

ページファイル page/index.njk

ページファイルは、HTMLファイルの生成対象となる各ページのテンプレートです。page/index.njkなので、サイトのトップページ(html/index.html)用のテンプレートですね。実際はもっとコンテンツがモリモリ入りますが、初期状態はこんな感じです。

冒頭のextendsで上述のレイアウトファイルlayout/_default.njkを継承し、下部のblock ~ endblockで、{% block contents %}を「<p>グランドトップのコンテンツ</p>」と定義しています。

2~5行目にあるsetタグは、変数を定義します。ここでは、ページ単位のTDK(<title>,meta description, meta keywords)とmeta og:typeを設定していて、次に紹介するパーシャルファイルでこの値を使用しています。

パーシャルファイル partial/site/_dochead.njk

partial/site/_dochead.njk<head>内のコードを制御する共通パーツです。各ページで定義された変数の値によって出力されるHTMLをアレンジしています。(長いので一部抜粋)

構文はとてもシンプルです。上部で条件によって変数の値を振り分けていて、下部で<head>内のHTMLに挿入しています。

条件分岐はif ~ else ~ endif。変数の代入にはダブルブラケッツ{{ }}を使います。

サイト共通変数の利用

<head>内では、サイト共通変数(src/template/data/site.json)の値も使います。
例えばページのタイトルタグを設定する部分(1~4行目)は次のようになっています。

{%- if pageT -%}
{%- set title = pageT + ' | ' + data.sitename -%}{%- else -%}
{%- set title = data.sitename  -%}
{%- endif -%}

ページ用テンプレートにpageTの値があれば、<title>
「{pageTの値} | {サイト共通変数のsitename}」になり、値がなければ
「{サイト共通変数のsitename}」だけになる、といった感じです。

JSONファイル内で、全ての値の親となるKey(名前)にdataを指定しているため、data.sitenameでサイト名が取得できるというわけですね。
Nunjucksは、サイト共通変数が簡単に使えて便利です。

HTMLを生成してみる

ここまでで、基本的なNunjucksの利用方法が見えてきたと思うので、最終的なアウトプット(HTML)がどのようになるのか、Gulpを一旦走らせてみましょう。

# 一旦 Nunjucksタスクを走らせる
npx gulp nunjucks

html/index.htmlが生成されれば成功です!(・∀・)v

生成されたHTMLファイル html/index.html

生成されたHTMLは次のようになります。
※上述のテンプレートファイル以外に、ヘッダー/フッター用のパーシャルファイルをインクルードしています。

gulp-beautifyでHTMLもキレイ。いや~、10年前に「インデントがなってない」と後輩さんにくどくど言ってたのはもはや都市伝説。

さて。HTMLファイルの生成まで確認ができたので、一通り準備は完了です。

後はモリモリマークアップを

だいぶ長くなりましたが… ゴールとしていた環境が整いました。あとは、テンプレートを駆使してモリモリマークアップを進めるだけです。
まずは黒い画面でGulpを起動。

# Gulpで監視を開始
npx gulp

これで、テンプレートファイルsrc/template/を変更・保存するとhtml/にHTMLファイルが生成され、ブラウザが自動でリロードするようになります。

npx gulpでHTMLファイルが生成されない場合は

共同開発をしていてgulp watchが変更を検知できない新しい.njkがある場合は、一度 Nunjucksタスクを実行するとスムーズです。

# 一度 Nunjucksタスクを実行
npx gulp nunjucks
# その後監視を開始
npx gulp

あとがき

いやー、前回に引き続き、思ったよりだいぶ膨らんでしまいました…。途中で2回に分けることも考えましたが、どうしても繋がりがある話なのでうまく切れず。読みにくくてすみません。

HTMLの制作環境も、どんどん変わって来ましたね。黒い画面を見ない日はありません。
サイトのヘッダーフッターや共通部分を外部ファイル化する。それだけのために、PHPなどのサーバサイドを使うケースもあると思います。キャッシュシステムが備えられていればそれも良いと思いますが、モバイル環境が前提となる今、サーバサイドであれクライアントサイドであれ削れる負荷は削りたいところ。

HTMLを事前に生成するこの方法は、複数のHTMLファイルをサーバにアップする必要がある、という点が懸念材料として残りますが、それも「GulpでFTPする、GitでFTPする、Gitでサーバとリポジトリを同期する」といった方法で手動作業を回避することが出来ます。
引き出しに入れておけば色んな場面で活躍しそうですね。

補足:{%--%}って何?

記事中のテンプレートコードにあるタグの前後の-について

Nunjucksに限らず、テンプレートエンジンは変数とタグブロックの外にあるすべてのものをそのまま出力し、ファイル内のすべての空白をそのまま出力するため、生成されたHTMLに不要な空行ができてしまいます。この不要な空行をコントロールする方法がNunjucksには用意してあり、それが(マイナス記号)です。

  • {%-とした場合:タグの直のホワイトスペースが削除されます
  • -%}とした場合:タグの直のホワイトスペースが削除されます
  • {{-とした場合:変数ののホワイトスペースが削除されます
  • -}}とした場合:変数ののホワイトスペースが削除されます
Nunjucks : Whitespace Control

参考サイト)多謝!

Nunjucks + gulp で静的 HTML をモジュール化する
gulp+Nunjucksことはじめ
gulp + Nunjucksで快適な静的HTMLの開発環境を整える