Nunjucks + gulp で静的 HTML をモジュール化する
やりたいこと
概要
静的 HTML を複数ページ作成するときに、共通部分(ヘッダーとかフッターとか)をコピペしたくない。
「PHP とか使って include とかすればいいじゃん」
ていう意見もあるんだけど、動作確認に web サーバー立てるの面倒だし、単なる HTML で済むなら HTMLで 完結させたい。
細かい背景
gulp-ect を使って、やりたいことは大体実現出来ていたんだけど、以下の2つの理由により、別の方法に切り替えたいと思った。
やったこと(TL;DR)
さきに結論だけ書いておくと、
- Nunjucks というテンプレートエンジンの記法で、ファイルを作成
- gulp-nunjucks-render という gulp のプラグインを使って、HTML を出力
- gulp-data という微妙な名前のも併用
という方法で実現した。
技術の選定
全体的な話
そもそも、やりたいことを実現するものの、一般的な名称が分からず、ググッて情報を得るのに若干手こずった。色々ググった結果、やりたいことを実現するには、テンプレートエンジンを使った方法が一般的らしいということが分かった。
テンプレートエンジン?そう。JavaScript とか web フレームワークで HTML を生成するあれ。ただ、HTML 作成を仕事としているフロントエンド界隈の人たちの間では、JavaScript が馴染みがあるようで、JavaScript のテンプレートエンジンを使った方法が多かった。
でも、やりたい事は JS から HTML を生成することじゃなくて、HTML ファイルを生成すること。なので、JavaScript のテンプレートエンジン単体ではなくて、それを gulp から使えるようにする gulp-xxx というのを使うと良さそう。(元々使っていた gulp-ect の方法もそうだし。)
テンプレートエンジンの選定
テンプレートエンジンと言っても色々あるが、
- 機能的にそこそこ充実してること
- メンテされていること
- HTML ベースであること(HAML のような独自記法(っていうのか?)ではないこと)
を条件とした。
以下、色々なテンプレートエンジンと、NG 理由を簡単に記載する。
- Mustache (gulp-mustache) → logicless な分、機能が少ない
- Handlebars (gulp-handlebars etc.) → Mustache と同様
- Dust.js (gulp-dust etc.)→ 検討するのを忘れてた・・・
- Pug (gulp-pug, Pug の昔の名前は Jade) → 独自記法
- Twig.js (gulp-twig) → 特に不可はないが、Nunjucks の方が star 数が多かったので
- ECT (gulp-ect) → メンテされてない
gulp-xxx の選定
フロントエンド界隈に詳しくないので最初は分からなかったけど、テンプレートエンジンを gulp から使うためには gulp-xxx というのを使うんだけど、同じことをするモジュールがいくつかある。Nunjucks の場合も
- gulp-nunjucks
- gulp-nunjucks-render
というのがあった。後者は前者の fork ?っぽいので、gulp-nunjucks-render を使うことにした。
やったこと
前準備
NodeJS やら NPM やら Gulp は、適当にインストールして下さい。
gulp-nunjucks-render のインストール
これも npm install でおなしゃす。
HTML を *.njk に分割
拡張子は慣用的に *.njk とつけるっぽい。今回は、以下のようなファイル・ディレクトリ構成にした。
- dest: 出力先
- src: HTMLを生成するための部品
- layouts/default.njk: レイアウトファイル
- partials/: 共通部品
- common: 各言語共通
- ga.njk: Google Analytics のトラッキングコード
- header:njk: ヘッダー(サイトのロゴとか)
- en: 英語
- head.njk: <head></head> の中身
- footer.njk: フッター
- ja: 日本語
- head.njk
- footer.njk
- common: 各言語共通
- main: 各ページのファイル
- en: 英語
- page1.njk
- page2.njk
- …
- ja: 日本語
- page1.njk
- page2.njk
- …
- en: 英語
各 *.njk ファイルの中身は、後ほど説明する。
gulp タスク
こんな感じ。
var gulp = require('gulp'); var nunjucks = require('gulp-nunjucks-render'); var prettify = require('gulp-prettify'); var data = require('gulp-data'); var paths = { 'nunjucks': { 'srcRoot': '/path/to/root/src/', 'src': '/path/to/root/src/main/**/*.njk', 'dest': '/path/to/root/dest', } } // 説明は後述 function getDataForFile(file) { return { file: file }; } gulp.task('nunjucks', function() { gulp.src(paths.nunjucks.src) .pipe(data(getDataForFile)) // 説明は後述 .pipe(nunjucks({path: paths.nunjucks.srcRoot})) // 必要に応じて prettify とか beautify とか入れる //.pipe(prettify({ // indent_size : 4, // extra_liners : '' //})) .pipe(gulp.dest(paths.nunjucks.dest)); });
あとは、gulp タスクを実行すれば、HTML が生成される。
*.njk の書き方(Nunjucks の使い方)
色んな機能があるが、今回使ったもののみを記述。
layout から、各種部品を include する。
layouts/default.njk は以下の通り。
<!DOCTYPE HTML> <html lang="{{ lang }}"> <head prefix="og: http://ogp.me/ns# fb: http://ogp.me/ns/fb#"> {% include "partials/" + lang + "/head.njk" %} </head> <body> {% include "partials/common/ga.njk" %} <div class="l-container"> {% include "partials/common/header.njk" %} {% block content %}{% endblock %} {% include "partials/" + lang + "/footer.njk" %} </div> </body> </html>
見ればだいたい何をやっているか分かると思うが、include で、各種部品を include している。
include の際の基準となるパスは、gulp-nunjucks-render に path パラメーターとして渡した値となる。今回の gulp タスクの場合は、paths.nunjucks.srcRoot が基準のパスとなる。
その他、{% %} の中は、普通に JavaScript が使えるので、
{% include "partials/" + lang + "/footer.njk" %}
みたいなのも普通に使える。
block と extends
page1.njk や page2.njk などは、以下のようになっている。
{% set lang = "ja" %} {% set title = "タイトル" %} {% extends "layouts/default.njk" %} {% block content %} ページの中身 {% endblock %}
set はその名の通り変数の設定。設定された変数は、include された部品の中でも有効となる。
ちょっと分かりにくいのは、block と extends の2つ。まずは、上の方の layouts/default.njk を見ると
{% block content %}{% endblock %}
という部分がある。layouts/default.njk では、この部分は空欄だが、それを継承(extends)した page1.njk では、その部分の content として 「ページの中身」という文章を定義している。
プログラミング言語に例えれば、layouts/default.njk は、抽象クラスで、page1.njk はそれを継承して content の実装を提供している、という感じ。
“data” を渡す
今回、gulp-data というのも使っている。これは何をするものかというと、gulp タスクによって現在処理中の njk ファイルに関する情報を、gulp のパイプラインに追加する、といったもの。うまく説明出来ないので、詳細はドキュメントを参照。
これをどこで使っているかというと、head.njk で 以下のようなところで使っている。
<meta property="og:url" content="https:/example.com/{{ file.relative | replace(".njk", ".html") }}">
生成される HTML は以下のとおり。
<meta property="og:url" content="https:/example.com/en/page1.html">
file には、現在処理中の njk ファイル(例えば src/main/en/page1.njk )に関する情報が入っていて、それをテンプレートの中に埋め込むことが出来る。
まとめ・感想
Nunjucks は、Mozilla の人たちによって開発されている JavaScript のテンプレートエンジン。それを gulp タスクから使うことで、静的 HTML ファイルをモジュール化することが出来る。
感想としては、一度仕組みを作ってしまえば非常に便利。HTML ファイルの直し忘れとかが減るのでおすすめ。