跳过正文
  1. 文章/

Hugo 动态内容的利器——Content Adapters

·4 分钟
 Author
文森的科技小站
目录

Hugo 内容适配器(Content adapters)
#

内容适配器用于在构建站点时动态创建页面。常用于从远程 JSON/TOML/YAML/XML 等数据源生成页面(新增于 v0.126.0)。


概览
#

  • 内容适配器是模板类型的一种,但它们位于 content 目录,而不是 layouts
  • 每个目录(每种语言)最多一个内容适配器,命名为 _content.gotmpl(或带语言后缀的 _content.<lang>.gotmpl)。
  • 内容适配器创建的页面的逻辑路径(logical path)相对于内容适配器所在目录。

示例目录结构:

content/
├── articles/
│   ├── _index.md
│   ├── article-1.md
│   └── article-2.md
├── books/
│   ├── _content.gotmpl  <-- content adapter
│   └── _index.md
└── films/
    ├── _content.gotmpl  <-- content adapter
    └── _index.md

内容适配器使用与 layouts 模板相同的语法和函数,可在模板内调用下列方法来创建页面和资源。

注意:EnableAllDimensions 方法为 v0.153.0 新增。


方法(Methods)
#

在内容适配器中常用的方法:AddPage、AddResource、Site、Store、EnableAllLanguages、EnableAllDimensions。

AddPage
#

向站点添加页面。最小需要设置 path 字段,建议同时设置 title

示例(content/books/_content.gotmpl):

{{ $content := dict
  "mediaType" "text/markdown"
  "value" "The _Hunchback of Notre Dame_ was written by Victor Hugo."
}}
{{ $page := dict
  "content" $content
  "kind" "page"
  "path" "the-hunchback-of-notre-dame"
  "title" "The Hunchback of Notre Dame"
}}
{{ .AddPage $page }}

AddResource
#

向站点添加页面资源(例如封面图片)。示例:

{{ with resources.Get "images/a.jpg" }}
  {{ $content := dict
    "mediaType" .MediaType.Type
    "value" .
  }}
  {{ $resource := dict
    "content" $content
    "path" "the-hunchback-of-notre-dame/cover.jpg"
  }}
  {{ $.AddResource $resource }}
{{ end }}

在页面模板中使用资源示例(layouts/page.html):

{{ with .Resources.Get "cover.jpg" }}
  <img src="{{ .RelPermalink }}" width="{{ .Width }}" height="{{ .Height }}" alt="">
{{ end }}

Site
#

返回要将页面添加到的 Site 对象。

{{ .Site.Title }}

注意:从内容适配器内调用时,Site 尚未完全构建,像 .Site.Pages 这类依赖已构建页面集合的方法会报错。

Store
#

返回一个持久化的“便签”(scratch pad),可在多次执行间存取数据。主要用于在启用多语言(EnableAllLanguages)时在不同执行之间传递值。

{{ .Store.Set "key" "value" }}
{{ .Store.Get "key" }}

EnableAllLanguages
#

默认情况下,Hugo 只为站点矩阵(sites matrix)中第一个匹配的站点执行内容适配器一次。调用此方法可扩展到所有语言(保持当前 role 与 version)。

{{ .EnableAllLanguages }}
{{ $content := dict
  "mediaType" "text/markdown"
  "value" "The _Hunchback of Notre Dame_ was written by Victor Hugo."
}}
{{ $page := dict
  "content" $content
  "kind" "page"
  "path" "the-hunchback-of-notre-dame"
  "title" "The Hunchback of Notre Dame"
}}
{{ .AddPage $page }}

EnableAllDimensions (v0.153.0)
#

默认情况下内容适配器仅为 sites matrix 的第一个匹配项执行。EnableAllDimensions 用于扩展执行到语言、role、version 的所有可能组合(更细粒度控制可通过 front matter 中的 sites.matrix 或 content mount 定义)。


页面映射(Page map)
#

调用 AddPage 时传入的 map 可设置任意 front matter 字段(不包括 markup),若要指定标记类型请设置 content.mediaType。下表列出常用字段:

Key描述必需
content.mediaType内容的媒体类型,默认 text/markdown
content.value内容字符串值。
dates.date创建日期(time.Time)。
dates.expiryDate过期日期(time.Time)。
dates.lastmod最后修改时间(time.Time)。
dates.publishDate发布时间(time.Time)。
params页面参数 map。
path相对于内容适配器的页面逻辑路径(不要带前导斜杠或扩展名)。✔️
title页面标题。

提示:虽然 path 是唯一必需字段,但建议同时设定 title。Hugo 会将 path 自动规范化(例如 A B C -> /section/a-b-c)。


资源映射(Resource map)
#

AddResource 使用的 map 字段说明:

Key描述必需
content.mediaType资源的媒体类型。✔️
content.value字符串或资源对象(string 或 resource)。✔️
name资源名称。
params资源参数 map。
path相对于内容适配器的资源逻辑路径(不要带前导斜杠)。✔️
title资源标题。

