Hugo 靜態站點生成器中的上下文和變量

已發表: 2022-03-10
快速總結↬在本文中,我們來看看流行的靜態站點生成器 Hugo 中的上下文和變量主題。 您將了解 Hugo 模板中的全局上下文、流控制和變量等概念,以及從內容文件通過模板到部分和基本模板的數據流。

在本文中,我們將仔細研究上下文在 Hugo 靜態站點生成器中的工作原理。 我們將研究數據如何從內容流向模板,某些構造如何改變可用數據,以及我們如何將這些數據傳遞給部分模板和基本模板。

本文不是對 Hugo 的介紹。 如果您對 Hugo 有一些經驗,您可能會從中獲得最大收益,因為我們不會從頭開始討論每個概念,而是專注於上下文和變量的主要主題。 但是,如果您自始至終參考 Hugo 文檔,即使沒有以前的經驗,您也可能能夠跟隨!

我們將通過建立一個示例頁面來研究各種概念。 不會詳細介紹示例站點所需的每個文件,但完整的項目可在 GitHub 上找到。 如果您想了解這些部分是如何組合在一起的,那麼這是一個很好的起點。 另請注意,我們不會介紹如何設置Hugo 站點或運行開發服務器 - 運行示例的說明在存儲庫中。

什麼是靜態站點生成器?

如果您對靜態站點生成器的概念不熟悉,這裡有一個快速介紹! 通過將靜態站點生成器與動態站點進行比較,也許可以更好地描述靜態站點生成器。 像 CMS 這樣的動態站點通常會為每次訪問從頭開始組裝一個頁面,可能會從數據庫中獲取數據並組合各種模板來完成此操作。 在實踐中,緩存的使用意味著頁面不會經常重新生成,但為了進行比較,我們可以這樣認為。 動態站點非常適合動態內容:經常更改的內容、根據輸入以多種不同配置呈現的內容以及站點訪問者可以操作的內容。

相比之下,許多網站很少改變並且很少接受訪問者的輸入。 應用程序的“幫助”部分、文章列表或電子書可能是此類站點的示例。 在這種情況下,更有意義的是在內容更改時將最終頁面組裝一次,然後為每個訪問者提供相同的頁面,直到內容再次更改。

動態站點具有更大的靈活性,但對運行它們的服務器提出了更高的要求。 它們也可能難以在地理上分佈,尤其是在涉及數據庫的情況下。 靜態站點生成器可以託管在任何能夠提供靜態文件的服務器上,並且易於分發。

今天,混合這些方法的常見解決方案是 JAMstack。 “JAM”代表 JavaScript、API 和標記,描述了 JAMstack 應用程序的構建塊:靜態站點生成器生成靜態文件以交付給客戶端,但堆棧具有運行在客戶端上的 JavaScript 形式的動態組件 —然後,此客戶端組件可以使用 API 向用戶提供動態功能。

雨果

Hugo 是一個流行的靜態站點生成器。 它是用 Go 編寫的,Go 是一種編譯型編程語言這一事實暗示了 Hugos 的一些優點和缺點。 一方面,Hugo非常快,這意味著它可以非常快速地生成靜態站點。 當然,這與使用 Hugo 創建的站點對最終用戶的快慢沒有關係,但對於開發人員而言,Hugo 在眨眼之間編譯甚至大型站點的事實是非常有價值的。

但是,由於 Hugo 是用編譯語言編寫的,因此很難對其進行擴展。 其他一些站點生成器允許您將自己的代碼(使用 Ruby、Python 或 JavaScript 等語言)插入到編譯過程中。 要擴展 Hugo,您需要將代碼添加到 Hugo 本身並重新編譯它- 否則,您會被 Hugo 開箱即用的模板函數所困擾。

雖然它確實提供了豐富多樣的功能,但如果您的頁面的生成涉及一些複雜的邏輯,這一事實可能會受到限制。 正如我們發現的那樣,如果一個網站最初是在動態平台上運行的,那麼您理所當然地認為可以放入自定義代碼的情況確實會越來越多。

我們的團隊維護著與我們的主要產品 Tower Git 客戶端相關的各種網站,我們最近考慮將其中一些網站轉移到靜態網站生成器中。 我們的一個網站,“學習”網站,看起來特別適合試點項目。 該站點包含各種免費學習材料,包括視頻、電子書和 Git 常見問題解答,以及其他技術主題。

