環境
- 2019 の Intel Mac
- macOS 13.2 (22D49)
- node v18.14.0(brew でインストール)
- npm 9.3.1(brew でインストール)
- @11ty/eleventy: ^2.0.0(package.json)
発生している問題
レイアウトの孫階層にある Nunjucks の block に対して、テンプレート側で挿入する値を定義しても展開されない。
以下サンプル。
_includes/layouts/base.njk
<!DOCTYPE HTML>
<html lang="ja">
<head>
<meta charset="utf-8" />
<title>{% block title %}</title>
<meta name="viewport" content="width=device-width, initial-scale=1" />
</head>
<body>
{% include 'layouts/main.njk' %}
</body>
</html>
_includes/layouts/main.njk
<main>
{% block content %}
</main>
base.njk
が main.njk
を読み込む形になっている。
この base.njk をテンプレートで extends
して、各 block に値を設定する。
src/index.njk
{% extends 'layouts/base.njk' %}
{% block title %}
STICKY NOTES
{% endblock %}
{% block content %}
Hello, world!
{% endblock %}
テンプレートとレイアウトの関係としては
自分: index.njk
子: base.njk
孫: main.njk
こんなイメージ。
これを 11ty でビルドすると、以下のように出力される。
<!DOCTYPE HTML>
<html lang="ja">
<head>
<meta charset="utf-8" />
<title>STICKY NOTES</title>
<meta name="viewport" content="width=device-width, initial-scale=1" />
</head>
<body>
<main>
</main>
</body>
</html>
head
の title
は値が出力されているが、body
の main
に出力されるはずの Hello, world
が出力されない。
今使ってる実際のレイアウトは
<!DOCTYPE HTML>
<html lang="ja">
{% include 'layouts/head.njk' %}
{% include 'layouts/analytics/google.njk' %}
<body>
{% include 'layouts/header.njk' %}
{% include 'layouts/main.njk' %}
{% include 'layouts/footer.njk' %}
</body>
</html>
こんな感じなので、block 構文が動いてくれないと非常に困る。
11ty の Shortcode で解決する
11ty には Shortcode
という機能があり、以下のような構文で JS の関数を呼び出して出力を得ることができる。
Shortcode
{% func 'args1'[, 'args2' ...] %}
PairedShortcode
{% func 'args1'[, 'args2' ...] %}
{% endfunc %}
func
にはショートコード名を入れる。
PairedShortcode
は少し特殊で func
から endfunc
までの間の内容が、ショートコードの第一引数に渡ってくる。
今回はこの PairedShortcode
を使用して、block 構文の代わりになる機能を作る。
resources/js/11ty-shortcodes.js
const prjRoot = process.env.PWD; // プロセス実行元の絶対パス
const jsRoot = `${prjRoot}/resources/js`;
// namespace
var Shortcodes = Shortcodes || {};
(function (_ns) {
// content 格納領域
_ns.blocks = {};
// blocks に key-value の形で、key に content を紐づけて格納する
_ns.block = (content, key) => {
_ns.blocks[key] = content;
};
// blocks から指定された key の内容を取り出して応答する
_ns.renderBlock = (key) => {
const content = _ns.blocks[key] ?? '';
delete _ns.blocks[key];
return content;
};
})(Shortcodes)
module.exports = Shortcodes;
で、これを 11ty の Shortcode として使えるように登録する。
eleventy.config.js
const shortcodes = require('./resources/js/11ty-shortcodes');
module.exports = (eleventyConfig) => {
// ...
setupShortcodes(eleventyConfig);
// ...
};
function setupShortcodes(eleventyConfig) {
eleventyConfig.addPairedShortcode('_block', shortcodes.block);
eleventyConfig.addShortcode('_renderBlock', shortcodes.renderBlock);
}
他のショートコードと判別がややこしくなると嫌なので、_
をプレフィックスとして付与してる。
表示する内容を設定する _block
と、_block
で格納した内容を表示する _renderBlock
という構成。
先程のサンプルに置き換えて使ってみる。
使用例
_includes/layouts/base.njk
<!DOCTYPE HTML>
<html lang="ja">
<head>
<meta charset="utf-8" />
<title>{% _renderBlock 'title' %}</title>
<meta name="viewport" content="width=device-width, initial-scale=1" />
</head>
<body>
{% include 'layouts/main.njk' %}
</body>
</html>
_includes/layouts/main.njk
<main>
{% _renderBlock 'content' %}
</main>
src/index.njk
{% extends 'layouts/base.njk' %}
{% _block 'title' %}
STICKY NOTES
{% end_block %}
{% _block 'content' %}
Hello, world!
{% end_block %}
Nunjucks の block 構文と違い関数呼び出しのイメージなので、引数にあたる title
と content
は文字列として '
で括ってるので注意。
11ty でビルドした結果は以下のようになる。
<!DOCTYPE HTML>
<html lang="ja">
<head>
<meta charset="utf-8" />
<title>STICKY NOTES</title>
<meta name="viewport" content="width=device-width, initial-scale=1" />
</head>
<body>
<main>
Hello, world!
</main>
</body>
</html>
今度は無事に出力された。
これであれば階層がいくら深くなっても(11ty が解釈できる限り)関係ないので block の代わりにおすすめ。
まとめ
- block の階層問題は触り始めにぶちあたるので焦る
- PairedShortcode 便利
- よきショートコードが作れると楽しい
- namespace の件は趣味なので気にしないで
まとめじゃないな。
Shortcode と Filter はちょろちょろ作ってるので、便利なのがあればまた記事にする。