说明:

  • content.value 为字符串时,Hugo 会为页面生成一个新资源,其发布路径相对于页面。
  • content.value 已经是一个 resource(比如通过 resources.GetRemote 得到的资源),Hugo 会直接使用该资源并按站点根发布,这种方式更高效。
  • 路径会被规范化,例如 A B C/cover.jpg -> /section/a-b-c/cover.jpg

示例:从远程数据生成书评页面
#

目标:从远程 JSON 创建每本书的页面,并为每本书添加封面资源。

步骤 1 — 创建内容结构:

content/
└── books/
    ├── _content.gotmpl  <-- content adapter
    └── _index.md

步骤 2 — 查看远程数据结构(示例):

步骤 3 — 创建内容适配器(content/books/_content.gotmpl):

{{/* Get remote data. */}}
{{ $data := dict }}
{{ $url := "<https://gohugo.io/shared/examples/data/books.json>" }}
{{ with try (resources.GetRemote $url) }}
  {{ with .Err }}
    {{ errorf "Unable to get remote resource %s: %s" $url . }}
  {{ else with .Value }}
    {{ $data = . | transform.Unmarshal }}
  {{ else }}
    {{ errorf "Unable to get remote resource %s: %s" $url }}
  {{ end }}
{{ end }}

{{/* Add pages and page resources. */}}
{{ range $data }}

  {{/* Add page. */}}
  {{ $content := dict "mediaType" "text/markdown" "value" .summary }}
  {{ $dates := dict "date" (time.AsTime .date) }}
  {{ $params := dict "author" .author "isbn" .isbn "rating" .rating "tags" .tags }}
  {{ $page := dict
    "content" $content
    "dates" $dates
    "kind" "page"
    "params" $params
    "path" .title
    "title" .title
  }}
  {{ $.AddPage $page }}

  {{/* Add page resource. */}}
  {{ $item := . }}
  {{ with $url := $item.cover }}
    {{ with try (resources.GetRemote $url) }}
      {{ with .Err }}
        {{ errorf "Unable to get remote resource %s: %s" $url . }}
      {{ else with .Value }}
        {{ $content := dict "mediaType" .MediaType.Type "value" .Content }}
        {{ $params := dict "alt" $item.title }}
        {{ $resource := dict
          "content" $content
          "params" $params
          "path" (printf "%s/cover.%s" $item.title .MediaType.SubType)
        }}
        {{ $.AddResource $resource }}
      {{ else }}
        {{ errorf "Unable to get remote resource %s: %s" $url }}
      {{ end }}
    {{ end }}
  {{ end }}

{{ end }}

步骤 4 — 创建页面模板(layouts/books/page.html):

{{ define "main" }}
  <h1>{{ .Title }}</h1>

  {{ with .Resources.GetMatch "cover.*" }}
    <img src="{{ .RelPermalink }}" width="{{ .Width }}" height="{{ .Height }}" alt="{{ .Params.alt }}">
  {{ end }}

  <p>Author: {{ .Params.author }}</p>

  <p>
    ISBN: {{ .Params.isbn }}<br>
    Rating: {{ .Params.rating }}<br>
    Review date: {{ .Date | time.Format ":date_long" }}
  </p>

  {{ with .GetTerms "tags" }}
    <p>Tags:</p>
    <ul>
      {{ range . }}
        <li><a href="{{ .RelPermalink }}">{{ .LinkTitle }}</a></li>
      {{ end }}
    </ul>
  {{ end }}

  {{ .Content }}
{{ end }}

多语言站点支持
#

有两种常见方法处理多语言内容适配器:

  1. 在一个内容适配器中使用 EnableAllLanguages 为所有语言生成内容。
  2. 为每种语言创建独立的内容适配器(按文件名或按目录)。

通过文件名区分语言
#

示例站点配置(按语言权重):

YAML / TOML / JSON 示例均可。然后在 content/books/ 下放置:

content/
└── books/
    ├── _content.de.gotmpl
    ├── _content.en.gotmpl
    ├── _index.de.md
    └── _index.en.md

通过内容目录区分语言
#

在配置中指定每个语言的 contentDir,例如:

示例目录结构:

content/
├── de/
│   └── books/
│       ├── _content.gotmpl
│       └── _index.md
└── en/
    └── books/
        ├── _content.gotmpl
        └── _index.md

页面冲突(Page collisions)
#

当两个或更多页面具有相同发布路径时会发生冲突(例如内容适配器生成的页面与目录中已有的 .md 文件路径相同)。由于构建过程是并发的,被发布页面的内容将不确定,无法控制处理顺序。

示例:

content/
└── books/
    ├── _content.gotmpl  <-- content adapter
    ├── _index.md
    └── the-hunchback-of-notre-dame.md

如果内容适配器也创建 books/the-hunchback-of-notre-dame,则该路径会冲突。构建时可使用 --printPathWarnings 标志来检测此类冲突。


参考与最后更新
#

  • 文档来源:Hugo 官方文档 - Content adapters(含示例与方法说明)

📖 阅读量: