STICKY NOTES

11ty でレイアウトの階層が深くなると block が使えない問題

2023-03-02
2023-03-02
yagasuke
eleven

SUMMARY

11ty で使用できる Nunjucks では block 構文がサポートされており、例えばレイアウト内に設置された block に対し、レイアウトを読み込んだテンプレート側で block が配置された位置に好きな文言を差し込んだりできる。
ただ、レイアウトが階層化されていると、孫階層から先は block 構文が動作しなくなってしまうため、11ty の Shortcode を使って解決する。

環境

  • 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.njkmain.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>

headtitle は値が出力されているが、bodymain に出力されるはずの 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 構文と違い関数呼び出しのイメージなので、引数にあたる titlecontent は文字列として ' で括ってるので注意。
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 はちょろちょろ作ってるので、便利なのがあればまた記事にする。