深入 Hugo 模板系統
-
template和partial基本沒有差,template是 go-template 原生功能,partial是 hugo 在template之上又新增其他功能的方法。 -
最大的差別在於
template只能直接被渲染,不能被 return,也不能被當作變數儲存。 -
基本上永遠可以放心的使用
partial不會有任何問題,也沒有效能問題。 -
trim space
{{- -}}計算的空白發生在 lexer 解析階段,parser(語法樹)根本不知道有這件事發生過,只會在最初模板 lexer 階段快速的計算完成,模板渲染時根本不知道 trim space 的存在。資訊然後是 Claude 的整理:
來源檔案:src/text/template/parse/lex.go 原始碼註解直接寫明(第95~99行):
If the action begins
"{{- "rather than"{{", then all space/tab/newlines preceding the action are trimmed; conversely if it ends" -}}"the leading spaces are trimmed. This is done entirely in the lexer; the parser never sees it happen.偵測 trim marker:- 字元緊跟在
{{之後(或緊接在}}之前)才算數,而且規定 - 後面(或前面)一定要接一個 ASCII 空白字元(空白/tab/\r/\n)才會生效,例如{{-3}}不會被誤判成 trim 語法,因為 -3 是負數字面值,- 後面接的是數字不是空白,這樣的設計是為了消除語意上的歧義。左邊
{{-(trim 前面文字尾端的空白):在 lexText(掃描一般文字內容的函式)裡,掃到{{之後先檢查它後面是不是接著 trim marker(hasLeftTrimMarker),如果是,就呼叫 rightTrimLength,去算出前面那段文字尾端有多少空白字元,然後直接把詞法掃描的位置指標(l.pos)往回退那個長度,等於是在把這段文字內容當作一個 token 送出之前,先把尾端空白排除掉,不送進最終的文字 token 裡。 右邊-}}(trim 後面文字開頭的空白):對應邏輯在 atRightDelim 與後續掃描右邊界那段(leftTrimLength 算開頭空白長度),原理對稱:偵測到-}}時,先算出接下來文字開頭有多少空白,之後產生下一個文字 token 時,直接跳過那段空白,不放進 token 內容裡。底層都是靠 strings.TrimRight / strings.TrimLeft 對 " \t\r\n" 這組字元做長度計算(rightTrimLength/leftTrimLength),拿到「要修剪掉幾個字元」這個數字後,直接調整 lexer 內部的指標位置(l.pos)去跳過那段範圍,而不是先產生完整字串再去做字串處理。
一句話總結:
{{- -}}不是模板執行期的「格式化」或「後處理」,而是在文字被切成 token 之前,詞法掃描器就已經算好要吃掉前後多少空白,直接調整掃描指標跳過那段範圍——所以最終產生的語法樹裡,那些被 trim 掉的空白根本從未存在過,parser 和後續執行階段完全看不到它們曾經存在。 -
discourse.gohugo.io有一則評論是「trim space 之後網站速度得到大幅提升」,我無法重現這個說法。我在 hugo-yore v2.0.0 + Hugo v0.164.0 實測 trim 了幾千個 spaces,前後對比效能完全相同。