它的內容主要是靜態的,並且任何交互功能(如通訊註冊)都已經由 JavaScript 提供支持。 2020 年底,我們將該站點從之前的 CMS 轉換為 Hugo ,今天它作為靜態站點運行。 自然地,我們在這個過程中學到了很多關於 Hugo 的知識。 這篇文章是分享我們學到的一些東西的一種方式。

我們的例子

由於本文源於我們將頁面轉換為 Hugo 的工作,因此將一個(非常!)簡化的假設登錄頁面作為示例似乎很自然。 我們的主要關注點將是一個可重用的所謂“列表”模板。

簡而言之,Hugo 將對包含子頁面的任何頁面使用列表模板。 Hugos 模板層次結構遠不止這些,但您不必實現所有可能的模板。 單個列表模板有很長的路要走。 它將用於任何需要列表模板但沒有更多專用模板可用的情況。

潛在的用例包括主頁、博客索引或常見問題解答列表。 我們的可重用列表模板將駐留在項目的layouts/_default/list.html中。 同樣,編譯我們的示例所需的其余文件都可以在 GitHub 上找到,您還可以在其中更好地了解這些部分是如何組合在一起的。 GitHub 存儲庫還附帶了一個single.html模板——顧名思義,這個模板用於沒有子頁面的頁面,但它們本身就是單個內容。

現在我們已經搭建好了舞台並解釋了我們將要做什麼,讓我們開始吧!

跳躍後更多! 繼續往下看↓

上下文或“點”

一切都從點開始。 在 Hugo 模板中,對象. ——“點”——指的是當前的上下文。 這是什麼意思? Hugo 中呈現的每個模板都可以訪問一組數據——它的上下文。 這最初設置為表示當前正在呈現的頁面的對象,包括其內容和一些元數據。 上下文還包括站點範圍的變量,例如配置選項和有關當前環境的信息。 您將使用.Title訪問當前頁面的標題等字段,並通過.Hugo.Version訪問正在使用的 Hugo 版本 - 換句話說,您正在訪問. 結構體。

重要的是,這種上下文可能會發生變化,使上面的“.Title”之類的引用指向其他內容,甚至使其無效。 例如,當您使用range遍歷某種集合時,或者當您將模板拆分為 partials 和 base templates時,就會發生這種情況。 稍後我們將詳細了解這一點!

Hugo 使用 Go 的“模板”包,所以當我們在本文中提到 Hugo 模板時,我們實際上是在談論 Go 模板。 Hugo 確實添加了很多標準 Go 模板中沒有的模板功能。

在我看來,上下文和重新綁定它的可能性是 Hugo 的最佳功能之一。 對我來說,總是讓“點”代表模板在某個特定點的主要焦點的任何對像是很有意義的,並在我繼續進行時根據需要重新綁定它。 當然,也有可能讓自己陷入混亂,但到目前為止我對它很滿意,以至於我很快就開始在我看過的任何其他靜態站點生成器中錯過它。

有了這個,我們已經準備好看看我們示例的簡單起點——下面的模板,位於我們項目的位置layouts/_default/list.html中:

 <html> <head> <title>{{ .Title }} | {{ .Site.Title }}</title> <link rel="stylesheet" href="/css/style.css"> </head> <body> <nav> <a class="logo" href="{{ "/" | relURL }}"> <img src="/img/tower-logo.svg"> <img src="/img/tower-claim.svg"> </a> <ul> <li><a href="/">Home</a></li> </ul> </nav> <section class="content"> <div class="container"> <h1>{{ .Title }}</h1> {{ .Content }} </div> </section> </body> </html>

大部分模板由基本的 HTML 結構、樣式錶鍊接、導航菜單和一些用於樣式的額外元素和類組成。 有趣的東西在花括號之間,它表示 Hugo 介入並發揮它的魔力,用評估某些表達式的結果替換大括號之間的任何內容,並可能操縱上下文。

