開發 Hugo 專案
開發 Hugo 專案是用「Hugo 語言」進行開發,這些語法全都是 Hugo 獨有,且這些語法都是用 Golang 實作的1。Golang 雖然是靜態語言,但是 Hugo 語言本身其實像是動態語言,完全不需要宣告變數型態。
開發環境設定
- 模板語法高亮
- prettier + prettier-plugin-go-template,Hugo 官方的 gotmplfmt 目前還不好用
- .vscode/settings.json 設定語言解析
"files.associations": {"**/layouts/**/*.json": "html"},
- myhugofixer 避免 AI 開發使用 deprecated API
模板系統
沒接觸過模板的人可能有點搞不懂,就是 Hugo 透過這些模板直接渲染 HTML,因此 Hugo 模板沒有程式語言的那些上下文,會自己從模板順序依序往下渲染。Hugo 只在乎基礎模板,從 baseof.html 開始渲染,根據目錄結構找到 home.html(原 index.html)、page.html(原 single.html)等等,其他 partial 模板只是在呼叫時用到。
Hugo 透過 Go 語言的高併發特性多個頁面同時渲染,因此你無法設定不同頁面的頁面渲染順序,唯一能控制的只有輸出類型,你可以自定義 outputFormats 的 weight 以設定不同輸出類型的先後順序。
每個變數的 life cycle 只在當前模板,即使是同一個 HTML 頁面的不同模板,變數也會被清空,要在不同模板、不同頁面共享變數,唯一的方式只有 Store 方法。
模板查找
需要搞懂這幾個:
- 必須先理解前面講的頁面種類
- .Page.Path 也要搞懂,其實就是相對於 content 目錄的路徑
- Template types 是 Hugo 渲染頁面的基礎
- 現在你才能開始讀 template lookup order
- 相關的還有 New template system 和 output formats
模板使用
inline template/partial
請務必善用 inline template/partial 功能,這是 Hugo 唯一一個類似函式的東西。inline template 適用於同一檔案模組化和重用,雖然 inline template 全局可用,但是在全局用 inline template 會讓維護變的困難。
template and partial
partial 和 template 最大的差別是 partial 可以選擇回傳變數或者直接渲染 HTML 內容,template 只能直接渲染 HTML。
不推薦使用 partial 作為函式回傳變數,因為 partial 沒辦法清楚定義輸入輸出,也沒有 early-return/goto 等等機制,每個 partial 也限制只能有一個 return,這大幅限制維護和可讀性。
partialCached 不適用於 inline partial,只適用於獨立檔案的 partial。
block
block 定義模板後原地立刻執行,只是一個縮寫,使用頻率屈指可數的語法糖。
_markup 目錄
此目錄用於自訂特定元素的渲染管道 (render hook),例如圖片、heading 或程式碼塊等等。
變數
沒有要講什麼特別的,重要的是小寫 page site 可以直接存取目前頁面的 page 和 site,所以不要再寫一堆 dollar sign。
各種不同的 .Site 用法
取得全站物件有幾種方式
.Site$.Sitesite
前兩種一樣,多了 dollar sign 可以讓你從迴圈中取得「當前 template 的 context」,小寫的 site 則是「當前頁面的 site」,如果在迴圈或其他 context 語法裡面可能會取用到別的 site。
便箋 Scratch Pad
hugo.Store 和 .Page.Store 和 newScratch 都有 scratch pad 功能,文檔是這樣介紹的:
Returns a globally scoped “scratch pad” to store and manipulate data.
為什麼要寫這種奇怪的內容?直白的寫跨 template 儲存變數不是很好嗎?完美體現文檔到底有多爛。
讀取設定檔
Shortcode 語法
Shortcode 有兩種呼叫方式,standard notation {{< >}} 和 markdown notation {{% %}},區別是百分比符號的會在 「Markdown 文件本身渲染前」放到 Markdown,之後會跟著整個頁面一起再次被渲染,主要用於渲染 TOC 等頁面等級的功能。因為會再次被渲染,因此使用 {{% %}} 的 shortcode 內部不可使用 .Page.RenderString 或是 markdownify
Shortcode 出現換行
Trim space! Trim space! Trim space! Always remember to trim space!
Markdownify
我懶的找原文連結了,markdownify 現在就是 site.Home.RenderString 的 alias,文檔又不寫,到底有啥毛病?
和 .Page.RenderString 的差別是 scope,如果你的調用有頁面交互,那 markdownify 就不適用。
循環引用
無解,只能用戶自行避免。我在多個官方說法都看到同樣的結論。
偵錯
Hugo 除錯別無他法只能把變數印出來:
{{ $var }}{{ debug.Dump $var }}{{ highlight (jsonify (dict "indent" " ") $var) "json" }}{{ site.Store }}+{{ site.Get }}{{ printf "type: %T, val: %s" $var $var }}{{ warnf "%s $var" }}- 以上方案搭配 console.log 在瀏覽器印出
沒有任何其他方法,不用再想了。
條件判斷
and 和 or 都是短路判斷,cond 不是。
路徑判斷
專案設計應該永遠都使用 logical path(也就是檔案在 content 目錄的相對路徑)而不是 URL 判斷,因為 Hugo 有很強大的 URL 自訂功能,用硬編碼的 URL 判斷完全毀了這個功能。
自製額外輸出
Hugo 是模板語言因此輸出是預設的那些模板,要額外設定輸出,例如 JSON 文件或者 llms.txt,你要
- 建立 layouts/llms.txt
- Outputs 設定 home section 新增 llms
- outputFormats 設定 llms 區
全文教學請見 How to add llms.txt to a Hugo Blog 和 Adding llms.txt & markdown output to your Hugo site,有了這兩個範例,其他輸出類型就可以依樣畫葫蘆完成。
如果額外輸出沒有渲染連結,請建立該輸出專用的 link render hook,比如建立了 home.fuse-search.json,他的 render hook 應該用 render-link.fuse-search.json,規則請見文檔。
輸出 PDF
官方不支援,但是可以透過 resources.PostProcess + Pagedjs 自己做。
網站作者
請用官方推薦做法完成。建議完全放棄 .Params 設定網站作者,因為 .Params 方案無法設定多作者,未來要支援多作者就會造成出現兩套作者系統。
判斷頁面是否有 ToC
請用 {{ if in .TableOfContents "<li>" }} 判斷。
見微知著,Hugo 麻煩的地方就是連這種東西都要人發文問才知道最佳做法,而且所有人用法都不一樣,你就知道 Hugo 開發有多大的麻煩和混亂。我甚至看過有人直接比較字數,一個一個數空的 .TableOfContents 回傳 33 個字。
設定合併
Hugo 有些設定會深層合併,有些只有表層、有些不合併,見文檔。