Hugo 模板类型完全指南:Base、Single、List、Partial、Render Hook 与 Shortcode 的实战与源码解析
【免费下载链接】hugoThe world’s fastest framework for building websites.项目地址: https://gitcode.com/gh_mirrors/hu/hugo
本篇技术指南以 Hugo 官方文档《Template types》为骨架,系统讲解 Hugo 模板体系的全部类型——从作为页面骨架的 Base 模板,到渲染具体内容的 Page/Single、列表类模板 Section/List/Taxonomy/Term,再到组件化的 Partial、View 模板,以及覆盖 Markdown 转换过程的 Render Hook 和从内容页调用的 Shortcode。结合当前 Hugo 仓库的源码(如 tpl/tplimpl/templatestore.go、hugolib/template_test.go)与文档站自身的模板布局(docs/layouts),你将掌握每种模板类型的作用、命名规范、回退机制与底层查找原理,能够为任意页面创建最精确匹配的模板。
模板的存放结构与项目布局
所有模板都创建在项目根目录的layouts目录下。虽然一个站点不一定需要用到下面每一种模板,但下面这个例子是中等复杂度站点的典型结构(来自官方文档《Template types》):
layouts/ ├── _markup/ │ ├── render-image.html <-- render hook │ └── render-link.html <-- render hook ├── _partials/ │ ├── footer.html │ └── header.html ├── _shortcodes/ │ ├── audio.html │ └── video.html ├── books/ │ ├── page.html │ └── section.html ├── films/ │ ├── _views/ │ │ └── card.html <-- view template │ ├── page.html │ └── section.html ├── baseof.html ├── home.html ├── page.html ├── section.html ├── taxonomy.html └── term.html几点值得注意的约定:
- 以下划线开头的
_markup/、_partials/、_shortcodes/是三类特殊目录,分别存放渲染钩子、局部模板和短代码模板; _views/子目录用于存放与某内容类型绑定的视图模板;- 以内容类型命名的顶层目录(如
books/、films/)存放该内容类型专属的page.html与section.html; baseof.html、home.html等通用模板位于layouts根目录。
Hugo 通过模板查找顺序(template lookup order)来决定每个页面使用哪个模板文件。文档特别强调:创建模板前必须透彻理解模板查找顺序,因为模板的选择依据是模板类型、页面 Kind、内容类型、section、语言和输出格式的组合。
[!NOTE] 模板可以同时存在于项目自身的
layouts目录和主题的layouts目录中,Hugo 会选择最具体的那一个,并在项目中与主题间交错查找。
Base 模板:全站布局的基石
Base模板作为其他模板可以构建于其上的基础布局,通常定义 HTML 的公共结构元素,如html、head、body,并包含跨页面反复出现的页眉、页脚、导航和脚本引入。把这些公共部分在Base模板中统一定义一次,可以避免冗余、保证一致性并简化站点维护。
Hugo 可以对以下模板类型应用Base模板:home、page、section、taxonomy、term、single、list 和 all。当解析这些模板类型时,只有同时满足以下两个条件才会应用Base模板:
- 模板中必须至少包含一个
defineaction; - 模板中只能包含
defineaction、空白和模板注释,不允许有任何其他内容。
[!NOTE] 如果模板不满足上述全部条件,Hugo 会原样执行该模板,不会应用Base模板。
应用Base模板时,Hugo 会用被应用模板中对应defineaction 的内容,替换Base模板中的blockaction。
下面这个Base模板通过partial函数引入head、header和footer元素,blockaction 作为占位符,其内容会被匹配的defineaction 替换:
<!DOCTYPE html> <html lang="{{ site.Language.Locale }}" dir="{{ or site.Language.Direction `ltr` }}"> <head> {{ partial "head.html" . }} </head> <body> <header> {{ partial "header.html" . }} </header> <main> {{ block "main" . }} This will be replaced with content from the corresponding "define" action found in the template to which this _base_ template is applied. {{ end }} </main> <footer> {{ partial "footer.html" . }} </footer> </body> </html>{{ define "main" }} This will replace the content of the "block" action found in the _base_ template. {{ end }}源码视角:Base 模板的查找与历史兼容
当前 Hugo 仓库对 Base 模板的处理在模板存储阶段完成。tpl/tplimpl/templatestore.go 中的fromLegacyPath函数体现了新模板系统对旧版本命名规则的兼容:在 Hugo 0.146.0 之前,baseof 关键字前会先拼接一个标识符(layout、type 或 kind),并用连字符分隔,例如/docs/list-baseof.html;新系统会把这个标识符移动到 baseof 关键字之后并去掉连字符,转换为/docs/baseof.list.html的形式。这意味着旧项目中的list-baseof.html类命名在现代 Hugo 中依然有效。
在 hugolib/template_test.go 的TestTemplateBOM测试中,可以看到 Base 模板与普通模板配合的完整链路:baseof.html中定义{{ block "main" . }}base main{{ end }},single.html中定义{{ define "main" }}Hi!?{{ end }},最终生成页面断言Base: Hi!?,验证了 block 被 define 替换的机制。TestTemplateManyBaseTemplates(hugolib/template_test.go)进一步验证了为 100 个页面分别生成专属 baseof 模板的场景。
当前文档站自身的 docs/layouts/baseof.html 就是一个真实的大规模 Base 模板示例,它通过partial引入了导航、搜索、面包屑等大量公共组件。
Home 模板:渲染站点首页
Home模板负责渲染站点首页。
下面的Home模板先渲染页面内容(.Content),再遍历站点所有常规页面并输出标题链接。Hugo 会先对它应用Base模板:
{{ define "main" }} {{ .Content }} {{ range .Site.RegularPages }} <h2><a href="{{ .RelPermalink }}">{{ .LinkTitle }}</a></h2> {{ end }} {{ end }}首页列表的过滤、排序和分组可以借助 Hugo 的页面集合方法与函数完成,相关速查可参考文档站的 quick-reference/page-collections 目录下关于页面集合的说明。文档站的 docs/layouts/home.html 是生产级示例,它组合了特征展示、开源项目介绍、赞助商等大量区块。
Page 模板:渲染常规页面
Page模板渲染一篇常规内容页面。
下面的Page模板渲染页面标题和正文内容,同样会先应用Base模板:
{{ define "main" }} <h1>{{ .Title }}</h1> {{ .Content }} {{ end }}需要说明的是:位于content根目录下的文件,其内容类型为page。而content目录下每个子目录会形成一个 section,section 内的页面可以拥有针对该 section 的专属模板(参见上文layouts/books/page.html的用法)。
Section 模板:渲染栏目列表
Section模板渲染某个 section 内的页面列表。
下面的Section模板渲染页面标题、正文内容,以及当前 section 内的页面列表:
{{ define "main" }} <h1>{{ .Title }}</h1> {{ .Content }} {{ range .Pages }} <h2><a href="{{ .RelPermalink }}">{{ .LinkTitle }}</a></h2> {{ end }} {{ end }}对列表的过滤、排序和分组,同样可以参考页面集合速查表。
Taxonomy 模板:渲染分类法术语列表
Taxonomy模板渲染某个 taxonomy 中的术语(term)列表。
下面的Taxonomy模板渲染页面标题、正文内容和当前分类法下的术语列表:
{{ define "main" }} <h1>{{ .Title }}</h1> {{ .Content }} {{ range .Pages }} <h2><a href="{{ .RelPermalink }}">{{ .LinkTitle }}</a></h2> {{ end }} {{ end }}Data 对象与术语排序
在Taxonomy模板中,Data对象提供以下分类法专属方法:
Singular(单数名,如 "tag")Plural(复数名,如 "tags")Terms
Terms方法返回一个 taxonomy 对象,可以继续调用其任何方法,包括Alphabetical(按字母排序)和ByCount(按关联页面数排序)。例如,用ByCount按每个术语关联的页面数量排序输出术语列表:
{{ define "main" }} <h1>{{ .Title }}</h1> {{ .Content }} {{ range .Data.Terms.ByCount }} <h2><a href="{{ .Page.RelPermalink }}">{{ .Page.LinkTitle }}</a> ({{ .Count }})</h2> {{ end }} {{ end }}注意这里遍历的元素是“术语 + 计数”的结构,因此通过.Page访问术语页面对象,通过.Count获取关联页面数量。文档站的分类法相关测试可在 hugolib/taxonomy_test.go 中查看。
Term 模板:渲染术语关联页面
Term模板渲染与某个 term 关联的页面列表。
下面的Term模板渲染页面标题、正文内容和与当前术语关联的页面列表:
{{ define "main" }} <h1>{{ .Title }}</h1> {{ .Content }} {{ range .Pages }} <h2><a href="{{ .RelPermalink }}">{{ .LinkTitle }}</a></h2> {{ end }} {{ end }}与Taxonomy模板类似,在Term模板中Data对象提供以下术语专属方法:
SingularPluralTerm
Single 模板:Page 模板的回退
Single模板是Page模板的回退方案。如果page模板不存在,Hugo 会转而查找single模板。
{{ define "main" }} <h1>{{ .Title }}</h1> {{ .Content }} {{ end }}在传统的 Hugo 模板体系中,layouts/_default/single.html是最常见的回退位置。这一点在文档站 docs/content/en/templates/lookup-order.md 中有详细描述:对于常规内容页(single page),Hugo 会在_default/single.html中查找 HTML 模板;对于列表页(section 列表、首页、分类法列表、分类法术语),则在_default/list.html中查找。
List 模板:列表类模板的回退
List模板是 home、section、taxonomy、term 四种模板的回退方案。若其中某个模板类型不存在,Hugo 会转而查找list模板。
{{ define "main" }} <h1>{{ .Title }}</h1> {{ .Content }} {{ range .Pages }} <h2><a href="{{ .RelPermalink }}">{{ .LinkTitle }}</a></h2> {{ end }} {{ end }}在传统模板体系中,它对应layouts/_default/list.html。文档站的 docs/layouts/list.html 就是这一回退模板的真实落地实现,同时它还提供了 docs/layouts/list.rss.xml 作为 RSS 输出格式的列表模板。
All 模板:终极通用回退
All模板是 home、page、section、taxonomy、term、single、list 全部模板类型的回退方案。若其中任一模板不存在,Hugo 都会转而查找all模板。
下面的All模板按页面 Kind 条件渲染不同类型的内容:
{{ define "main" }} {{ if eq .Kind "home" }} {{ .Content }} {{ range .Site.RegularPages }} <h2><a href="{{ .RelPermalink }}">{{ .LinkTitle }}</a></h2> {{ end }} {{ else if eq .Kind "page" }} <h1>{{ .Title }}</h1> {{ .Content }} {{ else if in (slice "section" "taxonomy" "term") .Kind }} <h1>{{ .Title }}</h1> {{ .Content }} {{ range .Pages }} <h2><a href="{{ .RelPermalink }}">{{ .LinkTitle }}</a></h2> {{ end }} {{ else }} {{ errorf "Unsupported page kind: %s" .Kind }} {{ end }} {{ end }}这个示例演示了三种典型的 Kind 分支处理:home渲染首页内容加常规页面列表;page渲染单页标题与内容;section/taxonomy/term渲染标题、内容与页面列表;其余情况(如404)通过errorf抛出明确的构建错误,帮助开发者尽早发现问题。errorf是 Hugo 模板内置函数,在 tpl/fmt 包中有对应实现。
Partial 模板:可复用组件
Partial模板通常用于渲染站点的某个组件,但也可以创建返回值的Partial模板。
例如,下面的Partial模板渲染版权信息:
<p>Copyright {{ now.Year }}. All rights reserved.</p>通过调用partial或partialCached函数执行Partial模板,可选地传入上下文作为第二个参数:
{{ partial "footer.html" . }}文档站的 docs/layouts/_partials/footer.html 即为此类模板的真实实现,而 docs/layouts/_partials/header.html 则展示了如何聚合导航、主题切换、搜索入口等复杂组件。
Partial 的命名匹配逻辑
与其它模板类型不同,Hugo 在查找匹配的Partial模板时不会考虑当前页面的 Kind、内容类型、逻辑路径、语言或输出格式。但它确实会应用与其它模板类型相同的名称匹配逻辑:先尝试最具体的匹配,找不到再逐步回退到更通用的版本。
例如,对于如下调用:
{{ partial "footer.section.de.html" . }}Hugo 使用如下查找顺序寻找匹配模板:
layouts/_partials/footer.section.de.htmllayouts/_partials/footer.section.htmllayouts/_partials/footer.de.htmllayouts/_partials/footer.html
从源码结构看,这种“逐级剥离名称标识符”的匹配逻辑与模板路径解析器(common/paths/pathparser.go)中对文件名的解析方式相呼应,_partials容器目录在 tpl/tplimpl/templatestore.go 中作为containerPartials被特殊识别与处理。
内联 Partial 模板
Partial模板还可以在其它模板内联定义。需要特别注意的是:模板命名空间是全局的,必须为这些内联Partial模板保证唯一名称,以避免命名冲突。
Value: {{ partial "my-inline-partial.html" . }} {{ define "_partials/my-inline-partial.html" }} {{ $value := 32 }} {{ return $value }} {{ end }}注意内联模板的define名称以_partials/为前缀,这与目录中的命名约定保持一致。文档站的模板转换逻辑中也有对内联模板的显式处理,见 tpl/tplimpl/templatetransform.go 的注释。
View 模板:页面渲染视图
View模板与Partial模板类似,通过调用Page对象上的Render方法触发。与Partial模板不同的是,View模板:
- 继承当前页面的上下文;
- 可以针对任何页面 Kind、内容类型、逻辑路径、语言或输出格式;
- 可以位于
layouts目录下的任何层级。
例如,下面的Home模板渲染页面内容,并为站点filmssection 中的每个页面渲染一个卡片组件:
{{ define "main" }} {{ .Content }} <ul> {{ range where site.RegularPages "Section" "films" }} {{ .Render "_views/card" }} {{ end }} </ul> {{ end }}<div class="card"> <h2><a href="{{ .RelPermalink }}">{{ .LinkTitle }}</a></h2> {{ .Summary }} </div>Render方法接受视图模板的路径(不含扩展名),视图模板内部可以直接使用当前页面的上下文(如.RelPermalink、.LinkTitle、.Summary)。关于View模板的命名和组织方式,请参阅Render方法文档。
Render Hook 模板:接管 Markdown 到 HTML 的转换
Render Hook模板用于覆盖 Markdown 转换为 HTML 的过程。Hugo 内置的渲染器(如 Goldmark)负责 Markdown 语法解析,而渲染钩子允许你在特定元素(链接、图片、标题、代码块等)输出 HTML 时插入自定义逻辑。
例如,下面的Render Hook模板在每个标题右侧添加一个锚点链接:
<h{{ .Level }} id="{{ .Anchor }}" {{- with .Attributes.class }} class="{{ . }}" {{- end }}> {{ .Text }} <a href="#{{ .Anchor }}">#</a> </h{{ .Level }}>该模板使用.Level(标题级别)、.Anchor(生成的锚点 ID)、.Text(标题文本)和.Attributes(附加属性)等上下文字段。当前文档站的 docs/layouts/_markup 目录下就包含了render-blockquote.html、render-codeblock.html、render-link.html、render-table.html、render-passthrough.html等多个生产级渲染钩子,可作深入学习参考。更多细节参见渲染钩子模板文档。
Shortcode 模板:从内容页调用
Shortcode模板用于渲染站点组件。与Partial、View模板不同,Shortcode模板是从内容页面内部调用的。
例如,下面的Shortcode模板从全局资源中获取音频文件并渲染 audio 元素:
{{ with resources.Get (.Get "src") }} <audio controls preload="auto" src="{{ .RelPermalink }}"></audio> {{ end }}然后在 Markdown 内容中调用该短代码:
{{</* audio src=/audio/test.mp3 */>}}resources.Get用于从站点全局资源中按路径获取资源(实现在 resources 模块),.Get "src"读取短代码调用时的参数。文档站的 docs/layouts/_shortcodes 目录包含 20 余个生产级短代码,例如img.html、code-toggle.html、include.html等,覆盖了从图片处理到文档包含的各类场景。更多细节参见短代码模板文档。
其它专用模板
除上述模板类型外,Hugo 还提供以下专用模板,用于生成站点基础设施类输出:
- Sitemaps(站点地图)
- RSS feeds(订阅源)
- 404 错误页面
- robots.txt 文件
这些模板同样遵循模板查找顺序,并且可以按输出格式(如 XML)区分。文档站自带的 docs/layouts/404.html、docs/layouts/list.rss.xml 以及 docs/layouts/home.redir、docs/layouts/home.headers 展示了专用输出(重定向规则、响应头)的实际用法。
结合查找顺序设计你的模板体系
用前端属性定向模板
你无法改变查找顺序去适配某个内容页,但可以通过修改内容页去适配模板:在前置元数据(front matter)中指定type、layout或两者。
考虑如下内容结构:
content/ ├── about.md └── contact.md位于content根目录的文件,其内容类型为page。若要让这些页面使用专属模板,可创建对应子目录:
layouts/ └── page/ └── single.html然而 contact 页面通常包含表单,需要不同的模板。在前置元数据中指定layout:
title = 'Contact' layout = 'contact'然后为 contact 页面创建专属模板:
layouts/ └── page/ ├── contact.html <-- 渲染 contact.md └── single.html <-- 渲染 about.md作为内容类型,page这个词较为模糊,也许miscellaneous更贴切。为每个页面添加type:
# content/about.md title = 'About' type = 'miscellaneous'# content/contact.md title = 'Contact' type = 'miscellaneous' layout = 'contact'然后把模板放入对应目录:
layouts/ └── miscellaneous/ ├── contact.html <-- 渲染 contact.md └── single.html <-- 渲染 about.md影响模板选择的参数
根据 docs/content/en/templates/lookup-order.md,Hugo 为给定页面选择模板时会考虑以下参数,按特异性从高到低排列:
- Kind:页面 Kind(首页也是一种),它同时决定页面是单页(single page,查找
_default/single.html)还是列表页(list page,查找_default/list.html); - Layout:可在前置元数据中设置;
- 输出格式:每个输出格式既有
name(如rss、amp、html)也有suffix(如xml、html)。Hugo 优先匹配两者都命中的模板(如index.amp.html),再逐步查找更不具体的模板;若输出格式的 Media Type 定义了多个后缀,只考虑第一个; - 语言:模板名中会考虑语言标签。若站点语言为
fr,index.fr.amp.html优先于index.amp.html,但index.amp.html又优先于index.fr.html; - Type:取前置元数据中
type的值,未设置时取根 section 名(如 "blog"),它永远有值,未设置则为 "page"; - Section:对
section、taxonomy和term类型相关。
小结:模板类型全景
| 模板类型 | 渲染对象 | 回退关系 | 典型文件 |
|---|---|---|---|
| Base | 公共布局骨架 | 无(作为其它模板的外壳) | layouts/baseof.html |
| Home | 站点首页 | 可回退到 List、All | layouts/home.html |
| Page | 常规内容页 | 可回退到 Single、All | layouts/page.html |
| Section | 栏目列表页 | 可回退到 List、All | layouts/section.html |
| Taxonomy | 分类法术语列表 | 可回退到 List、All | layouts/taxonomy.html |
| Term | 术语关联页面列表 | 可回退到 List、All | layouts/term.html |
| Single | Page 的回退 | 可回退到 All | layouts/single.html |
| List | 列表类模板的回退 | 可回退到 All | layouts/list.html |
| All | 所有模板的终极回退 | 无 | layouts/all.html |
| Partial | 可复用组件 / 返回值 | 名称逐级回退匹配 | layouts/_partials/*.html |
| View | 页面渲染视图 | 无 | layouts/**/_views/*.html |
| Render Hook | 覆盖 Markdown→HTML 转换 | 无 | layouts/_markup/*.html |
| Shortcode | 从内容页调用的组件 | 无 | layouts/_shortcodes/*.html |
合理利用这套模板类型体系,配合模板查找顺序的精确匹配规则,你既可以用 Base + Partial 构建高度一致的页面骨架,又可以通过内容类型目录、视图模板和渲染钩子为任意页面形态提供专属渲染方案——这正是 Hugo 灵活模板系统的核心价值所在。
【免费下载链接】hugoThe world’s fastest framework for building websites.项目地址: https://gitcode.com/gh_mirrors/hu/hugo
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考