Skip to main content

深入 Hugo 模板系統

  1. templatepartial 基本沒有差,template 是 go-template 原生功能,partial 是 hugo 在 template 之上又新增其他功能的方法。

  2. 最大的差別在於 template 只能直接被渲染,不能被 return,也不能被當作變數儲存。

  3. 基本上永遠可以放心的使用 partial 不會有任何問題,也沒有效能問題。

  4. 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 和後續執行階段完全看不到它們曾經存在。

  5. discourse.gohugo.io 有一則評論是「trim space 之後網站速度得到大幅提升」,我無法重現這個說法。我在 hugo-yore v2.0.0 + Hugo v0.164.0 實測 trim 了幾千個 spaces,前後對比效能完全相同。