Hugo のショートコード内で Markdown を使う
Hugo のショートコード内の Markdown もレンダーさせる際にちょっとハマったのでメモです。
やりたいこと
よく技術系の Web サイトで以下のようなメモカード (一般的になんていうのかわからない) ってノーティス目的やワーニング目的で使ったりするじゃないですか。このサイトでも前回から使い始めています。
これを Hugo のショートコードで作って、カード内のコンテンツで Markdown を使えるようにしたかったのですが、ハマって色々調べたり試してみました。
結論
今回色々調べてはうまくいかなかったりと記事が長くなってしまったので、結論を先に書いておきます。
Hugo v0.100.0 以降でショートコード内で Markdown をレンダーするには config.toml
の markup.goldmark.renderer.unsafe
オプションを true
にし、ショートコードの呼び出しは {{% shortcode %}}
で行う。(検証できていませんが、v0.100.0 より前のバージョンでは config.toml
の markup.defaultMarkdownHandler
を blackfriday
にすればレンダーできるかもしれません)
順序付きリスト内でショートコード利用時に順序付きリストをブレイクさせないためには、ショートコード定義内でコンテンツを渡す際に、{{ .Inner -}}
と後ろにハイフンを付けて、空白を除去する。
詳細や、NG 事例は以下で紹介していますのでよかったら読んでください。
Shortcode とは
Hugo ではレイアウトファイルを使ってサイト自体のデザインを自由度高くカスタマイズできますが、コンテンツ内容は純粋に Markdown のパースおよび規定の HTML への置換と CSS でのデザインが限界です。
Hugo v0.62.0 以降は Markdown Render Hooks 機能が追加され、一部の要素についてカスタマイズが可能となりましたが、利用できる要素は限定的です。
そこで登場するのがショートコード機能です。ショートコード機能を使えば任意の要素をコンテンツ内に挿入することができます。また Hugo のビルトインショートコードも多く用意されており、ショートコードを利用することで、コンテンツデザインについても高い自由度が得られます。
ビルトインショートコードは Shortcodes | Hugo で紹介されています。
環境
- OS
- Windows 10 21H2
- Hugo
- v0.101.0
準備
自分のサイトだとテンプレートも自作しているので、ショートコードの問題なのかテンプレートの問題なのかわからなくなるので、Quick Start | Hugo に沿ってテスト用サイトを作り、既存テーマを適用します。
-
新しいサイトを作ります。
hugo new site shortcodetest
-
Ananke theme を適用します。
cd shortcodetest git init git submodule add https://github.com/theNewDynamic/gohugo-theme-ananke.git themes/ananke
-
テーマを
config.toml
に定義します。echo theme = "ananke" >> config.toml
ショートコード作成と使用
-
layouts/shortcodes/memocard.html
というファイルを作成し、以下のコードを記載します。<div class="memocard {{ .Get "label" }}"> {{ .Inner }} </div>
Create Your Own Shortcodes | Hugo に説明がありますが、ショートコードの定義は
layouts/shortcodes
ディレクトリ配下に HTML ファイルを作成し、定義します。 -
新しいコンテンツを作成します。
hugo new shortcodecontent.md
-
作成したコンテンツファイル
content/shortcodecontent.md
に以下を記載します。1. 順序付きリスト 1 `{{< shortcode >}}` でショートコード呼び出し。 {{< memocard label="warning" >}} Markdown は**レンダーされません**。HTML は<strong>レンダーされます</strong>。 {{< /memocard >}} 1. 順序付きリスト 2 `{{% shortcode %}}` でショートコード呼び出し。 {{% memocard label="warning" %}} Markdown は**レンダーされます**。HTML は<strong>レンダーされません</strong>。 {{% /memocard %}} 1. 順序付きリスト 3 順序付きリストがブレイクされてしまいます。
-
サーバーを起動します。
hugo server -D
-
ブラウザで
http://localhost:1313/shortcodecontent
にアクセスします。他の Hugo server を起動している場合、ポートは
1313
以外になりますので、適宜置き換えてください。
事象と対処
すでにコンテンツの中で事象を書いてしまっていますが、以下のような表示結果になってしまいます。
生成された HTML を見てみると以下のようになっています。
<ol>
<li>
<p>順序付きリスト 1</p>
<p><code>{{< shortcode >}}</code> でショートコード呼び出し。</p>
<div class="memocard warning">
Markdown は**レンダーされません**。HTML は<strong>レンダーされます</strong>。
</div>
<p></p>
</li>
<li>
<p>順序付きリスト 2</p>
<p><code>{{% shortcode %}}</code> でショートコード呼び出し。</p>
<!-- raw HTML omitted -->
<p>Markdown は<strong>レンダーされます</strong>。HTML は<!-- raw HTML omitted -->レンダーされません<!-- raw HTML omitted -->。</p>
</li>
</ol>
<!-- raw HTML omitted -->
<ol>
<li>
<p>順序付きリスト 3</p>
<p>順序付きリストがブレイクされてしまいます。</p>
</li>
</ol>
まず順序付きリスト 1 に記載したショートコードは {{< shortcode >}}
という記法で記載しています。この記法の場合、ショートコードに渡した Markdown はレンダーされないようです。ただ何故か HTML はレンダーされるようです。
The < character indicates that the shortcode’s inner content does not need further rendering. Often shortcodes without markdown include internal HTML - Shortcodes | Hugo
次に順序付きリスト 2 に記載したショートコードは {{% shortcode %}}
という記法で記載しています。この記法の場合、ショートコードに渡した Markdown はレンダーされるのですが、ショートコード定義に記載した HTML がすべて無視されています。生成された HTML にも <!-- raw HTML omitted -->
とコメントされています。加えて、順序付きリストがブレイクされ、順序付きリスト 3 の連番が 1 に戻ってしまっています。HTML でも <ol>
が分かれてしまっています。
In Hugo 0.55 we changed how the % delimiter works. Shortcodes using the % as the outer-most delimiter will now be fully rendered when sent to the content renderer. - Shortcodes | Hugo
ショートコード内の Markdown と HTML をレンダーさせる解決方法
HTML が無視される問題については、config.toml
に次のように記載すれば解消されます。
[markup]
[markup.goldmark]
[markup.goldmark.renderer]
unsafe = true
markup.goldmark.renderer.unsafe
オプションは コンテンツ内の HTML をレンダーさせるかを決めるオプションです。インライン JavaScript も埋め込み可能になりますので、セキュリティーの観点からデフォルトでは false
(=レンダーしない) になっています。
unsafe By default, Goldmark does not render raw HTMLs and potentially dangerous links. If you have lots of inline HTML and/or JavaScript, you may need to turn this on.
- Configure Markup | Hugo
しかし、ショートコードのためにセキュリティーリスクを高めてまで、markup.goldmark.renderer.unsafe
オプションを true
にするのも嫌ですよね。 (結論、この方法しかありませんでした。)
ショートコード内の Markdown と HTML をレンダーさせる NG その 1
{{< shortcode >}}
は Markdown はレンダーされないが HTML はレンダーされる、{{% shortcode %}}
は Markdown はレンダーされるが、HTML はレンダーされる。であれば、渡されたコンテンツをただ表示するだけのショートコードを作成しネストすればどうだ、と考え以下のようにやってみました。
コンテンツを表示するだけのショートコード (inner.html
):
{{ .Inner }}
ショートコードをネストさせる (順序付きリスト 2 のところ):
1. 順序付きリスト 1
`{{< shortcode >}}` でショートコード呼び出し。
{{< memocard label="warning" >}}
Markdown は**レンダーされません**。HTML は<strong>レンダーされます</strong>。
{{< /memocard >}}
1. 順序付きリスト 2
`{{% shortcode %}}` を `{{< shortcode >}}` でラップしてショートコード呼び出し。
{{< memocard label="warning" >}}
{{% inner %}}
Markdown は**レンダーされます**。HTML は<strong>レンダーされません</strong>。
{{% /inner %}}
{{< /memocard >}}
1. 順序付きリスト 3
順序付きリストがブレイクされてしまいます。
Markdown も HTML もレンダーされないどころか、<pre><code></code></pre>
でラップされてしまいました。
この挙動は上で書いた config.toml
の markup.goldmark.renderer.unsafe
を true
にしても同じです。
なお、ショートコード内に HTML を書いて {{< shortcode >}}
で呼び出した外側のショートコード内の HTML はちゃんとレンダーされています。
<li>
<p>順序付きリスト 2</p>
<div class="memocard warning">
<pre><code>Markdown は**レンダーされます**。HTML は<strong>レンダーされません</strong>。</code></pre>
</div>
</li>
いずれにしてもこのやり方は NG です。
ショートコード内の Markdown と HTML をレンダーさせる NG その 2
そもそも config.toml
の markup.goldmark.renderer.unsafe
オプションは Markdown レンダリングエンジンに Goldmark が採用されているからです。 Goldmark は Hugo v0.60.0 からデフォルトレンダリングエンジンとして採用されています。それ以前は BlackFriday というレンダリングエンジンが使われていました。ではレンダリングエンジンを BlackFriday に戻してやればいいのではと思いましたが…。
config.toml
の markup.defaultMarkdownHandler
を blackfriday
に変更。
[markup]
defaultMarkdownHandler = 'blackfriday'
レンダーしてみると Hugo v0.100.0 で Blackfriday は削除されたとのことで、以下のようなエラーが出てしまいます。
Failed to reload config: add site dependencies: create deps: markup: Configured defaultMarkdownHandler "blackfriday" not found. Did you mean to use goldmark? Blackfriday was removed in Hugo v0.100.0.
hugo v0.101.0-466fa43c16709b4483689930a4f9ac8add5c9f66 windows/amd64 BuildDate=2022-06-16T07:09:16Z VendorInfo=gohugoio
Reload Page
まあそもそもこのやり方は config.toml
の markup.goldmark.renderer.unsafe
を true
にするのと同義だと思いますので、あまり意味ないですね。
ショートコード内の Markdown と HTML をレンダーさせる NG その3
その 3 と言いますかそもそも Hugo のビルトインショートコードがたくさんあるので、その実装を見れば答えがあるのでは?と考えたのですが、そもそも Hugo のビルトインショートコードにコンテンツを渡すタイプのものがない (シンタックスハイライトぐらい)ということに気づきました。ショートコードに Markdown 渡してレンダーさせるの邪道なのか。
順序付きリストがブレイクされる問題の解決方法
さて、ショートコード内の Markdown と HTML 両方をレンダーさせる方法は config.toml
の markup.goldmark.renderer.unsafe
を true
にするしかなかったわけですが、もう一つ順序付きリストがブレイクされてしまう問題がありました。
こちらはショートコード HTML 内で .Inner
を渡すときにハイフン -
をつけることで解消されました。
<div class="memocard {{ .Get "label" }}">
{{ .Inner -}}
</div>
-
は空白を除去してくれるようです。
Go 1.6 includes the ability to trim the whitespace from either side of a Go tag by including a hyphen (
-
) and space immediately beside the corresponding{{
or}}
delimiter. - Introduction to Hugo Templating | Hugo
なお、両サイドに -
をつけると {{< shortcode >}}
でショートコードを呼び出したときと同じ挙動 (Markdown はレンダーされない。HTML はレンダーされる)になります。
<div class="memocard {{ .Get "label" }}">
{{- .Inner -}}
</div>
あとがき
ちょっと釈然としませんが、Hugo のショートコードで Markdown をレンダーする方法と、順序付きリスト内でショートコードを使ってもブレイクさせない方法を調査、紹介しました。今後は Markdown Render Hooks 機能がもっと追加されればショートコードの利用を最小限にできるのかなと思います。