你可能猜到了,標題標籤中的 {{ .Title {{ .Title }} } 指的是當前頁面的標題,而{{ .Site.Title }}指的是整個站點的標題,在 Hugo 配置中設置. 像{{ .Title }}這樣的標籤只是告訴 Hugo 用當前上下文中字段Title的內容替換該標籤。

因此,我們在模板中訪問了屬於該頁面的一些數據。 這些數據來自哪裡? 這就是下一節的主題。

內容和前沿

上下文中可用的一些變量由 Hugo 自動提供。 其他的都是我們定義的,主要是在內容文件中。 還有其他數據源,如配置文件、環境變量、數據文件甚至 API。 在本文中,我們將重點關注作為數據源的內容文件。

通常,單個內容文件代表單個頁面。 典型的內容文件包括該頁面的主要內容,還包括有關該頁面的元數據,例如其標題或創建日期。 Hugo 支持主要內容和元數據的多種格式。 在本文中,我們可能會使用最常見的組合:內容以 Markdown 形式提供在包含元數據的文件中,作為 YAML 前端。

在實踐中,這意味著內容文件以一個部分開頭,該部分由每端包含三個破折號的行分隔。 這部分構成了前端,這裡元數據使用key: value語法定義(我們很快就會看到,YAML 也支持更複雜的數據結構)。 前面的內容是使用 Markdown 標記語言指定的實際內容。

讓我們通過一個例子來使事情更具體。 這是一個非常簡單的內容文件,其中包含一個前端字段和一段內容:

 --- title: Home --- Home page of the Tower Git client. Over 100,000 developers and designers use Tower to be more productive!

(此文件位於我們項目中的content/_index.md中, _index.md表示具有子頁面的頁面的內容文件。同樣,GitHub 存儲庫清楚地說明了哪個文件應該去哪裡。)

使用之前的模板以及一些樣式和外圍文件(都可以在 GitHub 上找到)進行渲染,結果如下所示:

Tower Git 客戶端的主頁
(大預覽)

您可能想知道我們的內容文件前面的字段名稱是否是預先確定的,或者我們是否可以添加任何我們喜歡的字段。 答案是“兩者”。 有一個預定義字段列表,但我們也可以添加我們能想到的任何其他字段。 但是,這些字段在模板中的訪問方式略有不同。 雖然像title這樣的預定義字段可以簡單地作為.Title訪問,但像author這樣的自定義字段是使用.Params.author訪問的。

(有關預定義字段的快速參考,以及函數、函數參數和頁面變量等內容,請參閱我們自己的 Hugo 備忘單!)

.Content變量,用於訪問模板中內容文件的主要內容,是特殊的。 Hugo 有一個“短代碼”功能,允許您在 Markdown 內容中使用一些額外的標籤。 您也可以定義自己的。 不幸的是,這些簡碼只能通過.Content變量起作用——雖然您可以通過 Markdown 過濾器運行任何其他數據,但這不會處理內容中的簡碼。

關於未定義變量的注意事項:訪問像.Date這樣的預定義字段始終有效,即使您沒有設置它——在這種情況下將返回一個空值。 訪問未定義的自定義字段,如.Params.thisHasNotBeenSet也可以,返回一個空值。 但是,訪問像.thisDoesNotExist這樣的非預定義頂級字段將阻止站點編譯。

正如前面.Params.author以及.Hugo.version.Site.title所指出的,鍊式調用可用於訪問嵌套在其他數據結構中的字段。 我們可以在前面的內容中定義這樣的結構。 讓我們看一個例子,我們定義一個地圖或字典,在我們的內容文件中為頁面上的橫幅指定一些屬性。 這是更新後的content/_index.md

 --- title: Home banner: headline: Try Tower For Free! subline: Download our trial to try Tower for 30 days --- Home page of the Tower Git client. Over 100,000 developers and designers use Tower to be more productive!

現在,讓我們在模板中添加一個橫幅,使用.Params以上述方式引用橫幅數據:

 <html> ... <body> ... <aside> <h2>{{ .Params.banner.headline }}</h2> <p>{{ .Params.banner.subline}}</p> </aside> </body> </html>

這是我們的網站現在的樣子:

Tower Git 客戶端的主頁,橫幅上寫著“免費試用 Tower”
(大預覽)

好的! 目前,我們正在訪問默認上下文的字段,沒有任何問題。 但是,如前所述,此上下文不是固定的,而是可以更改的。

讓我們來看看這可能是如何發生的。

流量控制

流控制語句是模板語言的重要組成部分,它允許您根據變量的值、循環數據等執行不同的操作。 Hugo 模板提供了預期的構造集,包括用於條件邏輯的if/else和用於循環的range 。 在這裡,我們不會籠統地介紹 Hugo 中的流控制(有關更多信息,請參閱文檔),而是關注這些語句如何影響上下文。 在這種情況下,最有趣的語句是withrange

讓我們從with 。 該語句檢查某個表達式是否具有“非空”值,如果有,則重新綁定上下文以引用該表達式的值end標記表示with語句的影響停止的點,並且上下文被反彈到之前的任何位置。 Hugo 文檔將非空值定義為 false、0 以及任何長度為零的數組、切片、映射或字符串。

目前,我們的列表模板根本沒有做太多的列表。 列表模板以某種方式實際展示其某些子頁面可能是有意義的。 這為我們提供了一個很好的機會來舉例說明我們的流控制語句。

也許我們想在頁面頂部顯示一些特色內容。 這可以是任何內容——例如博客文章、幫助文章或食譜。 現在,假設我們的 Tower 示例站點有一些頁面突出了它的特性、用例、幫助頁面、博客頁面和“學習平台”頁面。 這些都位於content/目錄中。 我們通過在主頁的內容文件中添加一個字段來配置要顯示的內容, content/_index.md 。 該頁面由其路徑引用,假設內容目錄為根目錄,如下所示:

 --- title: Home banner: headline: Try Tower For Free! subline: Download our trial to try Tower for 30 days without limitations featured: /features.md ... --- ...

接下來,必須修改我們的列表模板以顯示該內容。 Hugo 有一個模板函數.GetPage ,它允許我們引用當前正在渲染的頁面對像以外的頁面對象。 回想一下上下文, . , 最初是否綁定到表示正在呈現的頁面的對象? 使用.GetPagewith ,我們可以暫時將上下文重新綁定到另一個頁面,在顯示我們的特色內容時引用頁面的字段:

 <nav> ... </nav> <section class="featured"> <div class="container"> {{ with .GetPage .Params.featured }} <article> <h2>{{ .Title }}</h2> {{ .Summary }} <p><a href="{{ .Permalink }}">Read more →</a></p> </article> {{ end }} </div> </section>

在這裡, withend標籤之間的{{ .Title }}{{ .Summary }}{{ .Permalink }}的是特色頁面中的那些字段,而不是正在呈現的主要字段。

除了有特色內容之外,讓我們在下面列出更多內容。 就像特色內容一樣,列出的內容片段將在我們主頁的內容文件content/_index.md中定義。 我們將像這樣將內容路徑列表添加到我們的前端(在這種情況下還指定部分標題):

 --- ... listing_headline: Featured Pages listing: - /help.md - /use-cases.md - /blog/_index.md - /learn.md ---

博客頁面有自己的目錄和_index.md文件的原因是博客會有自己的子頁面——博客文章。

為了在我們的模板中顯示這個列表,我們將使用range 。 不出所料,該語句將遍歷一個列表,但它也會依次將上下文重新綁定到列表的每個元素。 這對我們的內容列表非常方便。

注意,從 Hugo 的角度來看,“listing”只包含一些字符串。 對於“範圍”循環的每次迭代,上下文將綁定到其中一個字符串。 為了訪問實際的頁面對象,我們將其路徑字符串(現在是.的值)作為參數提供給.GetPage 。 然後,我們將再次使用with語句將上下文重新綁定到列出的頁面對象,而不是其路徑字符串。 現在,很容易依次顯示每個列出的頁面的內容:

 <aside> ... </aside> <section class="listing"> <div class="container"> <h1>{{ .Params.listing_headline }}</h1> <div> {{ range .Params.listing }} {{ with $.GetPage . }} <article> <h2>{{ .Title }}</h2> {{ .Summary }} <p><a href="{{ .Permalink }}">Read more →</a></p> </article> {{ end }} {{ end }} </div> </div> </section>

這是該網站此時的樣子:

Tower Git 客戶端的主頁顯示四個特色頁面和橫幅
(大預覽)

但是等一下,上面的模板中有一些奇怪的東西——我們不是調用.GetPage ,而是調用$.GetPage 。 你能猜到為什麼.GetPage不起作用嗎?

符號.GetPage表示GetPage函數是當前上下文的方法。 確實,在默認上下文中,有這樣一個方法,但我們只是繼續前進並更改了上下文! 當我們調用.GetPage時,上下文被綁定到一個沒有那個方法的字符串。 我們解決這個問題的方法是下一節的主題。

全球背景

如上所示,在某些情況下上下文已更改,但我們仍希望訪問原始上下文。 在這裡,這是因為我們想要調用一個存在於原始上下文中的方法——另一種常見的情況是當我們想要訪問正在呈現的主頁的某些屬性時。 沒問題,有一個簡單的方法可以做到這一點。

在 Hugo 模板中, $稱為全局上下文,指的是上下文的原始值——模板處理開始時的上下文。 在上一節中,它用於調用.GetPage方法,即使我們已將上下文重新綁定到字符串。 現在,我們還將使用它來訪問正在呈現的頁面的字段。

在本文開頭,我提到我們的列表模板是可重用的。 到目前為止,我們只將它用於主頁,呈現位於content/_index.md的內容文件。 在示例存儲庫中,還有另一個將使用此模板呈現的內容文件: content/blog/_index.md 。 這是博客的索引頁面,就像主頁一樣,它顯示了一個特色內容並列出了更多——在這種情況下是博客文章。

現在,假設我們希望在主頁上顯示列出的內容略有不同- 不足以保證單獨的模板,但我們可以在模板本身中使用條件語句來做一些事情。 例如,如果我們檢測到我們正在呈現主頁,我們將在兩列網格中顯示列出的內容,而不是單列列表。

Hugo 帶有一個頁面方法, .IsHome ,它提供了我們需要的功能。 當我們發現我們在主頁上時,我們將通過向各個內容片段添加一個類來處理表示中的實際變化,讓我們的 CSS 文件完成剩下的工作。

當然,我們可以將類添加到 body 元素或一些包含元素,但這不能很好地演示全局上下文。 當我們為列出的內容編寫 HTML 時, . 指的是列出的頁面,但需要在正在呈現的主頁上調用IsHome 。 全球背景來拯救我們:

 <section class="listing"> <div class="container"> <h1>{{ .Params.listing_headline }}</h1> <div> {{ range .Params.listing }} {{ with $.GetPage . }} <article{{ if $.IsHome }} class="home"{{ end }}> <h2>{{ .Title }}</h2> {{ .Summary }} <p><a href="{{ .Permalink }}">Read more →</a></p> </article> {{ end }} {{ end }} </div> </div> </section>

博客索引看起來就像我們的主頁一樣,儘管內容不同:

Tower Git 客戶端的主頁顯示四個特色頁面和不同內容的橫幅
(大預覽)

…但是我們的主頁現在以網格形式顯示其特色內容:

Tower Git 客戶端的主頁顯示了兩行兩列中的四個特色頁面,而不是像以前那樣所有四個都在彼此的頂部
(大預覽)

部分模板

在建立一個真實的網站時,將您的模板分成幾部分很快就會變得有用。 也許您想重用模板的某些特定部分,或者您只是想將一個龐大、笨重的模板拆分成連貫的部分。 為此,Hugo 的部分模板是可行的方法。

從上下文的角度來看,這裡重要的是,當我們包含一個部分模板時,我們明確地將我們想要提供給它的上下文傳遞給它。 一種常見的做法是在包含部分時傳遞上下文,如下所示: {{ partial "my/partial.html" . }} {{ partial "my/partial.html" . }} 。 如果這裡的點是指正在呈現的頁面,那將是傳遞給部分的; 如果上下文已經反彈到其他東西,那就是傳遞下來的東西。

當然,您可以像在普通模板中一樣在部分模板中重新綁定上下文。 在這種情況下,全局上下文$是指傳遞給部分的原始上下文,而不是正在呈現的主頁面(除非這是傳入的內容)。

如果我們希望部分模板能夠訪問某些特定的數據,如果我們只將它傳遞給部分模板,我們可能會遇到問題。 還記得我們之前在重新綁定上下文後訪問頁面方法的問題嗎? partials也是如此,但在這種情況下,全局上下文無法幫助我們——如果我們已經將字符串傳遞給部分模板,那麼部分中的全局上下文將引用該字符串,我們贏了'不能調用頁面上下文中定義的方法。

解決這個問題的方法是在包含部分時傳入多條數據。 但是,我們只允許為部分調用提供一個參數。 但是,我們可以將此參數設為複合數據類型,通常是映射(在其他編程語言中稱為字典或散列)。

例如,在此映射中,我們可以將Page鍵設置為當前頁面對象,以及用於傳入任何自定義數據的其他鍵。然後頁面對象將作為部分中的.Page可用,而另一個類似地訪問地圖的值。 使用dict模板函數創建映射,該函數接受偶數個參數,交替解釋為鍵、其值、鍵、其值等。

在我們的示例模板中,讓我們將特色和列出內容的代碼移動到部分中。 對於特色內容,傳入特色頁面對象就足夠了。 但是,除了要呈現的特定列出的內容之外,列出的內容還需要訪問.IsHome方法。 如前所述,雖然.IsHome在列出的頁面的頁面對像上也可用,但這不會給我們正確的答案——我們想知道正在呈現的主頁是否是主頁。

相反,我們可以將一個布爾集傳遞給調用.IsHome的結果,但將來部分可能需要訪問其他頁面方法——讓我們傳入主頁對像以及列出的頁面對象。 在我們的示例中,主頁位於$中,列出的頁面位於. . 因此,在傳遞給listed的部分的映射中,鍵Page獲取值$而鍵“Listed”獲取值. . 這是更新後的主模板:

 <body> <nav> <a class="logo" href="{{ "/" | relURL }}"> <img src="/img/tower-logo.svg"> <img src="/img/tower-claim.svg"> </a> <ul> <li><a href="/">Home</a></li> <li><a href="/blog/">Blog</a></li> </ul> </nav> <section class="featured"> <div class="container"> {{ with .GetPage .Params.featured }} {{ partial "partials/featured.html" . }} {{ end }} </div> </section> <section class="content"> <div class="container"> <h1>{{ .Title }}</h1> {{ .Content }} </div> </section> <aside> <h2>{{ .Params.banner.headline }}</h2> <p>{{ .Params.banner.subline}}</p> </aside> <section class="listing"> <div class="container"> <h1>{{ .Params.listing_headline }}</h1> <div> {{ range .Params.listing }} {{ with $.GetPage . }} {{ partial "partials/listed.html" (dict "Page" $ "Listed" .) }} {{ end }} {{ end }} </div> </div> </section> </body>

與列表模板的一部分相比,我們的“精選”部分內容沒有變化:

 <article> <h2>{{ .Title }}</h2> {{ .Summary }} <p><a href="{{ .Permalink }}">Read more →</a></p> </article>

然而,我們對列出的內容的部分反映了這樣一個事實,即原始頁面對象現在位於.Page中,而列出的內容片段位於.Listed中:

 <article{{ if .Page.IsHome }} class="home"{{ end }}> <h2>{{ .Listed.Title }}</h2> {{ .Listed.Summary }} <p><a href="{{ .Listed.Permalink }}">Read more →</a></p> </article>

Hugo 還提供了基本模板功能,可以讓你擴展一個通用的基本模板,而不是包含子模板。 在這種情況下,上下文的工作方式類似:在擴展基本模板時,您提供的數據將構成該模板中的原始上下文。

自定義變量

也可以在 Hugo 模板中分配和重新分配您自己的自定義變量。 這些將在聲明它們的模板中可用,但除非我們明確傳遞它們,否則它們不會進入任何部分或基本模板。 在“塊”內聲明的自定義變量(如if語句指定的變量)僅在該塊內可用 - 如果我們想在塊外引用它,我們需要在塊外聲明它,然後在塊內修改它根據需要阻止。

自定義變量的名稱以美元符號 ( $ )為前綴。 要聲明一個變量並同時給它一個值,請使用:=運算符。 對變量的後續賦值使用=運算符(不帶冒號)。 一個變量在被聲明之前不能被賦值,也不能在沒有給它一個值的情況下被聲明。

自定義變量的一個用例是通過將一些中間結果分配給適當命名的變量來簡化長函數調用。 例如,我們可以將特色頁面對象分配給名為$featured的變量,然後將該變量提供給with語句。 我們還可以將要提供給“列出的”部分的數據放在一個變量中,並將其提供給部分調用。

以下是我們的模板在進行這些更改後的樣子:

 <section class="featured"> <div class="container"> {{ $featured := .GetPage .Params.featured }} {{ with $featured }} {{ partial "partials/featured.html" . }} {{ end }} </div> </section> <section class="content"> ... </section> <aside> ... </aside> <section class="listing"> <div class="container"> <h1>{{ .Params.listing_headline }}</h1> <div> {{ range .Params.listing }} {{ with $.GetPage . }} {{ $context := (dict "Page" $ "Listed" .) }} {{ partial "partials/listed.html" $context }} {{ end }} {{ end }} </div> </div> </section>

根據我對 Hugo 的經驗,我建議您在嘗試在模板中實現一些更複雜的邏輯時自由地使用自定義變量。 雖然嘗試保持代碼簡潔是很自然的,但這很容易使事情變得不那麼清晰,從而使您和其他人感到困惑。

相反,為每個步驟使用描述性命名的變量,不要擔心使用兩行(或三行或四行等)來做。

。刮

最後,讓我們介紹一下.Scratch機制。 在早期版本的 Hugo 中,自定義變量只能分配一次; 無法重新定義自定義變量。 如今,可以重新定義自定義變量,這使得.Scratch不那麼重要,儘管它仍然有它的用途。

簡而言之, .Scratch是一個允許您設置和修改自己的變量(如自定義變量)的暫存區。 與自定義變量不同, .Scratch屬於頁面上下文,因此將上下文傳遞給部分,例如,將自動帶上暫存變量。

您可以通過調用其方法SetGet來設置和檢索.Scratch上的變量。 還有比這些更多的方法,例如設置和更新復合數據類型,但這兩種方法足以滿足我們的需求。 Set兩個參數:要設置的數據的鍵和值。 Get只需要一個:您要檢索的數據的密鑰。

之前,我們使用dict創建了一個 map 數據結構,將多條數據傳遞給一個 partial。 這樣做是為了讓列出的頁面的部分可以訪問原始頁面上下文和特定的列出頁面對象。 使用.Scratch不一定是更好或更壞的方式來做到這一點 - 哪個更可取可能取決於具體情況。

讓我們看看使用.Scratch而不是dict將數據傳遞給部分的列表模板會是什麼樣子。 我們調用$.Scratch.Get (再次使用全局上下文)將暫存變量“listed”設置為. - 在這種情況下,列出的頁面對象。 然後我們只將頁面對象$傳遞給部分。 臨時變量將自動跟隨。

 <section class="listing"> <div class="container"> <h1>{{ .Params.listing_headline }}</h1> <div> {{ range .Params.listing }} {{ with $.GetPage . }} {{ $.Scratch.Set "listed" . }} {{ partial "partials/listed.html" $ }} {{ end }} {{ end }} </div> </div> </section>

這也需要對listed.html部分進行一些修改——原始頁面上下文現在可以作為“點”使用,而列出的頁面是從.Scratch對像中檢索的。 我們將使用自定義變量來簡化對所列頁面的訪問:

 <article{{ if .IsHome }} class="home"{{ end }}> {{ $listed := .Scratch.Get "listed" }} <h2>{{ $listed.Title }}</h2> {{ $listed.Summary }} <p><a href="{{ $listed.Permalink }}">Read more →</a></p> </article>

以這種方式做事的一個論據是一致性。 使用.Scratch ,您可以養成始終將當前頁面對像傳遞給任何部分的習慣,將任何額外的數據添加為臨時變量。 然後,無論何時您編寫或編輯您的分部,您都知道. 是一個頁面對象。 當然,您也可以使用傳入的映射為自己建立一個約定:例如,始終將頁面對像作為.Page發送。

結論

當涉及到上下文和數據時,靜態站點生成器既有好處也有局限性。 一方面,在每次頁面訪問時運行效率太低的操作在頁面編譯時只運行一次可能會非常好。 另一方面,即使在主要是靜態站點上,訪問網絡請求的某些部分的頻率也會讓您感到驚訝。

處理查詢字符串參數,例如,在靜態站點上,您必須求助於 JavaScript 或一些專有解決方案,如 Netlify 的重定向。 這裡的重點是,雖然從動態站點跳轉到靜態站點在理論上很簡單,但確實需要轉變思維方式。 一開始,很容易回到你的舊習慣,但練習會變得完美。

至此,我們結束了對 Hugo 靜態站點生成器中的數據管理的了解。 Even though we focused only on a narrow sector of its functionality, there are certainly things we didn't cover that could have been included. Nevertheless, I hope this article gave you some added insight into how data flows from content files, to templates, to subtemplates and how it can be modified along the way.

Note : If you already have some Hugo experience, we have a nice resource for you, quite appropriately residing on our aforementioned, Hugo-driven “Learn” site! When you just need to check the order of the arguments to the replaceRE function, how to retrieve the next page in a section, or what the “expiration date” front matter field is called, a cheat sheet comes in handy. We've put together just such a reference, so download a Hugo cheat sheet, in a package also featuring a host of other cheat sheets on everything from Git to the Visual Studio Code editor.

延伸閱讀

If you're looking for more information on Hugo, here are some nice resources:

  • The official Hugo documentation is always a good place to start!
  • A great series of in-depth posts on Hugo on Regis Philibert's blog.