Hugo 是一个基于 Go 语言的现代的静态站点生成器。Hugo 提供了 强大的模板引擎 和 灵活的主题系统,并支持 Shortcodes 和 Hooks 扩展功能。
Hugo Introduction
Hugo
v0.146.0
模板系统变更
layouts/_default/
目录已移除,所有文件移至layouts/
根目录layouts/partials/
→layouts/_partials/
layouts/shortcodes/
→layouts/_shortcodes/
安装和初始化
下面将介绍 Windows 下的安装方式,其他平台参照:MacOS, Linux
方式一:在 Git Bash
或 PowerShell
中使用 winget
命令安装:
winget install Hugo.Hugo.Extended
卸载命令:winget uninstall --name "Hugo (Extended)"
方式二:从Github直接下载, 然后配置环境变量即可:
- 下载 hugo 扩展版:github
- 解压到指定目录,例如
D:\Develop\env\hugo\hugo_extended_0.147.7_windows-amd64
- 配置环境变量:
bash
# 设置 HUGO_HOME [System.Environment]::SetEnvironmentVariable("HUGO_HOME", "D:\Develop\env\hugo\hugo_extended_0.147.7_windows-amd64", "Machine") # 安全追加到 PATH $currentPath = [System.Environment]::GetEnvironmentVariable("Path", "Machine") $newPath = if ($currentPath -like "*$env:HUGO_HOME*") { $currentPath } else { "$currentPath;$env:HUGO_HOME" } [System.Environment]::SetEnvironmentVariable("Path", $newPath, "Machine")
注意:需要以管理员身份运行 PowerShell 才能修改 Machine 级别的环境变量。除了使用命令,也可以在系统设置中搜索环境变量,然后手动添加环境变量。
为什么推荐 Hugo 的 “Extended” 版本?
- Extended 版本内置了 LibSass 编译器 (C/C++ 实现的 Sass 编译器)
- 支持将图片转换为 WebP 格式
Hugo 为什么不需要 Go 的开发环境?
- Go 是一种编译型语言,Go 编译器通常会进行静态链接,这意味着程序运行所需的所有库(标准库和一些第三方库,如果它们也是纯 Go 实现的话)都会被直接编译进最终的可执行文件中。
- Go 语言可以将程序编译成一个单一的、原生的、不依赖外部运行时的可执行文件
初始化 Hugo 项目
hugo new site myblog
在生成的目录中,找到 layouts 目录,在其下创建 home.html
文件,用于自定义首页内容。在其中随意写一些HTML,然后启动项目:
hugo server --bind 0.0.0.0 --port 1313
访问 http://localhost:1313
即可看到首页内容
Hugo配置文件
Hugo 的配置文件是站点所有设置的核心。默认情况下,Hugo 会在站点根目录下查找名为 hugo.toml
(首选), hugo.yaml
, hugo.json
, config.toml
, config.yaml
, 或 config.json
的文件。
Hugo 支持三种主要的配置文件格式:
- TOML (Tom’s Obvious, Minimal Language):
.toml
文件。这是 Hugo 官方文档和很多示例中常用的格式。tomlbaseURL = "https://example.com/" languageCode = "en-us" title = "My New Hugo Site" [params] author = "John Doe" description = "A great website."
- YAML (YAML Ain’t Markup Language):
.yaml
或.yml
文件。使用缩进来表示层级结构。yamlbaseURL: "https://example.com/" languageCode: "en-us" title: "My New Hugo Site" params: author: "John Doe" description: "A great website."
- JSON (JavaScript Object Notation):
.json
文件。虽然也支持,但在 Hugo 配置中不如 TOML 或 YAML 常见。json{ "baseURL": "https://example.com/", "languageCode": "en-us", "title": "My New Hugo Site", "params": { "author": "John Doe", "description": "A great website." } }
以下是一些最基本和常用的配置项:
-
baseURL
: (字符串) 必需项。你网站的根 URL,例如https://example.com/
或https://myusername.github.io/myproject/
(如果部署在子目录)。部署到生产环境时必须正确设置此项,否则链接、RSS、站点地图等会出错。 -
languageCode
: (字符串) 网站的主要语言代码,例如en-us
,zh-cn
。 -
title
: (字符串) 网站的全局标题。 -
theme
: (字符串) 如果你使用一个下载的或创建的 Hugo 主题,在这里指定主题的名称。主题必须放在站点根目录下的themes/
文件夹中。例如theme = "ananke"
。 如果自己在项目根目录的layouts/
中开发模板,则不需要设置此项或将其注释掉。 -
[params]
: (表/对象) 用于存放所有主题特定或用户自定义的参数。这些参数可以在模板中通过.Site.Params.yourParamName
来访问。这是配置主题外观、功能和自定义信息的常用位置。 -
[menu]
: (表数组) 用于定义导航菜单。例如[[menu.main]]
定义主导航。 -
[taxonomies]
: (表) 用于声明网站使用的分类法,例如category = "categories"
和tag = "tags"
。 -
[markup]
: (表) 配置 Markdown 渲染器的行为,例如代码高亮、目录生成等。toml[markup] [markup.goldmark.renderer] unsafe = true # 允许在 Markdown 中使用原始 HTML (谨慎使用) [markup.highlight] # style = "monokai" noClasses = false # 使用 class 而不是内联 style 进行代码高亮 [markup.tableOfContents] startLevel = 2 endLevel = 3 ordered = false
-
[outputs]
和[outputFormats]
: (表) 控制 Hugo 为不同类型的页面生成哪些输出格式 (例如 HTML, RSS, JSON, AMP)。 -
[imaging]
: (表) 配置 Hugo Pipes 图片处理的默认参数。 -
[services]
: (表) 配置与第三方服务的集成,例如[services.googleAnalytics]
或[services.disqus]
。 -
[privacy]
: (表) 配置与隐私相关的设置,例如[privacy.youtube]
是否使用no-cookie
域名。
配置文件的拆分与组织 (Configuration Directory): 对于复杂的站点,Hugo 推荐使用配置目录来拆分和组织配置文件。
- 在站点根目录下创建
config/
目录。在config/
下创建_default/
目录,用于存放基础/默认配置。 - 在
_default/
目录中,你可以将配置拆分成多个文件,例如:config.toml
(或hugo.toml
): 存放核心配置如baseURL
,title
,languageCode
。params.toml
: 专门存放所有[params]
下的参数。menus.toml
: 专门存放所有[menu]
定义。markup.toml
: 存放[markup]
配置。languages.en.toml
,menus.en.toml
(如果有多语言): 特定语言的配置。
- Hugo 会自动加载并合并
config/_default/
目录下的所有配置文件。 - 按环境配置: 你还可以在
config/
目录下为不同环境创建子目录,例如config/development/
和config/production/
。这些特定环境的配置会覆盖或合并到_default/
的配置之上。- 例如,在
config/development/config.toml
中可以设置baseURL = "http://localhost:1313/"
。 - 在
config/production/config.toml
中可以设置baseURL = "https://yourdomain.com/"
。 - 通过
hugo server -e development
或hugo -e production
(或设置HUGO_ENV
环境变量) 来指定当前环境。
- 例如,在
查看最终配置: 可以使用以下命令来查看 Hugo 在合并所有配置文件和应用主题默认设置后的最终配置:
hugo config
更多配置文件内容参照:Hugo configuration
自定义样式
在 Hugo 中添加和管理自定义样式主要有两种方式:直接使用 CSS 文件,或者使用 SCSS/SASS并通过 Hugo Pipes 进行编译。
1. 直接使用 CSS 文件 (简单场景)
如果你有一个或多个预先编写好的 CSS 文件,不希望进行预处理或复杂的构建步骤。
-
存放位置:
- 将你的 CSS 文件(例如
style.css
,custom.css
)放在站点或主题根目录下的static/css/
目录中。- 例如:
kblog/static/css/theme.css
- 例如:
- 将你的 CSS 文件(例如
-
在模板中引入: 在你的基础模板 (
layouts/baseof.html
) 或头部局部模板 (layouts/_partials/head-content.html
) 的<head>
标签内,像普通 HTML 一样链接它们:html{{/* layouts/_partials/head-content.html */}} <head> {{/* ...其他 meta 和 title 标签... */}} <link rel="stylesheet" href="{{ "css/theme.css" | relURL }}"> {{/* 如果有多个 CSS 文件 */}} {{/* <link rel="stylesheet" href="{{ "css/another-style.css" | relURL }}"> */}} </head>
"css/theme.css"
: 路径是相对于static/
目录的。| relURL
: Hugo 模板函数,确保生成的 URL 是相对于站点根目录的正确路径,无论你的baseURL
是否包含子目录。
-
优点: 简单直接,易于理解。
-
缺点:
- CSS 文件不会被 Hugo Pipes 处理(例如压缩、指纹化等),除非你使用外部工具。
- 不利于使用 SCSS/SASS 的高级特性(变量、嵌套、混合等)。
- 多个 CSS 文件会导致多次 HTTP 请求(除非手动合并或使用 HTTP/2)。
2. 使用 SCSS/SASS 与 Hugo Pipes
Hugo Extended 版本内置了 LibSass 编译器,允许你直接在项目中使用 SCSS/SASS 文件,并通过 Hugo Pipes 进行编译、压缩、指纹化等优化。
-
存放位置:
- 将你的 SCSS/SASS 主入口文件(例如
main.scss
)放在站点或主题根目录下的assets/scss/
(或assets/css/
也可以,只要resources.Get
的路径正确) 目录中。- 例如:
kblog/assets/scss/main.scss
- 例如:
- 你可以将 SCSS 拆分成多个 partials (文件名以下划线
_
开头,如_variables.scss
,_header.scss
),然后在主main.scss
文件中使用@import
指令引入它们。textkblog/ └── assets/ └── scss/ ├── main.scss <-- 主入口文件 ├── abstracts/ │ └── _variables.scss │ └── _mixins.scss ├── components/ │ └── _buttons.scss │ └── _cards.scss └── layout/ └── _header.scss └── _footer.scss
main.scss
文件内容可能像这样:scss// abstracts @import 'abstracts/variables'; @import 'abstracts/mixins'; // layout @import 'layout/header'; @import 'layout/footer'; // ...
- 将你的 SCSS/SASS 主入口文件(例如
-
在模板中引入和处理 (通常在
layouts/_partials/head/css.html
):go-html-template{{/* kblog/layouts/_partials/head/css.html */}} {{- $cssResourcePath := "scss/main.scss" -}} {{/* SCSS主入口文件路径 */}} {{- $styles := resources.Get $cssResourcePath -}} {{- if $styles -}} {{- $cssOptions := (dict "targetPath" "css/style.css" "enableSourceMap" hugo.IsDevelopment ) -}} {{- /* 使用 css.Sass (Hugo v0.128.0+ 推荐) */}} {{- $processedStyles := $styles | css.Sass $cssOptions -}} {{- if not hugo.IsDevelopment -}} {{/* 仅在生产环境压缩和指纹化 */}} {{- $processedStyles = $processedStyles | minify | fingerprint "sha256" -}} {{- end -}} <link rel="stylesheet" href="{{ $processedStyles.RelPermalink }}" {{ if not hugo.IsDevelopment }}integrity="{{ $processedStyles.Data.Integrity }}"{{ end }}> {{- else -}} {{- warnf "SCSS resource not found at 'assets/%s'." $cssResourcePath -}} {{- end -}}
Hugo Pipes 详解:
$cssResourcePath
: 定义 SCSS 主入口文件的路径 (相对于assets/
目录)。resources.Get $cssResourcePath
: 获取 SCSS 资源对象。if $styles
: 检查资源是否存在。$cssOptions
(字典):targetPath
: Hugo Pipes 处理后生成的 CSS 文件在最终publishDir
(通常是public
) 中的路径。例如,所有 SCSS 文件会被编译并合并到public/css/style.css
。enableSourceMap
: 在开发模式 (hugo.IsDevelopment
为true
) 下是否生成 Source Map,方便调试。
css.Sass $cssOptions
: 核心函数 (需要 Hugo Extended 版本)。将获取到的 SCSS 资源对象($styles
)通过 Sass 编译器处理,应用$cssOptions
。它会处理所有@import
并编译成一个 CSS 资源对象。minify
: 压缩生成的 CSS。fingerprint "sha256"
: 为文件名添加基于内容的哈希指纹 (例如style.a1b2c3d4.css
),并计算 SRI (Subresource Integrity) 哈希。.RelPermalink
: 获取处理后(可能已指纹化)的 CSS 文件的相对 URL。.Data.Integrity
: 获取 SRI 哈希。
-
优点:
- 可以使用 SCSS/SASS 的所有强大功能。
- Hugo Pipes 自动处理编译、压缩、指纹化,无需外部构建工具。
- 更好的性能(通过压缩和指纹化实现有效的浏览器缓存)。
- 更易于维护和组织大型 CSS 代码库。
选择哪种方式?
- 对于非常简单的项目或快速原型,直接使用
static/css/
可能足够。 - 对于绝大多数现代网站和主题开发,强烈推荐使用
assets/
目录和 Hugo Pipes (SCSS/SASS) 的方式,因为它提供了更好的开发体验和最终的性能优化。
静态资源管理
static/
目录用于存放那些不需要 Hugo 进行任何处理、直接原样复制到最终输出目录(publishDir
,通常是 public/
)根级别的文件。
-
存放位置:
- 站点根目录下的
static/
文件夹。 - 主题根目录下的
static/
文件夹。
- 站点根目录下的
-
工作原理:
- 当 Hugo 构建站点时,它会将
static/
目录中的所有文件和文件夹按原样复制到publishDir
的根目录。 - 路径映射:
static/images/logo.png
->public/images/logo.png
(URL:yoursite.com/images/logo.png
)static/js/vendor.js
->public/js/vendor.js
(URL:yoursite.com/js/vendor.js
)static/favicon.ico
->public/favicon.ico
(URL:yoursite.com/favicon.ico
)static/robots.txt
->public/robots.txt
(URL:yoursite.com/robots.txt
) (除非你通过enableRobotsTXT = true
让 Hugo 生成它)
- 当 Hugo 构建站点时,它会将
-
适用场景:
- 图片 (Images): 网站 Logo、文章配图(如果这些图片不需要 Hugo Pipes 的图片处理功能,如缩放、裁剪、格式转换)。
- 字体文件 (Fonts): 自定义字体文件 (
.woff
,.woff2
,.ttf
等)。 - 第三方 JavaScript/CSS 库: 如果你使用的某些第三方库已经提供了预编译和压缩好的版本,可以直接放在
static/js/
或static/css/
下。 favicon.ico
: 网站图标。robots.txt
: 如果你希望手动管理robots.txt
文件 (而不是让 Hugo 生成它)。ads.txt
,humans.txt
等验证或说明文件。- PDF, MP3, 视频文件等其他媒体文件。
-
在模板中引用
static/
目录下的资源:- 使用相对于站点根目录的路径,并通常配合
relURL
或absURL
。
go-html-template<link rel="icon" href="{{ "favicon.ico" | relURL }}"> <img src="{{ "images/my-logo.png" | relURL }}" alt="My Logo"> <script src="{{ "js/some-vendor-library.js" | relURL }}"></script>
- 使用相对于站点根目录的路径,并通常配合
-
主题和项目的
static/
目录合并:- 如果你的项目和使用的主题都有
static/
目录,并且其中有相同路径的文件,项目根目录static/
下的文件会覆盖主题static/
目录下的同名文件。 - 如果路径不同,则它们的内容会被合并到最终的
public/
目录中。
- 如果你的项目和使用的主题都有
static/
vs assets/
的关键区别:
特性 | static/ 目录 |
assets/ 目录 |
---|---|---|
处理方式 | 文件被原样复制到 public/ 根目录。 |
文件由 Hugo Pipes 处理(编译、转换、压缩、指纹化等)。 |
适用内容 | 不需要处理的静态文件:图片、字体、预编译的JS/CSS库等。 | 需要处理的源文件:SCSS/SASS, JS模块, 需要优化的图片等。 |
模板引用 | 通常通过硬编码路径 + relURL /absURL 。 |
通过 resources.Get 获取资源对象,然后处理并获取 .RelPermalink 。 |
优化 | Hugo 不对其进行优化。 | Hugo Pipes 提供强大的优化能力。 |
Hugo模板与常用变量
Hugo 模板基于 Go 语言的 html/template
和 text/template
包。
- 什么是模板?
模板是位于项目 layouts/
目录(或主题的 layouts/
目录)下的 HTML 文件(或其他格式的文件,如 xml
, json
)。
它们包含了页面的基本结构和一些特殊的模板标签 {{ }}
,用于插入动态内容或执行逻辑。
- 模板标签
{{ }}
(Delimiters):
- 所有 Hugo/Go 模板的逻辑和变量都包裹在双花括号
{{
和}}
之间。 {{- ... -}}
(去除空白): 在标签的任意一侧或两侧添加连字符-
可以去除该侧由模板标签自身产生的空白字符(包括换行和空格)。
- 上下文 (Context - The Dot
.
):
-
在模板的任何给定点,都有一个“当前上下文”,通过一个点 (
.
) 来引用。 -
上下文的值可以是单个数据(如字符串、数字),也可以是一个包含多个字段和方法的复杂对象(如 Hugo 的页面对象或站点对象)。
示例:
{{ .Title }}
- 如果当前上下文是一个页面对象,这将输出该页面的标题。 -
上下文的改变: 在
range
循环或with
语句块内部,.
的上下文会改变,指向当前循环的元素或with
语句绑定的值。 -
访问顶层上下文 (
$.
): 在被改变的上下文中(如range
或with
块内),可以使用美元符号加点 ($.
) 来引用传递到该模板最顶层的上下文。例如,在range
循环内部,{{ $.Site.Title }}
仍然可以访问站点标题。
- 注释:
{{/* 这是一个模板注释,不会被渲染 */}}
学习和调试技巧:
- 官方文档: Hugo 的官方文档 (gohugo.io/documentation/) 。
printf "%#v" .
: 在模板中临时打印{{ printf "%#v" . }}
可以输出当前上下文.
的详细结构和所有可用字段,对于理解数据结构非常有帮助。warnf
/errorf
: 使用这些函数在 Hugo 控制台打印调试信息或在特定条件下中断构建。
常用站点变量
站点变量 提供了访问整个站点配置和全局信息的能力。可以在模板中通过 .Site
(如果当前上下文是页面对象) 或直接通过全局变量 site
(在大多数情况下) 来访问。
.Site.Title
: (字符串) 站点的全局标题 (来自hugo.toml
)。.Site.BaseURL
: (字符串) 站点的根 URL (来自hugo.toml
)。.Site.LanguageCode
: (字符串) 站点的主要语言代码。.Site.Params
: (映射) 包含hugo.toml
中[params]
部分定义的所有自定义参数。.Site.Menus
: (映射) 包含所有已定义菜单的映射。例如,.Site.Menus.main
是一个包含 “main” 菜单所有条目的列表。.Site.Taxonomies
: (映射) 包含站点所有分类法的映射。键是分类法的单数名称 (例如 “category”, “tag”),值是另一个映射,该映射的键是词条名称,值是包含该词条下所有页面的加权列表。.Site.Pages
: (页面集合) 包含站点所有页面(包括常规页、区段页、首页、分类法页等)的集合。.Site.RegularPages
: (页面集合) 包含站点所有常规内容页面的集合(不包括列表页等)。这是最常用于遍历所有文章的集合。.Site.Sections
: (页面集合) 包含站点所有顶级区段的页面对象(即顶级目录_index.md
对应的页面)。.Site.Data
: (映射) 允许访问data/
目录下所有数据文件(JSON, YAML, TOML)的内容。.Site.Lastmod
: (时间对象) 整个站点的最后修改时间(通常是最新内容的修改时间)。.Site.Config
: (配置对象) 提供对站点完整配置的访问,包括语言特定配置。例如.Site.Config.Services.GoogleAnalytics.ID
。.Site.Languages
: (语言集合) 在多语言站点中,包含所有已配置语言的列表。.Site.Home
: (页面对象) 指向站点首页的页面对象。site.GetPage "path/or/kind" "identifier"
: 用于获取站点中任何类型的特定页面对象。{{ $blogSection := site.GetPage "section" "posts" }}
{{ $aboutPage := site.GetPage "about.md" }}
(如果 about.md 在 content 根目录)
site.Data.filename.key
: 访问data/filename.toml
(或.yaml
,.json
) 中key
的值。
常用页面变量
当 Hugo 渲染一个页面时,当前上下文 .
通常是一个页面对象 (Page Object),它包含了关于该页面的大量信息。
.Title
: (字符串) 页面的标题,通常来自前置元数据的title
字段。.Content
: (HTML) 页面主要的 Markdown 内容渲染后的 HTML。.Summary
: (HTML) Hugo 自动生成的页面摘要(默认前70个词),或来自前置元数据的summary
字段。.Description
: (字符串) 页面的描述,通常来自前置元数据的description
字段,常用于 SEO。.Permalink
: (字符串) 页面的绝对永久链接 (URL),包含baseURL
。.RelPermalink
: (字符串) 页面相对于站点根目录的永久链接。推荐用于内部链接。.Date
: (时间对象) 页面的主要日期,通常来自前置元数据的date
字段。可以使用.Format
方法格式化。.PublishDate
: (时间对象) 页面的发布日期,来自publishDate
字段。如果未设置,则回退到date
。.Lastmod
: (时间对象) 页面的最后修改日期,可以来自 Git 信息或前置元数据的lastmod
字段。.Params
: (映射) 一个包含页面前置元数据中所有自定义参数的映射。例如,如果前置元数据中有author: "Alice"
,则可以通过.Params.author
访问。.WordCount
: (整数) 页面内容的字数。.ReadingTime
: (整数) 估计的阅读时间(分钟)。.TableOfContents
: (HTML) Hugo 根据页面 Markdown 内容中的 H2-H6 标题自动生成的 HTML 目录。可以在hugo.toml
的[markup.tableOfContents]
中配置其行为。.Kind
: (字符串) 页面的种类,例如"page"
,"home"
,"section"
,"taxonomy"
,"term"
。.Type
: (字符串) 页面的内容类型,通常由其所在区段的第一级目录名决定,或由前置元数据的type
字段指定。.Layout
: (字符串) 页面前置元数据中指定的布局名称 (如果有)。.Section
: (字符串) 页面所属的顶级区段的名称 (例如content/blog/my-post.md
的.Section
是"blog"
)。.CurrentSection
: (页面对象) 当前页面所属的顶级区段的页面对象 (即该区段_index.md
对应的 Page 对象)。非常适用于在单页中链接到同区段的其他文章。(Hugo 0.93.0+).Parent
: (页面对象) 当前页面的父级页面对象(如果它是嵌套在某个_index.md
下的,或者它是区段的直接子页面)。.IsHome
: (布尔值) 如果当前页面是站点首页,则为true
。.IsPage
: (布尔值) 如果当前页面是一个常规的内容页面 (.Kind
为"page"
), 则为true
。.IsSection
: (布尔值) 如果当前页面是一个区段列表页 (.Kind
为"section"
), 则为true
。.File
: (文件对象) 提供关于源文件的信息,例如.File.Path
,.File.LogicalName
,.File.UniqueID
。.Resources
: (资源集合) 如果当前页面是一个页面包 (Page Bundle) (即content/my-post/index.md
结构),.Resources
提供了访问该包内所有资源的方法。.Translations
: (页面集合) 一个包含当前页面所有已翻译版本的页面对象的列表(用于多语言站点)。.OutputFormats
: (输出格式集合) 获取当前页面可用的输出格式列表。.AlternativeOutputFormats
: (输出格式集合) 获取当前页面除了 HTML 之外的其他输出格式。.Site
: (站点对象) 从页面上下文中访问全局站点信息和配置。
常用语法与函数
-
变量定义:
{{ $myVariable := "一些值" }}
: 声明并初始化一个新变量。变量名以$
开头。{{ $myVariable = "新值" }}
: 给一个已声明的变量重新赋值 (注意,作用域可能受限)。
-
输出变量:
{{ .VariableName }}
: 输出变量VariableName
的值。{{ .Params.customField }}
: 输出页面前置元数据中params
下的customField
的值。{{ site.Title }}
或{{ .Site.Title }}
: 输出站点标题。(site
是一个全局可用的变量,指向站点对象;在页面上下文中,.Site
也可以访问)。
-
管道符
|
:- 用于将一个表达式的结果传递给下一个函数作为其最后一个参数(或在某些情况下是第一个)
{{ .Content | markdownify }}
: 将页面的 Markdown 内容.Content
传递给markdownify
函数进行渲染。
示例:
{{ "hello world" | upper | printf "Formatted: %s" }}
: “hello world” -> “HELLO WORLD” -> “Formatted: HELLO WORLD” -
比较函数:
eq
: 等于 (e.g.,{{ if eq .Title "首页" }}
)ne
: 不等于lt
: 小于le
: 小于等于gt
: 大于ge
: 大于等于
-
逻辑操作符:
{{ if and (gt .WordCount 100) .Params.hasSummary }}
(逻辑与){{ if or .IsHome .IsSection }}
(逻辑或){{ if not .IsPage }}
(逻辑非)
条件与循环语句
-
条件语句 (
if
,else if
,else
,end
):go-html-template{{ if .Params.showAuthor }} <p>作者: {{ .Params.authorName }}</p> {{ else if .Params.showEditor }} <p>编辑: {{ .Params.editorName }}</p> {{ else }} <p>作者信息未提供。</p> {{ end }}
条件可以是布尔值、指针、数组、切片、字符串、映射等。非零数字、非空字符串/集合/映射、非
nil
指针被视为true
。 -
with
语句 (with/else/end
): 用于检查一个变量是否有“真”值,并在其块内将上下文.
重新绑定到该变量的值。如果变量为“假”(如nil
或空字符串),则with
块不执行,可以有else
块。go-html-template{{ with .Params.featuredImage }} <img src="{{ .RelPermalink }}" alt="特色图片"> {{/* 这里的 . 指向 featuredImage 对象 */}} {{ else }} <p>没有特色图片。</p> {{ end }}
-
循环 (
range
,else
,end
): 用于遍历数组、切片、映射或通道。go-html-template{{ range .Site.Menus.main }} <li><a href="{{ .URL }}">{{ .Name }}</a></li> {{/* 这里的 . 指向当前菜单项 */}} {{ else }} <p>没有菜单项。</p> {{ end }}
遍历映射时:
{{ range $key, $value := .Params }}
Hugo模板函数
- 字符串处理:
upper
,lower
,title
(首字母大写),humanize
,urlize
,substr
,replace
,strings.HasPrefix
,strings.Contains
,strings.TrimSuffix
,chomp
(移除末尾换行) - 数学:
add
,sub
,mul
,div
,mod
,math.Max
,math.Min
- 日期和时间:
now
(当前时间对象),.Date.Format "layout"
,time "string"
(将字符串转为时间对象) - 内容处理:
markdownify
(渲染 Markdown),plainify
(移除 HTML 标签),safeHTML
,safeCSS
,safeJS
,safeURL
(标记内容为安全,避免转义) - URL 和路径:
relURL
,absURL
,relLangURL
,absLangURL
- 数据处理:
jsonify
,transform.Unmarshal
- 集合操作:
first N
,last N
,after N
,slice collection start [end]
,in collection item
,intersect coll1 coll2
,union coll1 coll2
,uniq collection
,len collection
- 条件:
default value fallback
,isset object "key"
- 字典/映射:
dict "key1" val1 "key2" val2
- 资源处理 (Hugo Pipes):
resources.Get
,resources.FromString
,minify
,fingerprint
,images.Resize
,css.Sass
,js.Build
等。 - 调试:
printf
,warnf
,errorf
模板组织与继承
Hugo 的模板系统通过一套优雅的继承和组合机制,允许开发者创建既灵活又易于维护的网站布局。核心概念包括基础模板 (baseof.html
)、块 (block
)、定义 (define
) 和局部模板 (_partials/
)。
1. baseof.html
- 网站的统一骨架
baseof.html
文件是 Hugo 模板继承系统的基石。它通常位于 layouts/
目录(或 layouts/_default/
在旧版 Hugo 中,但新版推荐直接在 layouts/
)。
-
作用:
- 定义整个网站所有(或大部分)页面共享的HTML骨架结构。这包括
<html>
,<head>
,<body>
标签,以及通常不变的页眉 (header)、页脚 (footer)、主要的导航结构等。 - 通过块 (
block
) 机制,在骨架中预留出可被具体页面模板填充或覆盖的区域。
- 定义整个网站所有(或大部分)页面共享的HTML骨架结构。这包括
-
示例
layouts/baseof.html
:go-html-template<!DOCTYPE html> <html lang="{{ .Site.LanguageCode | default "en-us" }}"> <head> {{- partial "head-content.html" . -}} {{/* 引入通用的 head 内容 */}} {{- block "head-extra" . }}{{ end -}} {{/* 为特定页面预留的额外 head 内容区 */}} </head> <body class="flex flex-col min-h-screen"> <div id="page-content-wrapper" class="flex-grow"> <header id="site-header" role="banner"> {{- partial "header.html" . -}} {{/* 引入通用页眉 */}} </header> {{/* 主体内容块,由子模板决定其内部结构 */}} {{- block "body-content" . -}} {{/* 默认行为:单栏主内容区 */}} <div class="container mx-auto py-8"> <main id="main-content-default" role="main"> {{- block "main" . }}{{/* 主要内容填充区 */}}{{- end -}} </main> </div> {{- end -}} </div> <footer id="site-footer" role="contentinfo"> {{- partial "footer.html" . -}} {{/* 引入通用页脚 */}} </footer> {{- partialCached "body-scripts.html" . -}} {{/* 引入通用的 body 底部脚本 */}} </body> </html>
-
最佳实践: 一个站点通常只需要一个主
baseof.html
文件。你也可以为特定的区段或内容类型创建不同的
baseof.html
文件 (例如layouts/posts/baseof.html
),它们会覆盖全局的baseof.html
,为该区段提供完全不同的骨架。但这种做法相对少见,通常通过在一个baseof.html
中定义多个block
来实现不同页面的差异化。
2. {{ block "name" . }}
- 在骨架中定义可覆盖区域
block
关键字用于在基础模板 (baseof.html
) 中定义一个命名区域,这个区域的内容可以被子模板(如 page.html
, home.html
等)提供或覆盖。
-
语法:
{{ block "blockName" .Context }}
可选的默认内容{{ end }}
"blockName"
: 你为这个块指定一个唯一的名称(字符串)。.Context
: 通常传递当前上下文 (.
) 给这个块,这样默认内容或子模板填充的内容可以访问它。- 可选的默认内容: 如果子模板没有定义(
define
)这个块,那么块内部的默认内容(如果有的话)将会被渲染。
-
示例 (在
baseof.html
中):go-html-template<head> <title>{{ block "title" . }}{{ .Site.Title }}{{ end }}</title> {{/* "title" 块,默认显示站点标题 */}} {{ block "head-extra" . }}{{ end }} {{/* "head-extra" 块,默认无内容 */}} </head> <body> {{ block "main" . }} <p>这是 main 块的默认内容,如果子模板没有定义 main 块,你就会看到我。</p> {{ end }} </body>
-
嵌套块: 你可以在一个
block
内部定义另一个block
,实现更细粒度的控制。go-html-template{{ block "content" . }} <article> {{ block "article-header" . }}<h2>默认文章标题</h2>{{ end }} {{ block "article-body" . }}<p>默认文章内容。</p>{{ end }} </article> {{ end }}
3. {{ define "name" . }}
- 在子模板中填充或覆盖区域
define
关键字用于在子模板中(例如 layouts/page.html
, layouts/home.html
, layouts/section.html
等)提供 baseof.html
中同名 block
的具体实现。
-
语法:
{{ define "blockName" .Context }}
此块的具体 HTML 和模板逻辑{{ end }}
"blockName"
: 必须与baseof.html
中block
的名称完全匹配。.Context
: 通常是.
,表示这个define
块内的代码将使用子模板自身的上下文(通常是当前页面对象)。
-
工作原理: 当 Hugo 渲染一个页面时(例如,渲染一篇博客文章):
- Hugo 根据页面的类型、区段、布局等因素选择一个最终的内容模板 (例如
layouts/posts/single.html
或回退到layouts/page.html
)。 - 然后,Hugo 查找这个内容模板所基于的基础模板 (
baseof.html
)。 - Hugo 会先处理
baseof.html
。当遇到{{ block "name" . }}
时,它会检查最终的内容模板是否{{ define "name" . }}
了这个块。- 如果内容模板中有对应的
define
,则baseof.html
中的block
区域将被内容模板中define
的内容替换。 - 如果内容模板中没有对应的
define
,则baseof.html
中的block
区域将使用其自身的默认内容(如果块定义时提供了默认内容)。如果块定义时也没有默认内容(如{{ block "name" . }}{{ end }}
),则该区域为空。
- 如果内容模板中有对应的
- Hugo 根据页面的类型、区段、布局等因素选择一个最终的内容模板 (例如
-
示例
layouts/page.html
:go-html-template{{/* layouts/page.html */}} {{/* 这个模板隐式地继承了 layouts/baseof.html */}} {{/* 覆盖 baseof.html 中的 "title" 块 */}} {{ define "title" }} {{ .Title }} | {{ .Site.Title }} {{/* 文章页标题格式 */}} {{ end }} {{/* 覆盖 baseof.html 中的 "main" 块 */}} {{ define "main" }} <article> <h1>{{ .Title }}</h1> <div class="content"> {{ .Content }} </div> </article> {{ end }} {{/* page.html 没有 define "head-extra",所以如果 baseof.html 的 head-extra 块是空的, 那么最终渲染的 HTML 中,head-extra 部分也会是空的。 如果 baseof.html 的 head-extra 块有默认内容,则会使用那个默认内容。 */}}
-
define
块的位置:define
块可以出现在子模板文件的任何顶层位置,它们的顺序不重要。Hugo 会先解析所有define
,然后再与baseof.html
组合。 -
注意: 在
{{ define "blockName" . }}
和{{ end }}
语句之外,只允许出现模板注释 ({{/* ... */}}
)。任何其他文本(包括 HTML 注释<!-- -->
或普通文本)都可能导致模板解析错误或意外的空白输出。
4. layouts/_partials/
- 可复用的 HTML 片段 (局部模板)
局部模板 (Partials) 是存放在 layouts/_partials/
目录(或主题的 themes/THEME_NAME/layouts/_partials/
)下的小块 HTML 模板代码。它们用于封装那些可能在多个不同模板中重复使用的 UI 组件或代码片段。
-
作用:
- 代码复用 (DRY - Don’t Repeat Yourself): 避免在多个模板中复制粘贴相同的 HTML 结构。
- 模块化和组织性: 将复杂的页面分解为更小、更易于管理的部分。
- 易于维护: 修改一个局部模板,所有引用它的地方都会自动更新。
-
创建: 在
layouts/_partials/
目录下创建.html
文件,例如:layouts/_partials/site-header.html
layouts/_partials/post-card.html
layouts/_partials/sidebar/categories-widget.html
(可以有子目录)
-
调用: 在任何其他模板文件中(包括
baseof.html
、页面模板、甚至其他局部模板中),使用partial
或partialCached
函数来嵌入一个局部模板。-
{{ partial "path/to/partialName.html" .Context }}
:"path/to/partialName.html"
: 局部模板文件相对于layouts/_partials/
目录的路径。如果 partial 在_partials/
根目录下,则直接用文件名,例如"footer.html"
。如果在子目录中,例如_partials/components/button.html
,则用"components/button.html"
。.Context
: 非常重要! 这是传递给局部模板的上下文。通常我们传递.
(当前上下文),这样局部模板就能访问到调用它的模板所拥有的数据(例如当前页面对象.
, 站点对象.Site
等)。如果不传递上下文,局部模板内部的.
将是nil
或一个空的上下文。
-
{{ partialCached "path/to/partialName.html" .Context [cacheKeyParam1] [cacheKeyParam2] ... }}
:- 与
partial
类似,但 Hugo 会尝试缓存这个局部模板的渲染结果。 - 如果一个局部模板的内容不经常变化,或者其输出仅依赖于少数几个参数,使用
partialCached
可以显著提高构建速度,尤其是在大型站点或复杂 partial 中。 [cacheKeyParam...]
: (可选) 提供额外的参数作为缓存键的一部分。如果这些参数的值发生变化,Hugo 会重新渲染该 partial。如果上下文.
很复杂且经常变化,而 partial 的输出仅依赖于其中一小部分,那么明确提供这些小的依赖作为缓存键会更有效。
- 与
-
-
示例:
layouts/_partials/copyright.html
:go-html-template<p>© {{ now.Year }} {{ .Site.Title }}</p>
在
layouts/_partials/footer.html
中调用:go-html-template<footer> {{ partial "copyright.html" . }} <p>All rights reserved.</p> </footer>
-
局部模板的上下文:
- 局部模板接收调用它时传递的上下文。
- 在局部模板内部,
.
指向这个传递过来的上下文。 - 如果需要访问调用
partial
函数的那个模板的顶层上下文(例如,如果 partial 被嵌套调用了),可以使用$.
。
-
局部模板返回值: 局部模板不仅可以输出 HTML,还可以通过
{{ return $value }}
语句返回一个值 (任何类型)。这使得 partial 可以像函数一样使用。layouts/_partials/get-featured-image.html
:go-html-template{{ $image := "" }} {{ with .Resources.GetMatch "featured.*" }} {{ $image = .RelPermalink }} {{ else with .Site.Params.defaultFeaturedImage }} {{ $image = . | relURL }} {{ end }} {{ return $image }}
调用:
{{ $featuredImgSrc := partial "get-featured-image.html" . }}
核心页面模板
在 Hugo 中,layouts/
目录下的不同 HTML 文件充当了渲染不同种类和类型页面的“蓝图”。Hugo 通过一套精密的模板查找顺序 (Template Lookup Order) 来为每一个需要生成的页面选择最合适的模板。
所有这些具体的页面模板(如 section.html
, page.html
)通常都会隐式地或显式地继承 layouts/baseof.html
(如果存在)。它们通过 {{ define "blockName" . }}
来填充或覆盖 baseof.html
中定义的 {{ block "blockName" . }}
区域。
以下是 Hugo中最常用的一些具体页面模板文件名及其详细描述:
1. layouts/home.html
(首页模板)
layouts/home.html
(首页模板)作用:- 专门用于渲染你网站的首页 (即访问你网站根 URL
baseURL/
时看到的页面)。 - 这是唯一一个 Hugo 页面种类 (Page Kind) 为
home
的页面。
- 专门用于渲染你网站的首页 (即访问你网站根 URL
- 原理:
- 当 Hugo 构建首页时,它会优先查找
layouts/home.html
。 - 模板的上下文
.
是代表首页的页面对象,其内容和元数据通常来自站点根目录下的content/_index.md
文件 (如果存在)。 - 它会填充
baseof.html
中的块。
- 当 Hugo 构建首页时,它会优先查找
- 典型内容:
- 网站的欢迎信息、品牌展示。
- 最新文章列表、特色内容摘要。
- 指向网站其他重要部分的链接。
- 示例 (
layouts/home.html
):go-html-template{{ define "title" }}{{ .Site.Title }}{{ with .Site.Params.tagline }} - {{ . }}{{ end }}{{ end }} {{ define "main" }} <h1>{{ .Site.Title }}</h1> {{ with .Site.Params.description }}<p>{{ . }}</p>{{ end }} {{ with .Content }}<div>{{ . | markdownify }}</div>{{ end }} {{/* 来自 content/_index.md */}} <h2>最新博文</h2> <ul> {{ range first 5 (where site.RegularPages "Type" "posts") }} <li><a href="{{ .RelPermalink }}">{{ .Title }}</a></li> {{ end }} </ul> {{ end }}
2. layouts/page.html
和layouts/single.html
layouts/page.html
(单页模板 - 推荐的通用名称)或layouts/single.html
(单页模板 - 传统名称)作用:- 用于渲染单个、独立的内容页面,例如一篇博客文章、一个“关于我们”页面、一个产品详情页等。
- 对应 Hugo 页面种类 (Page Kind) 中的
page
。 - 注意: Hugo v0.146.0+ 倾向于使用
page.html
作为比single.html
优先级更高的通用单页模板名。如果两者都存在于同一查找路径,page.html
会被优先选择。通常,在一个项目中,你会选择使用其中一个作为主要的通用单页模板。
- 原理:
- 当 Hugo 渲染一个常规内容文件 (例如
content/posts/my-article.md
或一个 Leaf Bundle 如content/about/index.md
) 时,它会查找对应的单页模板。 - 模板的上下文
.
是代表当前这篇文章或页面的页面对象。 - 它会填充
baseof.html
中的块。
- 当 Hugo 渲染一个常规内容文件 (例如
- 典型内容:
- 文章标题、发布日期、作者、分类、标签等元信息。
- 文章的主要内容 (
.Content
)。 - 相关文章链接、评论区等。
- (如我们教程中)左侧区段导航、右侧内容目录。
- 示例 (
layouts/page.html
):go-html-template{{ define "title" }}{{ .Title }} | {{ .Site.Title }}{{ end }} {{ define "main" }} <article> <h1>{{ .Title }}</h1> <p>发布于: {{ .Date.Format "January 2, 2006" }}</p> <div>{{ .Content }}</div> </article> {{ end }}
3. layouts/section.html
layouts/section.html
(区段列表模板)作用:- 用于渲染区段 (Section) 的列表页面。区段通常对应于
content/
目录下的顶级文件夹(如content/posts/
)或嵌套文件夹(如content/docs/v1/
)。 - 这个页面会列出该区段下的内容(文章、子区段等)。
- 对应 Hugo 页面种类 (Page Kind) 中的
section
。
- 用于渲染区段 (Section) 的列表页面。区段通常对应于
- 原理:
- 当用户访问一个区段的 URL (例如
yoursite.com/posts/
或yoursite.com/docs/v1/
) 时,如果该区段目录下存在_index.md
文件,Hugo 会使用这个_index.md
作为该区段列表页的内容源和元数据源。 - Hugo 会查找用于渲染此区段列表页的模板。
- 模板的上下文
.
是代表当前区段列表页的页面对象 (来自其_index.md
)。 - 它会填充
baseof.html
中的块。
- 当用户访问一个区段的 URL (例如
- 典型内容:
- 区段的标题和描述 (来自
_index.md
)。 - 该区段下所有文章或子页面的列表(通常带分页)。
- 指向子区段的链接 (如果存在)。
- 区段的标题和描述 (来自
- 示例 (
layouts/section.html
):go-html-template{{ define "title" }}{{ .Title }} | {{ .Site.Title }}{{ end }} {{ define "main" }} <h1>{{ .Title }}</h1> <div>{{ .Content | markdownify }}</div> <ul> {{ $paginator := .Paginate .RegularPages.ByDate.Reverse }} {{ range $paginator.Pages }} <li> <h2><a href="{{ .RelPermalink }}">{{ .Title }}</a></h2> <p>{{ .Summary }}</p> </li> {{ end }} </ul> {{ partial "pagination.html" (dict "paginator" $paginator) }} {{ end }}
4. layouts/taxonomy.html
- layouts/taxonomy.html` (分类法列表模板)作用:
- 用于渲染一个分类法 (Taxonomy) 本身的列表页面。这个页面通常会列出该分类法下的所有词条 (Terms)。
- 例如,访问
/categories/
会显示所有已定义的分类(如 “技术”, “生活”);访问/tags/
会显示所有已定义的标签。 - 对应 Hugo 页面种类 (Page Kind) 中的
taxonomy
。
- 原理:
- 当用户访问一个分类法的根 URL (例如
yoursite.com/categories/
) 时,Hugo 会查找此模板。 - 如果存在对应的
content/taxonomyName/_index.md
(例如content/categories/_index.md
),它将作为该页面的内容源。 - 模板的上下文
.
是代表当前分类法列表页的页面对象。 - 它会填充
baseof.html
中的块。
- 当用户访问一个分类法的根 URL (例如
- 典型内容:
- 分类法的标题 (例如 “所有分类”)。
- 该分类法下所有词条的列表,通常每个词条会显示其名称、链接以及关联的文章数量。
- 示例 (
layouts/taxonomy.html
):go-html-template{{ define "title" }}{{ .Title }} | {{ .Site.Title }}{{ end }} {{ define "main" }} <h1>{{ .Title }}</h1> {{ with .Content }}<div>{{ . | markdownify }}</div>{{ end }} <ul> {{ range .Data.Terms.ByCount }} {{/* 按文章数量排序词条 */}} <li> <a href="{{ .Page.RelPermalink }}">{{ .Page.Title | humanize }}</a> <span>({{ .Count }})</span> </li> {{ end }} </ul> {{ end }}
5. layouts/term.html
layouts/term.html
(词条列表模板)作用:- 用于渲染属于某个特定分类法词条 (Term) 的所有内容页面的列表。
- 例如,访问
/categories/hugo/
会显示所有被归类到 “hugo” 这个分类下的文章。 - 对应 Hugo 页面种类 (Page Kind) 中的
term
。
- 原理:
- 当用户访问一个特定词条的 URL (例如
yoursite.com/categories/hugo/
) 时,Hugo 会查找此模板。 - 如果存在对应的
content/taxonomyName/termName/_index.md
(例如content/categories/hugo/_index.md
),它将作为该页面的内容源。 - 模板的上下文
.
是代表当前词条列表页的页面对象。 - 它会填充
baseof.html
中的块。
- 当用户访问一个特定词条的 URL (例如
- 典型内容:
- 词条的标题 (例如 “分类:Hugo”)。
- 列出所有属于该词条的文章(通常带分页)。
- 示例 (
layouts/term.html
):go-html-template{{ define "title" }}{{ .Title }} ({{ .Data.Singular | humanize }}) | {{ .Site.Title }}{{ end }} {{ define "main" }} <h1>{{ .Title }}</h1> <p>属于 "{{ .Data.Plural | humanize }}" 分类法</p> {{ with .Content }}<div>{{ . | markdownify }}</div>{{ end }} <ul> {{ $paginator := .Paginate .Pages.ByDate.Reverse }} {{ range $paginator.Pages }} <li> <h2><a href="{{ .RelPermalink }}">{{ .Title }}</a></h2> <p>{{ .Summary }}</p> </li> {{ end }} </ul> {{ partial "pagination.html" (dict "paginator" $paginator) }} {{ end }}
6. layouts/list.html
layouts/list.html
(通用列表模板)作用:- 这是一个通用的回退列表模板。如果 Hugo 在为某个列表类型的页面(如
section
,taxonomy
,term
)查找更具体的模板(如section.html
或taxonomy.html
)时没有找到,它最终会尝试使用layouts/list.html
。
- 这是一个通用的回退列表模板。如果 Hugo 在为某个列表类型的页面(如
- 原理:
- 它的上下文
.
会是当前正在渲染的列表页面对象(可能是区段、分类法或词条)。 - 因此,
list.html
需要编写得足够通用,能够处理不同列表类型的共性(例如,它们都有.Title
,.Content
,.Pages
或.Data.Terms
等)。
- 它的上下文
- 典型内容:
- 显示
.Title
和.Content
。 - 根据
.Kind
或其他条件来决定是遍历.Pages
(对于 section, term) 还是.Data.Terms
(对于 taxonomy)。 - 包含分页逻辑。
- 显示
- 何时使用:
- 如果你的所有列表页(或大部分)具有非常相似的布局和功能,你可以只创建一个
list.html
,而不是为section
,taxonomy
,term
分别创建模板。 - 或者,你可以创建
section.html
,taxonomy.html
,term.html
作为主要的列表模板,而将list.html
作为一种不太可能被触发的最终安全网,或者用于一些你没有预料到的自定义列表类型。
- 如果你的所有列表页(或大部分)具有非常相似的布局和功能,你可以只创建一个
模板的查找顺序与特化
Hugo 有一个非常明确的模板查找顺序,它总是从最具体到最通用。
简化版的查找逻辑 (以渲染 /posts/my-category/_index.md
这个子区段列表页为例):
layouts/posts/my-category/section.html
(最具体:特定路径下的特定类型)layouts/posts/my-category/list.html
layouts/my-category/section.html
(按类型或区段名)layouts/my-category/list.html
layouts/posts/section.html
(父区段的)layouts/posts/list.html
layouts/section.html
(全局默认区段模板)layouts/list.html
(全局默认列表模板)themes/THEME_NAME/layouts/...
(在主题中按类似顺序查找)
这意味着:
- 你可以通过在更具体的路径下创建模板来覆盖 (override) 更通用的模板。
- 例如,如果你有一个通用的
layouts/section.html
,但希望/posts/
区段有完全不同的列表布局,你只需创建layouts/posts/section.html
即可。
通过前置元数据指定模板:
除了基于路径和类型的查找,你还可以在任何内容文件(包括 _index.md
和 index.md
)的前置元数据中通过 layout
或 type
参数来强制 Hugo 使用特定的模板文件:
layout: "my-custom-layout"
: Hugo 会查找layouts/my-custom-layout.html
(或者根据页面种类,如layouts/_default/my-custom-layout.html
在旧版中,或直接在layouts/
下查找)。layout
参数指定的模板通常会覆盖基于种类和区段的查找。type: "product"
: Hugo 会在layouts/product/
目录下查找对应的模板 (例如layouts/product/list.html
或layouts/product/single.html
),这个查找优先级很高。
Shortcodes 和 Hooks
Hugo 不仅仅是一个简单的 Markdown 到 HTML 的转换器。它提供了强大的扩展机制,如短代码 (Shortcodes) 和渲染钩子 (Render Hooks),能够轻松地在内容中嵌入复杂元素或自定义 Markdown 的渲染行为。
短代码 (Shortcodes)
短代码 (Shortcodes) - 能在内容中更方便的添加丰富的功能,如视频播放器、图片库、提示框、自定义组件等。短代码主要分为下列两种类型:
-
简单短代码 (Simple Shortcodes): 不包含结束标签,通常用于插入简单的、自闭合的 HTML 元素或简短内容。
Markdown 中的调用方式:
{{/* < shortcodeName param1="value1" param2="value2" > */}}
。 或者使用位置参数:{{/*< shortcodeName "value1" "value2" >*/}}
-
闭合短代码 (Closing Shortcodes): 包含开始标签和结束标签,它们之间的内容(称为
.Inner
内容)可以被短代码模板捕获和处理。Markdown 中的调用方式:markdown{{< shortcodeName param1="value1" >}} 这里是 **Markdown** 格式的 _内部内容_。 {{< /shortcodeName >}}
一. 如何创建自定义短代码
1. 自定义短代码的存放位置:
- 在项目或主题根目录下的
layouts/_shortcodes/
目录中创建.html
文件。文件名(不含扩展名)即为短代码的名称。例如,layouts/_shortcodes/youtube.html
将定义一个名为youtube
的短代码。 - 可以在
_shortcodes/
下创建子目录来组织它们,调用时需要指定相对路径,例如{{< media/video src="..." >}}
对应layouts/_shortcodes/media/video.html
。
短代码的查找顺序:
Hugo 会首先在项目根目录的
layouts/_shortcodes/
中查找,然后在主题的layouts/_shortcodes/
中查找。这意味着可以通过在项目中创建同名文件来覆盖主题提供的短代码。
2. 短代码模板 (.html
文件) 的内容: 短代码模板是标准的Hugo模板,可使用所有可用的模板变量和函数
-
访问参数: 使用
.Get "paramName"
: 获取命名参数的值。.Get N
: 获取位置参数的值 (N 是从0开始的索引)。.Params
: 一个包含所有传递给短代码的参数的集合。- 如果使用命名参数,
.Params
是一个映射 (map)。 - 如果使用位置参数,
.Params
是一个切片 (slice)。
- 如果使用命名参数,
.IsNamedParams
: 布尔值,判断调用时是否使用了命名参数。
-
访问内部内容 (
.Inner
): 仅对闭合短代码有效。.Inner
变量包含开始和结束短代码标签之间的原始文本内容。- 通常需要用
| markdownify
(如果内部内容是 Markdown) 或.Page.RenderString
(更通用的渲染方式,也会处理短代码) 来处理.Inner
以得到渲染后的 HTML。go-html-template{{ .Inner | .Page.RenderString (dict "display" "block") }}
-
访问页面上下文 (
.Page
):.Page
变量提供了对调用该短代码的那个页面的完整页面对象的访问权限。这允许短代码根据其所在的页面上下文来调整行为。例如,{{ .Page.Title }}
。 -
其他有用的短代码变量:
.Name
: 短代码的文件名(不含扩展名)。.Ordinal
: 短代码在其父级内容中的顺序(从0开始)。.Parent
: 如果短代码被嵌套在另一个短代码内部,.Parent
提供了对父短代码上下文的访问。.Position
: 调用短代码的源文件名和行号,非常适用于错误报告 (warnf
,errorf
)。.Scratch
: 短代码作用域的“草稿本”,用于存储和操作临时数据。
示例1:简单的 youtube.html
短代码: layouts/_shortcodes/youtube.html
:
{{- $id := .Get 0 | default (.Get "id") -}}
{{- if $id -}}
<div class="youtube-embed-wrapper" style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden; max-width: 100%;">
<iframe style="position: absolute; top: 0; left: 0; width: 100%; height: 100%;"
src="https://www.youtube.com/embed/{{ $id }}"
title="YouTube video player"
frameborder="0"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
allowfullscreen>
</iframe>
</div>
{{- else -}}
{{- warnf "YouTube shortcode called without an ID. Position: %s" .Position -}}
{{- end -}}
在 Markdown 中使用: {{< youtube "VIDEO_ID_HERE" >}}
或 {{< youtube id="VIDEO_ID_HERE" >}}
示例2:自定义提示框 alert.html
(闭合短代码): layouts/_shortcodes/alert.html
:
{{ $type := .Get "type" | default "info" }}
<div class="alert alert-{{ $type }}" role="alert">
{{ with .Get "title" }}<h4>{{ . }}</h4>{{ end }}
{{ .Inner | .Page.RenderString (dict "display" "block") }}
</div>
在 Markdown 中使用:
{{< alert type="warning" title="重要提示" >}}
这是一个**警告**信息,请仔细阅读。
列表:
* 项目一
* 项目二
{{< /alert >}}
二. Hugo 内置短代码
Hugo 提供了一些有用的内置短代码,例如:
figure
: 用于插入带标题和属性的图片。gist
: 嵌入 GitHub Gist。highlight
: 高亮显示代码块 (虽然现在更推荐使用 Markdown 的代码围栏)。ref
和relref
: 创建对其他内部内容页面的健壮链接。tweet
: 嵌入推文。 查阅 Hugo 官方文档以获取完整的内置短代码列表及其用法。
渲染钩子 (Render Hooks)
当想改变某些 Markdown 元素(如图片、链接、标题、代码块)默认渲染成 HTML 的方式时,可以使用渲染钩子 (Render Hooks)。
-
渲染钩子允许你为特定的 Markdown 元素提供自定义的 HTML 模板。当 Hugo 的 Markdown 处理器 (Goldmark 是默认的)遇到这些元素时,它会使用你的自定义模板来生成 HTML,而不是使用其内置的默认渲染逻辑。
-
Hugo 支持以下几种元素的渲染钩子:
- 链接 (Link):
render-link.html
- 图片 (Image):
render-image.html
- 标题 (Heading):
render-heading.html
- 代码块 (Code Block):
render-codeblock.html
(以及更具体的render-codeblock-LANGUAGE.html
)
- 链接 (Link):
何时使用渲染钩子?
当需要全局性地、统一地改变某种 Markdown 元素的默认 HTML 输出时。 例如:所有外部链接都自动在新标签页打开;所有图片都自动包裹在
<figure>
中并添加特定的 CSS 类;所有标题都自动带有锚链接。
如何创建渲染钩子
-
存放位置: 在项目或主题根目录下的
layouts/_markup/
目录中创建与钩子类型对应的.html
文件- 例如,自定义链接渲染:
layouts/_markup/render-link.html
- 例如,自定义图片渲染:
layouts/_markup/render-image.html
- 例如,自定义链接渲染:
-
渲染钩子模板的内容: 钩子模板会接收一些特定的上下文变量,这些变量包含了关于正在被渲染的 Markdown 元素的信息。
-
render-link.html
上下文:.Destination
: 链接的目标 URL。.Text
: 链接的显示文本(Markdown:[Text](Destination)
)。.Title
: 链接的title
属性(Markdown:[Text](Destination "Title")
)。.Page
: 当前页面的 Page 对象。
示例: (为外部链接添加
target="_blank"
和rel="noopener noreferrer"
):go-html-template<a href="{{ .Destination | safeURL }}" {{- with .Title }} title="{{ . }}"{{ end -}} {{- if strings.HasPrefix .Destination "http" }} target="_blank" rel="noopener noreferrer"{{ end -}} >{{ .Text | safeHTML }}</a>
-
render-image.html
上下文:.Destination
: 图片的 URL。.Text
: 图片的alt
文本(Markdown:
)。.Title
: 图片的title
属性(Markdown:
)。.Page
: 当前页面的 Page 对象。.Ordinal
: 图片在当前内容中的顺序。
示例 (
layouts/_markup/render-image.html
- 为图片添加一个包裹div
和figcaption):go-html-template<figure class="render-image-figure"> <img src="{{ .Destination | safeURL }}" alt="{{ .Text | plainify }}" {{- with .Title }} title="{{ . }}"{{ end -}} /> {{- with .Title -}} <figcaption>{{ . | markdownify }}</figcaption> {{- end -}} </figure>
-
render-heading.html
上下文:.Level
: 标题级别 (整数 1-6)。.AnchorID
: Hugo 为该标题生成的 ID (用于锚链接)。.Text
: 标题的文本内容 (已处理 Markdown)。.Page
: 当前页面的 Page 对象。
示例 (
layouts/_markup/render-heading.html
- 为标题添加一个可点击的锚链接图标):go-html-template<h{{ .Level }} id="{{ .AnchorID }}"> {{ .Text | safeHTML }} <a href="#{{ .AnchorID }}" class="anchor-link" aria-label="锚链接到此标题">#</a> </h{{ .Level }}>
(还需要为
.anchor-link
添加 CSS 样式使其在悬停时可见等) -
render-codeblock.html
和render-codeblock-LANGUAGE.html
上下文:.Type
: 代码块的类型(通常是语言名称)。.Inner
: 代码块的原始文本内容。.Options
: 从代码围栏信息字符串中解析出的选项(例如hl_lines="1 3-5"
)。.Ordinal
: 代码块在内容中的顺序。.Page
: 当前页面的 Page 对象。
示例 (
layouts/_markup/render-codeblock.html
- 添加一个复制按钮,实际复制功能需 JS):go-html-template{{ $code := .Inner }} {{ $lang := .Type | default "" }} <div class="codeblock-wrapper"> <div class="codeblock-header"> <span class="codeblock-language">{{ $lang }}</span> <button class="copy-code-button" aria-label="复制代码">复制</button> </div> {{/* 使用 Hugo 内置的 highlight 函数处理代码高亮 */}} {{ highlight $code $lang (.Options | default "") }} </div>