gulpでPugのコンパイル環境を構築します。
- Pugのコンパイル環境を作成
- pugタスクの自動実行
- watchタスクにキャッシュ機能を付ける
- 親子関係の解消
- 絶対パスを相対パスにする
Pugのコンパイル環境を作成
ファイル構成
src/pug/
をコンパイルしてpublic/
に出力する構成です。
├─ public/
│ └─ コンパイルされたHTML
│
├─ src/
│ └─ pug/
│ ├─ index.pug
│ ├─ service/
│ │ └─ index.pug
│ │
│ └─ _include/
│ ├─ _config.pug
│ ├─ _head.pug
│ ├─ _js.pug
│ └─ _layout.pug
│
├─ gulpfile.js
└─ package.json
pugファイルの作成
src/pugディレクトリに以下のファイルを作成します。
index.pug | トップページ |
service/index.pug | サービスページ |
_includes/_config.pug | 設定ファイル |
_includes/_head.pug | 共通パーツ <head> |
_includes/_js.pug | 共通パーツ JS |
_includes/_layout.pug | レイアウト |
動作確認を行うためのシンプルなファイル構成で、_includesディレクトリにはHTMLを出力しないファイルをまとめています。
//- 定数
- var SITE_URL = "https://localhost/";
- var SITE_NAME = "テスト";
//- 変数
- var lang = "ja";
- var description = "description";
- var ogType = "website";
- var ogUrl = SITE_URL + "assets/images/ogp.png";
meta(charset="UTF-8")
- title = (title ? title + " | " + SITE_NAME : SITE_NAME)
title #{title}
meta(name="description" content=description)
block css
link(rel="stylesheet" href=SITE_URL + "assets/css/style.css")
//- OG
meta(property="og:title" content=(ogTitle ? ogTitle + " | " + SITE_NAME : title))
meta(property="og:type" content=ogType)
meta(property="og:image" content=ogImage)
meta(property="og:url" content=ogUrl)
meta(property="og:description" content=description)
meta(property="og:site_name" content=SITE_NAME)
meta(property="og:locale" content=lang)
meta(property="fb:admins" content="")
meta(property="fb:app_id" content="")
block js
script(type="text/javascript" src=SITE_URL + "assets/js/common.min.js")
//- 設定読み込み
include _config
//- 設定の上書き
block variables
doctype html
html
head
//- head読み込み
include _head
body
div.container
main
//- コンテンツ
block content
//- JS読み込み
include _js
extend _includes/_layout
//- 設定の上書き
append variables
//- Require
- var title = 'ホーム';
- var description = 'ディスクリプション';
//- Optional
//- var ogType = '';
//- var ogTitle = '';
//- var ogUrl = '';
//- CSS追加
append css
link(rel="stylesheet" href=SITE_URL + "assets/css/layout-home.css")
//- JS追加
append js
script(type="text/javascript" src=SITE_URL + "assets/js/home.min.js")
//- コンテンツ
block content
p コンテンツ
extend ../_includes/_layout
//- 設定の上書き
append variables
//- Require
- var lang = 'en';
- var title = 'サービス';
- var description = 'サービスディスクリプション';
//- Optional
- var ogType = 'ogtest';
- var ogTitle = 'ogtitle';
- var ogUrl = 'ogurl';
//- コンテンツ
block content
p サービス
パッケージの追加
gulpを実行するために必要なパッケージを入れます。
yarn add -D gulp gulp-notify gulp-plumber gulp-debug
gulp-notify
はエラー通知。gulp-plumber
はエラーが発生した場合にタスクが停止するのを防止します。gulp-debug
は処理中のファイルをログに表示できます。
次はPugをコンパイルするために必要なパッケージを追加します。
yarn add -D gulp-pug gulp-html-beautify gulp-filter
gulp-pug
は今回の主役です。gulp-html-beautify
は出力するHTMLを綺麗に整形できます。gulp-filter
は_
から始まるファイル(前述の_include/_config.pugなど)をコンパイル対象外にするために使用します。gulp.src()
関数でも同じことができますが、後に設定する親子関係を解消するところで必須になるので先に入れておきます。
gulpfile.jsの作成と実行
gulpfile.jsにPugのコンパイルタスクを記述します。
const gulp = require('gulp');
const notify = require('gulp-notify'); // エラー通知
const plumber = require('gulp-plumber'); // エラー時のタスク停止防止
const debug = require('gulp-debug'); // ログ表示
const filter = require('gulp-filter'); // ファイルフィルター
const pug = require('gulp-pug'); // Pug
const htmlbeautify = require('gulp-html-beautify'); // HTML整形
const paths = {
pug: {
src: 'src/pug/**/*.pug', // コンパイル対象
dest: 'public/' // 出力先
}
}
/**
* pugタスク
*/
function pugCompile() {
return gulp.src(paths.pug.src)
.pipe(plumber({
errorHandler: notify.onError('Error: <%= error.message %>')
}))
.pipe(filter(function (file) { // _から始まるファイルを除外
return !/\/_/.test(file.path) && !/^_/.test(file.relative);
}))
.pipe(pug())
.pipe(htmlbeautify({
eol: '\n',
indent_size: 2,
indent_char: ' ',
indent_with_tabs: false,
end_with_newline: true,
preserve_newlines: true,
max_preserve_newlines: 2,
indent_inner_html: true,
brace_style: 'collapse',
indent_scripts: 'normal',
wrap_line_length: 0,
wrap_attributes: 'auto'
}))
.pipe(gulp.dest(paths.pug.dest))
.pipe(debug({title: 'pug dest:'}));
}
exports.pug = pugCompile; // pugタスク
exports.default = gulp.series(pugCompile); // defaultタスク
HTMLを整形するhtmlbeautify
のオプションは好みなので調整が必要かもしれません。
作成したタスクが正常に動作するか確認するため、ターミナルからyarn gulp
かyarn gulp pug
、環境によってはgulp
かgulp pug
でコンパイルできるか試してみます。
# defaultタスクの実行
yarn gulp
# pugタスクの実行
yarn gulp pug
正常に動作した場合は以下のファイルが出力されます。
- public/index.html
- public/service/index.html
これでPugのコンパイル環境は完成です。
pugタスクの自動実行
前述で作成したpugタスクを自動実行させるためにwatchタスクを作成します。
watchタスクはファイルを監視して、変更があった場合に指定したタスクを実行できます。
watchタスクの追加と実行
gulpfile.jsを以下のように変更します。
const gulp = require('gulp');
const notify = require('gulp-notify'); // エラー通知
const plumber = require('gulp-plumber'); // エラー時のタスク停止防止
const debug = require('gulp-debug'); // ログ表示
const filter = require('gulp-filter'); // ファイルフィルター
const pug = require('gulp-pug'); // Pug
const htmlbeautify = require('gulp-html-beautify'); // HTML整形
const paths = {
pug: {
src: 'src/pug/**/*.pug', // コンパイル対象
dest: 'public/' // 出力先
}
}
/**
* pugタスク
*/
function pugCompile() {
return gulp.src(paths.pug.src)
.pipe(plumber({
errorHandler: notify.onError('Error: <%= error.message %>')
}))
.pipe(filter(function (file) { // _から始まるファイルを除外
return !/\/_/.test(file.path) && !/^_/.test(file.relative);
}))
.pipe(pug())
.pipe(htmlbeautify())
.pipe(gulp.dest(paths.pug.dest))
.pipe(debug({title: 'pug dest:'}));
}
/**
* watchタスクで実行する関数
*/
function watch() {
return gulp.watch(paths.pug.src, gulp.series(pugCompile))
}
exports.pug = pugCompile; // pugタスク
exports.watch = gulp.series(watch); // watchタスク
exports.default = gulp.series(pugCompile); // defaultタスク
watchタスクで実行する関数(34-39行目)とwatchタスク(43行目)を追加しました。
監視対象はsrc/pug/**/*.pug
です。
ターミナルからyarn gulp watch
コマンドを実行すると監視状態になります。
# watchタスクの実行(停止:Ctrl + C)
yarn gulp watch
監視状態でsrc/pug/index.pug
や src/pug/service/index.pug
を変更して保存すると自動的にコンパイルされます。
ですが、どのファイルを変更しても全てのファイルがコンパイルされるので、次はキャッシュ機能を付けて、変更があったファイルのみコンパイルされるようにします。
watchタスクにキャッシュ機能を付ける
キャッシュ機能を付けるにはgulp-cached
パッケージを使用します。
また、キャッシュ機能はwatchタスクの時だけ動いてほしいので、条件分岐ができるgulp-if
パッケージも入れます。
パッケージの追加と実行
ターミナルから以下のコマンドを実行してパッケージを追加します。
yarn add -D gulp-cached gulp-if
gulpfile.jsを以下のように変更します。
const gulp = require('gulp');
const notify = require('gulp-notify'); // エラー通知
const plumber = require('gulp-plumber'); // エラー時のタスク停止防止
const debug = require('gulp-debug'); // ログ表示
const filter = require('gulp-filter'); // ファイルフィルター
const cached = require('gulp-cached'); // ファイルキャッシュ
const gulpif = require('gulp-if'); // 条件分岐
const pug = require('gulp-pug'); // Pug
const htmlbeautify = require('gulp-html-beautify'); // HTML整形
const paths = {
pug: {
src: 'src/pug/**/*.pug', // コンパイル対象
dest: 'public/' // 出力先
}
}
let isWatching = false; // watchタスクを動かしているか
/**
* pugタスク
*/
function pugCompile() {
return gulp.src(paths.pug.src)
.pipe(plumber({
errorHandler: notify.onError('Error: <%= error.message %>')
}))
.pipe(gulpif(isWatching, cached('pug'))) // watchタスク時にキャッシュ機能を使う
.pipe(filter(function (file) { // _から始まるファイルを除外
return !/\/_/.test(file.path) && !/^_/.test(file.relative);
}))
.pipe(pug())
.pipe(htmlbeautify())
.pipe(gulp.dest(paths.pug.dest))
.pipe(debug({title: 'pug dest:'}));
}
/**
* watchタスクで実行する関数
*/
function watch() {
isWatching = true;
return gulp.watch(paths.pug.src, gulp.series(pugCompile))
}
exports.pug = pugCompile; // pugタスク
exports.watch = gulp.series(watch); // watchタスク
exports.default = gulp.series(pugCompile); // defaultタスク
7,8行目でgulp-cached
とgulp-if
パッケージを読み込み、
19行目にwatchタスクの有無を判定するisWatching
変数を追加をしました。
watchタスク実行時に43行目でisWatching = true
を設定し、
29行目のgulpif
でisWatching == true
のときだけキャッシュ機能を使うようにしています。
ターミナルからyarn gulp watch
コマンドを実行して、src/pug/index.pug
かsrc/pug/service/index.pug
を変更すると、1回目はキャッシュがないので全てのファイルがコンパイルされ、2回目からは変更したファイルだけコンパイルされるようになります。
処理説明をすると実際はすべてのファイルが処理されていますが、キャッシュが行われたファイルに変更がない場合は29行目で処理が中断されます。
これでキャッシュ機能が正常に動いているのは確認できましたが、できれば1回目から変更したファイルだけコンパイルするのが理想的なので、先にキャッシュさせるように変更します。
先にキャッシュを行うように変更
gulpfile.jsを以下のように変更します。
const gulp = require('gulp');
const notify = require('gulp-notify'); // エラー通知
const plumber = require('gulp-plumber'); // エラー時のタスク停止防止
const debug = require('gulp-debug'); // ログ表示
const filter = require('gulp-filter'); // ファイルフィルター
const cached = require('gulp-cached'); // ファイルキャッシュ
const gulpif = require('gulp-if'); // 条件分岐
const pug = require('gulp-pug'); // Pug
const htmlbeautify = require('gulp-html-beautify'); // HTML整形
const paths = {
pug: {
src: 'src/pug/**/*.pug', // コンパイル対象
dest: 'public/' // 出力先
}
}
let isWatching = false; // watchタスクを動かしているか
/**
* pugタスク
*/
function pugCompile() {
return gulp.src(paths.pug.src)
.pipe(plumber({
errorHandler: notify.onError('Error: <%= error.message %>')
}))
.pipe(gulpif(isWatching, cached('pug'))) // watchタスク時にキャッシュ機能を使う
.pipe(filter(function (file) { // _から始まるファイルを除外
return !/\/_/.test(file.path) && !/^_/.test(file.relative);
}))
.pipe(pug())
.pipe(htmlbeautify())
.pipe(gulp.dest(paths.pug.dest))
.pipe(debug({title: 'pug dest:'}));
}
/**
* キャッシュ
*/
function pugCache(){
return gulp.src(paths.pug.src)
.pipe(cached('pug'))
.pipe(debug({title: 'pug cached:'}));
}
/**
* watchタスクで実行する関数
*/
function watch() {
isWatching = true;
return gulp.watch(paths.pug.src, gulp.series(pugCompile))
}
exports.pug = pugCompile; // pugタスク
exports.watch = gulp.series(pugCache, watch); // watchタスク
exports.default = gulp.series(pugCompile); // defaultタスク
39-46行目にpugファイルのキャッシュを行うpugCache
関数を追加し、57行目のwatchタスクでpugCache
関数を呼び出した後にwatch
関数を呼ぶように変更しました。
ターミナルからyarn gulp watch
コマンドを実行してsrc/pug/index.pug
かsrc/pug/service/index.pug
を変更して保存すると、対象のファイルだけコンパイルされるようになります。
ですが、src/pug/_includes/_layout.pug
を変更して保存するとどうでしょうか?コンパイルされないはずです。
これは30-32行目で設定しているgulp-filter
で、ファイル名が_
から始まるファイルはコンパイル対象外になるためです。
次はこの問題を解決します。
親子関係の解決
先にPugの構成を整理すると、src/pug/index.pug
の1行目はextend _includes/_layout
となっていて、src/pug/_includes/_layout.pug
を読み込んでいます。
読み込んでいる方を「子」にすると以下のようになります。
親 =src/pug/index.pug
子 =src/pug/_includes/_layout.pug
以下の作業を行えば、子に変更があると親のファイルがコンパイルされるようになります。
@unisharp/gulp-pug-inheritanceパッケージの追加と実行
ターミナルから以下のコマンドを実行してパッケージを追加します。
yarn add -D @unisharp/gulp-pug-inheritance
gulpfile.jsを以下のように変更します。
const gulp = require('gulp');
const notify = require('gulp-notify'); // エラー通知
const plumber = require('gulp-plumber'); // エラー時のタスク停止防止
const debug = require('gulp-debug'); // ログ表示
const filter = require('gulp-filter'); // ファイルフィルター
const cached = require('gulp-cached'); // ファイルキャッシュ
const gulpif = require('gulp-if'); // 条件分岐
const pug = require('gulp-pug'); // Pug
const htmlbeautify = require('gulp-html-beautify'); // HTML整形
const pugInheritance = require('@unisharp/gulp-pug-inheritance'); // 親子関係を解決
const path = require('path');
const paths = {
pug: {
src: 'src/pug/**/*.pug', // コンパイル対象
dest: 'public/' // 出力先
}
}
let isWatching = false; // watchタスクを動かしているか
/**
* pugタスク
*/
function pugCompile() {
return gulp.src(paths.pug.src)
.pipe(plumber({
errorHandler: notify.onError('Error: <%= error.message %>')
}))
.pipe(gulpif(isWatching, cached('pug'))) // watchタスク時にファイルをキャッシュさせる
.pipe(gulpif(isWatching, pugInheritance(paths.pug.src))) // 親子関係を解決
.pipe(filter(function (file) { // _から始まるファイルを除外
return !/\/_/.test(file.path) && !/^_/.test(file.relative);
}))
.pipe(pug())
.pipe(htmlbeautify())
.pipe(gulp.dest(paths.pug.dest))
.pipe(debug({title: 'pug dest:'}));
}
/**
* キャッシュ
*/
function pugCache(){
return gulp.src(paths.pug.src)
.pipe(cached('pug'))
.pipe(debug({title: 'pug cached:'}));
}
/**
* watchタスクで実行する関数
*/
function watch() {
isWatching = true;
return gulp.watch(paths.pug.src, gulp.series(pugCompile))
}
exports.pug = pugCompile; // pugタスク
exports.watch = gulp.series(pugCache, watch); // watchタスク
exports.default = gulp.series(pugCompile); // defaultタスク
11, 33行目を追記しました。
28行目のpugInheritance
関数の引数には、すべてのpugファイルを管理させる必要があるのでpaths.pug.src (src/pug/**/*.pug)
を指定しています。また、watchタスクのみで動作させるようにしています。
ターミナルからyarn gulp watch
コマンドを実行してsrc/pug/_includes/_layout.pug
を変更すると、親になっているsrc/pug/index.pug
とsrc/pug/service/index.pug
がコンパイルされます。
細かく動作確認するなら、src/pug/index.pug
の1行目をコメントアウトしてsrc/pug/_includes/_layout.pug
を変更すると、src/pug/service/index.pug
だけコンパイルされるはずです。
これでwatchタスクは完璧です。
最後は出力されたHTMLのscriptパスなどが絶対パスになっている部分の修正です。
絶対パスを相対パスにする
絶対パスを相対パスにする方法はいくつかあると思いますが、今回はgulpからpugにデータを渡して処理させる方法を使います。
gulpfile.js
で実行しているpug
のオプションにlocals
というオプションがあり、このlocals
に値を入れるとpugファイル内で使用することができます。
実装イメージは以下のようになります。
pug({
locals:{
キー: 値
}
})
このlocals
に処理中のpugファイルがルートから何階層目にあるか解析した結果を入れます。
ですが、そのまま書くと少し関数が長くなり可読性が落ちるためgulp-data
というパッケージを使います。
このパッケージを使って処理されたデータは、pugが自動でlocals
にマージします。
gulp-dataパッケージの追加と実行
ターミナルから以下のコマンドを実行してパッケージを追加します。
yarn add -D gulp-data
gulpfile.jsを以下のように変更します。
const gulp = require('gulp');
const notify = require('gulp-notify'); // エラー通知
const plumber = require('gulp-plumber'); // エラー時のタスク停止防止
const debug = require('gulp-debug'); // ログ表示
const filter = require('gulp-filter'); // ファイルフィルター
const cached = require('gulp-cached'); // ファイルキャッシュ
const gulpif = require('gulp-if'); // 条件分岐
const data = require('gulp-data'); // データオブジェクトの作成
const pug = require('gulp-pug'); // Pug
const htmlbeautify = require('gulp-html-beautify'); // HTML整形
const pugInheritance = require('@unisharp/gulp-pug-inheritance'); // 親子関係を解決
const path = require('path');
const paths = {
pug: {
src: 'src/pug/**/*.pug', // コンパイル対象
dest: 'public/' // 出力先
}
}
let isWatching = false; // watchタスクを動かしているか
/**
* pugタスク
*/
function pugCompile() {
return gulp.src(paths.pug.src)
.pipe(plumber({
errorHandler: notify.onError('Error: <%= error.message %>')
}))
.pipe(gulpif(isWatching, cached('pug'))) // watchタスク時にファイルをキャッシュさせる
.pipe(gulpif(isWatching, pugInheritance(paths.pug.src))) // 親子関係を解決
.pipe(filter(function (file) { // _から始まるファイルを除外
return !/\/_/.test(file.path) && !/^_/.test(file.relative);
}))
.pipe(data(file => { // データオブジェクトの作成
return {
hierarchy: path.relative(file.relative, '.').replace(/\.\.$/, '') || './' // 相対階層
}
}))
.pipe(pug())
.pipe(htmlbeautify({
eol: '\n',
indent_size: 2,
indent_char: ' ',
indent_with_tabs: false,
end_with_newline: true,
preserve_newlines: true,
max_preserve_newlines: 2,
indent_inner_html: true,
brace_style: 'collapse',
indent_scripts: 'normal',
wrap_line_length: 0,
wrap_attributes: 'auto'
}))
.pipe(gulp.dest(paths.pug.dest))
.pipe(debug({title: 'pug dest:'}));
}
/**
* キャッシュ
*/
function pugCache(){
return gulp.src(paths.pug.src)
.pipe(cached('pug'))
.pipe(debug({title: 'pug cached:'}));
}
/**
* watchタスクで実行する関数
*/
function watch() {
isWatching = true;
return gulp.watch(paths.pug.src, gulp.series(pugCompile))
}
exports.pug = pugCompile; // pugタスク
exports.watch = gulp.series(pugCache, watch); // watchタスク
exports.default = gulp.series(pugCompile); // defaultタスク
8行目にgulp-data
を追加し、
38-42行目に相対パスの階層./
や ../
などをhierarchy
プロパティに入れるようにしました。
次はこのheierarchy
プロパティをpugで処理します。
src/pug/_includes/_config.pug
に関数を追加します。
//- 定数
- var SITE_URL = "https://localhost/";
- var SITE_NAME = "テスト";
//- 変数
- var lang = "ja";
- var description = "description";
- var ogType = "website";
- var ogUrl = SITE_URL + "assets/images/ogp.png";
//- 相対パス
- function relativeUrl(url){ return hierarchy + url.replace(/(^\/)/, ''); }
11-12行目にrelativeUrl
関数を追加しました。
全体で使用しているpugを編集すると結果が分かりやすいので、src/pug/_includes/_js.pug
を以下のように編集します。
block js
script(type="text/javascript" src=relativeUrl("assets/js/common.min.js"))
これでターミナルからyarn gulp
をして、出力されたHTMLファイルのscript
部分を確認してみてください。public/index.html
ではsrc="./assets/js/common.min.js"
public/service/index.html
ではsrc="../assets/js/common.min.js"
になったでしょうか?
あとは同じ要領で他の部分も変更して完了です。
終わりに
gulpを使ったPugのコンパイル環境構築はできたでしょうか。
ファイル構成に関してベストとは言えませんが、今回の構成でもそれなりに使用できるので拡張してもらえたらと思います。
今回作成したファイルは以下からダウンロードできます。
コメント