Hugo 開發的實用知識
本文彙整開發 Hugo 時一定會用到,但是很難在文檔中找到,或是文檔根本就沒寫的知識,主要是在 Hugo 論壇 挖到的資訊。
Shortcode Markdown Notation
Shortcode 有兩種呼叫方式,{{< >}} {{% %}},區別是百分比符號的會在 Markdown 渲染前預先處理,因此有些奇怪的問題就要靠他解決,例如在 ToC 加上 icon。
Shortcode 出現換行
Trim space! Trim space! Trim space! Always remember to trim space!
Nested Shortcode
這是老生常談的問題,然而我也不是很懂他到底該怎麼做,因為如果這個問題很單純就不會有一堆人問,maintainer 回答也不會每次都是一長串對話。
頁面內容判斷
hasShortcode 可以判斷頁面是否使用指定 shortcode,而且在 .Content render 之前就可以判斷,但是 .Store 方式不行。
要 trigger .Content evaluate 可以使用 {{ $noop := .WordCount }} 完成。
循環引用
無解,只能用戶自行避免。我在多個官方說法都看到同樣的結論。
偵錯
{{ debug.Dump . }}{{ highlight (jsonify (dict "indent" " ") $MAP_VARIABLE) "json" }}{{ site.Store }}+{{ site.Get }}{{ printf "type: %T, val: %s" $VAL $VAL }}
效能
檢測:hugo --templateMetrics --templateMetricsHints
優化:通常都是頁面又 walk 一遍其他頁面導致 O(N^2) 複雜度,不然預設 O(N) 加上 Hugo 平行處理的機制沒什麼好優化了,頂多就是把檢測找到的複雜模板改用 partialCached 快取處理,然後資料結構基本觀念要有,不要大型數據還在用 range 跑。
條件判斷
and 和 or 都是短路判斷,cond 不是。
路徑判斷
專案設計應該永遠都使用 logical path(也就是檔案在 content 目錄的相對路徑)而不是 URL 判斷,因為 Hugo 有很強大的 URL 自訂功能。
行內 define
我不知道這實際上到底該叫什麼名字,因為論壇上所有人講的都不一樣。下方介紹行內 define 會分成資訊、總結和測試三個段落。
資訊
總之你可以在檔案內寫 define 作為一個 local scoped function 來使用,但是有幾個問題:
- 模板沒有「宣告」的先後順序問題,這個說法是錯的,下一點解釋。
- Maintainer 說:模板有 “parse time",也就是說所有動作執行之前會先解析所有 templates,等一下的測試會證實這點。
- 行內 define 的模板全局可用,因此要避免撞名。
- Maintainer 說:template 和 partial 除了支援 partialCached 以外基本沒差,主要是 template 語義上屬於 local macro。
- Maintainer 說:inline partial 應該永遠加上 .html suffix,但我有用和沒用感覺一樣。
總結
- 行內 define 無須擔心順序問題,請見下方測試。
- 行內 define 無順序問題,但是取用 .Store 內容還是要注意頁面渲染順序。
- 行內 define 用於 macro 用途,也就是單模板內部用的小工具,只是要避免撞名。
- partial 是 Hugo 做的工具,template 是 Go 原生。
- partial 和 template 的差別是 return / partialCached 功能,否則沒差。
- 至於為什麼這個有問題?我也不知道。
測試
define 順序測試
只需要這些檔案測試,所有測試都正常渲染沒有順序問題。
❯ tree
.
├── content
│ └── _index.md
├── layouts
│ ├── _partials
│ │ └── foo.html
│ └── home.html
└── hugo.toml
hugo.toml
baseURL = 'https://example.org/'
disableKinds = ["page", "taxonomy", "section", "term", "rss", "sitemap"]
_index.md:
+++
title = 'Home'
+++
home.html:
{{ partial "foo.html" . }}
foo.html:
Try render test1-1 with template: {{ template "test1-1" . }}
Try render test1-2 with partial: {{ partial "inline/test1-2" . }}
<br>
<br>
<br>
Try render test2-1 with template recursion: {{ template "test2-1" (dict "level" 1) }}
Try render test2-2 with partial recursion: {{ partial "inline/test2-2" (dict "level" 1) }}
{{- define "test1-1" -}}
test1-1<br>
{{- end -}}
{{- define "_partials/inline/test1-2" -}}
test1-2<br>
{{- end -}}
{{- define "test2-1" -}}
{{ $level := .level }}
{{ printf "Level %d<br>" $level | safeHTML }}
{{ if le $level 2 }}
{{ template "test2-1" (dict "level" (add $level 1)) }}
{{ end }}
{{- end -}}
{{- define "_partials/inline/test2-2" -}}
{{ $level := .level }}
{{ printf "Level %d<br>" $level | safeHTML }}
{{ if le $level 2 }}
{{ partial "inline/test2-2" (dict "level" (add $level 1)) }}
{{ end }}
{{- end -}}
自製額外輸出
全文教學請見 How to add llms.txt to a Hugo Blog 和 Adding llms.txt & markdown output to your Hugo site
Hugo 是模板語言因此輸出是預設的那些模板,要額外設定輸出,例如 JSON 文件或者 llms.txt,你要
- 建立 layouts/llms.txt
- Outputs 設定 home section 新增 llms
- outputFormats 設定 llms 區
有了這兩個範例,其他輸出類型就可以依樣畫葫蘆完成。
輸出 PDF
官方不支援,但是可以透過 resources.PostProcess + Pagedjs 自己做。
設定 syntax highlighting
有幾種方案完成。
markup 設定 noClasses true,就可以直接用 style 設定 Chroma styles
設定 noClasses false,然後 CSS 使用 hugo gen chromastyles 完成
hugo gen chromastyles --style=solarized-light | sed 's/\./html.dark ./' >> assets/css/chroma.css hugo gen chromastyles --style=onedark | sed 's/\./html:not(.dark) ./' >> assets/css/custom.css這設定了 html.dark 方式指定的 light mode/dark mode Chroma style。修改 sed 指令也可以套用到
data-theme方式的 dark mode。外部 JS,使用 highlight.js/prism.js,網路上教學很多,不贅述也不推薦,因為拖累客戶端的效能。
shiki syntax highlighting,這和外部 JS 一樣是修改已經構建完成的 HTML,最大的差別是 shiki 採用 VS Code 引擎因此更正確,還有 shiki 是伺服器端渲染,完全沒有客戶端效能問題。有兩種方案
- 使用 rehype 方案,速度很慢
- 自製 Node JS 腳本,速度可加快超過百倍
翻譯回退 (language fallback)
使用 module.mounts 完成,這是官方推薦做法,lang.Merge 只幫你找到頁面不會渲染。