.HasShortcode 関数
外部の JavaScript ファイルを利用して動作するショートコードを作成すると、Web サイトの表現力を大きく向上させることができます。 例えば、次のようなショートコードが考えられます。
- 独自の構文でコードを記述すると UML 図を出力してくれる
mermaid
ショートコード(mermaid.js などを利用) - TeX 構文でコードを記述すると数式を出力してくれる
math
ショートコード(MathJax.js などを利用)
このとき悩ましいのが、どのようにして次のような script
要素を出力するかです。
<script src="for-shortcode.min.js"></script>
すべてのページにこのようなコードを出力してしまうと、この JavaScript が必要ないページでもファイルの読み込みが発生してしまいます。 こういった拡張が増えてくると、大量の JavaScript ファイルが読み込まれることになり、重い Web サイトになってしまいます。
このような場合の救世主が Page.HasShortcode
関数です。
ページテンプレート内で、
{{ if .HasShortcode "my-shortcode" }}
...
{{ end }}
といった記述をしておくと、Markdown ファイル内で my-shortcode
ショートコードを使用している場合のみ出力を行うことができます。
実装例
例えば、ベーステンプレートの body
要素の末尾に次のように記述しておけば、Markdown ファイル内で mermaid
ショートコードを使用している場合のみ、mermaid.js
の読み込みと初期化処理を実行することができます。
...
{{- if .HasShortcode "mermaid" }}
<script src="https://cdn.jsdelivr.net/npm/mermaid/dist/mermaid.min.js"></script>
<script>mermaid.initialize({startOnLoad: true});</script>
{{- end }}
</body>
</html>
ちなみに、mermaid
ショートコードの実装は次のような感じになっています。
<div class="mermaid">
{{ .Inner }}
</div>
Markdown ファイルからは次のように呼び出します。
{{< mermaid >}}
sequenceDiagram
Client->>Cache: search cache
Cache-->>Client: cache
Client->>Repo: fetch data
Repo-->>Client: data
{{< /mermaid >}}
(応用)他のページを間接的に出力するとき
baseof.html での .HasShortcode がうまく動作しない例
Page.HasShortcode
関数は、あくまで現在処理しようとしているページの Markdown 内でショートコードが使われているかどうかを調べます。
例えば、ホームページテンプレートで、次のように最新記事の内容を間接的に取得して表示しているような場合は、baseof.html
テンプレートに記述した .HasShortcode
関数は意図通り動作しない可能性があります。
<!-- 最近の記事をいくつかまとめて表示 -->
{{- range first 3 .Site.RegularPages.ByLastmod.Reverse -}}
<article class="xArticle" itemscope itemtype="https://schema.org/BlogPosting">
{{ .Render "inc-article" }}
</article>
{{- end -}}
なぜなら、ホームページ (content/_index.md
) の Markdown ファイルではショートコードを使っていないのにもかかわらず(.HasShortcode
が false
になる)、上記ループで表示される別ページ内で JavaScript のロードを必要としていることがあるからです。
このようなケースに対応するには、上記のような Page
オブジェクトをループしている部分で、.HasShortcode
によるチェックを行わなければいけません。
ただし、ループ内で JavaScript ファイルをロードする script
要素を出力してしまうと、読み込みタイミングとしてはよろしくない(できれば body
の末尾がいい)し、読み込み用のコードが重複してしまいます。
解決案
解決方法はいろいろありそうですが、ひとつの解決方法としては、ショートコード内で、自分自身が必要とする JavaScript ファイル(下記の例では mermaid.min.js
)をロードする JavaScript コードを出力してしまうという方法です。
<div class="mermaid">
{{ .Inner }}
</div>
<script>
(function() {
const JS_FILE = 'https://cdn.jsdelivr.net/npm/mermaid/dist/mermaid.min.js';
// JSファイルの多重ロードを防止
const jsSet = window.loadedJsSet = window.loadedJsSet || new Set();
if (jsSet.has(JS_FILE)) return;
jsSet.add(JS_FILE);
// JSファイルの動的読み込み
const script = document.createElement('script');
script.src = JS_FILE;
script.onload = function() {
// 必要に応じてJSファイルロード後に初期化処理
mermaid.initialize({startOnLoad: true});
}
document.body.appendChild(script);
})();
</script>
すでにロードした JavaScript の情報は、グローバルな window.loadedJsSet
変数に格納しておくことで重複ロードを防いでいます。
この方法であれば、baseof.html
などのベーステンプレートで script
要素を出力する必要もなく、ショートコード用のファイルだけで完結できます。
ある意味 .HasShortcode
を使う方法よりもすっきりするかもしれません。
この方法の欠点としては、このショートコードを使うたびに同じ JavaScript コードが出力されてしまうという点でしょうか。 とはいえ、ページ内でのショートコードの呼び出し回数が、たかだか数回程度と想定できるのであれば、それほど気にしなくてもいいと思います。