Featured image of post Hugo 静态网站建站手册

Hugo 静态网站建站手册

Hugo 官方文档的详尽总结与中文翻译,提供良好的静态网站搭建学习与查阅帮助

注意:

  • 本文档为对 官方文档 的总结和中文翻译(详尽)。整理该文档供学习和查阅使用

  • Hugo 默认支持 yaml toml json 的配置形式,对 yaml 的支持最好。为方便后面的内容编写,本文全部采用 yaml 配置,其他配置形式自行参阅转换

  • Hugo 基于 Golang 的 html/template 语法,如果你完全不懂 Go,可以参阅 万字速通 Golang

简介

关于 Hugo

Hugo 安全模型

运行时安全

Hugo 生成静态文件,因此一旦构建,运行时就是浏览器与集成的服务。但在开发和构建时,hugo 是一个可执行文件

Hugo 保证安的主要方法是沙箱和严格的安全策略:

  • 虚拟文件系统,只有主项目(不含第三方)被允许挂载项目之外的目录
  • 只有主项目可以遍历符号链接
  • 用户对文件系统只读
  • 使用一些外部的二进制文件支持 ASCII 和类似的文件。但是这些都是预定义的,默认情况下是禁止的( 查看安全策略

安全策略

Hugo 内置安全策略,限制对 os exec 远程通信 的访问

任何使用不在安全策略允许列表中的功能的构建都将失败,默认配置如下所示 config.yaml

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
security:
  enableInlineShortcodes: false
  exec:
    allow:
    - ^dart-sass-embedded$
    - ^go$
    - ^npx$
    - ^postcss$
    osEnv:
    - (?i)^((HTTPS?|NO)_PROXY|PATH(EXT)?|APPDATA|TE?MP|TERM|GO\w+)$
  funcs:
    getenv:
    - ^HUGO_
    - ^CI$
  http:
    methods:
    - (?i)GET|POST
    urls:
    - .*

注:这些和其他配置设置可以被OS环境覆盖

  • 如果要阻止所有远程HTTP获取数据:HUGO_SECURITY_HTTP_URLS=none hugo

依赖安全

Hugo构建为一个静态二进制文件,使用Go模块来管理其依赖关系

建议将此文件提交到版本控制系统。如果校验和不匹配,Hugo构建将失败,这表明存在依赖性篡改。

Web 应用安全

模板和配置作者受信任,但是发送的数据不受信任

  • 内容文件中的端短代码和数据处理是可信的
  • Hugo 生成的是静态站点,没有用户输入的概念
  • 默认 删除/转义 潜在的不安全内容

通用数据保护规则(GDPR)

General Data Protection Regulation(GDPR),关于如何配置站点以满足新规则:

  • 没有数据库,直接通过 markdown 生成静态 HTML

  • 可以与外部服务集成(@v0.41+),Hugo 提供一个包含相关内置模板的隐私配置

注意

  1. 这些设置默认关闭,必须对网站进行自己的评估,并应用适当的设置
  2. 适用于内部模板,某些主题可能包含用于嵌入Google Analytics等服务的自定义模板。在这种情况下,这些选项无效
  3. 在未来的版本中进一步改进

所有隐私设置

以下是所有隐私设置。这些设置需要放在站点配置中 config.yaml

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
privacy:
  disqus:
    disable: false
  googleAnalytics:
    disable: false
    anonymizeIP: true # 使用户的IP地址在Google Analytics中匿名
    respectDoNotTrack: true # 使GA模板尊重“不跟踪”HTTP标头
    useSessionStorage: true # 禁用Cookie的使用,并使用会话存储存储GA客户端ID,GA@4 不支持
  instagram:
    disable: false
    simple: true # 构建静态和非JS版本的Instagram图像卡,需要禁用内联样式:services.instagram.disableInlineCSS=true
  twitter:
    disable: false
    enableDNT: true # 推特及其在您网站上的嵌入页面,不会用于包括个性化建议和个性化广告的目的
    simple: true # 将生成一个静态的、无JS版本的推文,需要禁用内联样式:services.twitter.disableInlineCSS=true
  vimeo:
    disable: false
    enableDNT: true # vimeo播放器将被阻止跟踪任何会话数据,包括所有cookie和统计数据
    simple: true # 视频缩略图将从Vimeo的服务器中获取,并覆盖有播放按钮。如果用户单击播放视频,它将直接在Vimeo网站上的新选项卡中打开
  youtube:
    disable: true
    privacyEnhanced: true # 当打开隐私增强模式时,除非用户播放嵌入视频,否则YouTube不会在您的网站上存储访问者的信息

注:添加注释的默认值都为 false

禁用所有服务

禁用Hugo中所有相关服务的隐私配置示例。使用此配置,其他设置无关紧要

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
privacy:
  disqus:
    disable: true
  googleAnalytics:
    disable: true
  instagram:
    disable: true
  twitter:
    disable: true
  vimeo:
    disable: true
  youtube:
    disable: true

选择 Hugo

官方视频介绍(英文):

Hugo是一个用Go编写的快速、现代的静态网站生成器

  • 使用Hugo构建的网站非常快速和安全:
    1. 网站可以托管在任何地方,包括Netlify、Heroku、GoDaddy、DreamHost、GitHub Pages、GitLab Pages、Surge、Firebase、Google Cloud Storage、Amazon S3、Rackspace、Azure和CloudFront
    2. 并且可以与CDN协同工作。Hugo站点运行时不需要数据库或依赖昂贵的运行时

为什么速度块

从技术上讲,Hugo 使用文件和模板的源目录,并将其作为输入创建一个完整的网站

适用人群

喜欢在文本编辑器而不是浏览器中写作的人:

  • 为那些希望手工编写自己的网站而不必担心设置复杂的运行时、依赖关系和数据库的人而设计
  • 适用于创建博客、公司网站、投资组合网站、文档、单个登录页面或具有数千页的网站的用户

Hugo 特点

Hugo 拥有极快的速度、强大的内容管理和强大的模板语言,使其非常适合各种静态网站

注:当页面内容和页面数量较多时,基于 go 的 Hugo 速度具有明显优势

网站特点

  1. 极快的构建时间(每页<1毫秒)
  2. 完全跨平台,易于在macOS、Linux、Windows等平台上安装
  3. 在开发过程中使用 LiveReload 实时渲染更改
  4. 强大的主题
  5. 在任何地方托管您的网站

配置特点

  1. 项目的直接组织,包括网站部分
  2. 可自定义 URLs
  3. 支持可配置分类法,包括类别和标记
  4. 通过 强大的模板函数 按需对内容进行排序
  5. 自动生成目录
  6. 动态菜单创建
  7. Pretty URLs 支持
  8. Permalink 模式支持
  9. 通过别名重定向

内容特点

  1. 本机Markdown和Emacs Org Mode支持,以及通过外部助手提供的其他语言(请参阅支持的格式)
  2. OML、YAML和JSON元数据支持
  3. 可自定义主页
  4. 多种内容类型
  5. 自动定义的用户和内容摘要
  6. 短代码丰富 Markdown 内容
  7. 阅读分钟数
  8. 单词数量

其他特点

  1. 集成 Disqus 评论支持
  2. 集成的 Google Analytics 支持
  3. 自动创建 RSS
  4. 支持 Go HTML 模板
  5. 语法高亮显示,由 Chroma 提供

静态站点生成的优点

  1. 性能卓越
  2. 资源占用低
  3. 安全性高
  4. 利于 SEO

安装

前提条件

尽管并非在所有情况下都需要,但使用 Hugo 同步文档时经常使用 Git Go

版本

Hugo有两个版本:标准版和扩展版。使用扩展版,您可以:

  • 编码 WebP 图像(两个版本都可以)

  • 使用嵌入式 LibSass 转译器将 Sass 转为 CSS(很多主题有使用 Sass)

建议安装扩展版

源码安装

源码编译安装,首先安装好依赖的工具:

设置好 GOPATH 环境变量,获取源码并编译:

1
2
$ export GOPATH=$HOME/go
$ go get -v github.com/spf13/hugo

源码会下载到 $GOPATH/src 目录,二进制在 $GOPATH/bin/

如果需要更新所有Hugo的依赖库,增加 -u 参数:

1
$ go get -u -v github.com/spf13/hugo

二进制安装

Hugo Releases 下载对应的操作系统版本的Hugo二进制文件(hugo 或者 hugo.exe

Mac下直接使用 Homebrew 安装:

1
brew install hugo

注:尽量选择带 extended 的扩展版,除非你现在和将来使用的主题都不带 Sass

开始

命令

使用 hugo 命令生成站点,如果是 windows 系统:

  • 不要使用 Command Prompt 和 Windows PowerShell
  • 使用 PowerShell(与 Windows PowerShell 不一样)

创建站点

打开命令行工具,选择博客代码需要置放的目录

1
2
3
4
5
6
7
$ hugo new site quickstart # 新建 quickstart 目录
$ cd quickstart
$ git init # 初始化仓库,准备添加主题
$ git submodule add https://github.com/theNewDynamic/gohugo-theme-ananke themes/ananke
$ echo "theme = 'ananke'" >> config.toml # 配置文件配置主题(为主题目录名)
$ hugo # 生成静态页面到 public 文件夹下(不会删除上一次生成的,有需要可以先手动删除)
$ hugo server # 启动本地服务,预览站点

创建页面

1
$ hugo new posts/first.md # 在 quickstart/content/posts/ 下创建页面 first.md
  • 默认生成页面头部注释,配置页面元数据,用于站点预览、分类、聚合等
1
2
3
4
5
---
title: 第一篇文章
date: 2022-11-20T09:03:20-08:00
draft: true # 草稿
---

注:还有封面等其他元数据配置,前往 页面头部注释

  • 追加一些 markdown 内容在 first.md 注释之后
1
2
## 介绍
这是我的第一篇 **博客**
  • 启动本地测试服务,会监听文件内容更新,liveLoaded
1
2
3
4
5
# 由于新建的文章注释中:draft=true,hugo server 命令默认不会生成草稿页面
# 如果需要生成,执行下面的命令
$ hugo server --buildDrafts
# 或者
$ hugo server -D

配置站点

如果你进行了之前的操作,config.yaml 文件将会是:

1
2
3
4
5
baseURL: "http://example.org/"
languageCode: "en-us"
title: "My New Hugo Site"
theme: "ananke"
# 可以自行更改配置

其他命令

1
2
3
4
5
6
7
$ hugo version
$ hugo help
$ hugo server --help
$ hugo --buildDrafts    # or -D。同时生成 draft=true 的页面
$ hugo --buildExpired   # or -E。同时生成 expiryDate 时间小于当前时间的页面
$ hugo --buildFuture    # or -F。同时生成 date 或 publishDate 时间大于当前时间的页面
$ hugo server --navigateToChanged # 启动服务,且路由跳转到正在更新的页面

配置

站点目录

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
quickstart/
├── archetypes/
│   └── default.md # 指明新生成的页面默认的头部注释
├── assets/ # 存储所有需要由 Hugo Pipes 处理的文件。只有使用了 .Permalink 或 .RelPermalink 的文件才会发布到public 目录
├── config/ # 默认情况下不会创建。存储 yaml、toml、json 文件,几乎不用配置,但是 Hugo 提供了配置指令
├── content/ # 网站的所有内容都将位于此目录中。content 下的顶级文件夹表示分类
├── data/ # 存储Hugo在生成网站时可以使用的配置文件。也可以创建从动态内容中提取的数据模板
├── layouts/ # 以 .html 文件的形式存储模板,指定如何将内容视图呈现到静态网站中
├── public/ # hugo 命令生成的静态页面存储位置
├── static/ # 存储所有静态内容:图像、CSS、JavaScript等
├── themes/ # 主题目录
└── config.toml # 生成页面配置文件

配置目录

官方视频介绍(英文):

除了使用单个站点配置文件外,还可以使用 configDir 目录(默认为 config/)来更简单的组织和维护,环境特定设置

每个目录包含一组文件,其中包含环境特有的设置。文件可以本地化为特定语言

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
├── config
│   ├── _default # 默认,会合并目录下的每一个配置
│   │   ├── config.toml
│   │   ├── languages.toml
│   │   ├── menus.en.toml
│   │   ├── menus.zh.toml
│   │   └── params.toml
│   ├── production
│   │   ├── config.toml
│   │   └── params.toml
│   └── staging
│       ├── config.toml
│       └── params.toml

注意:

  • 运行 hugo server,Hugo 设置 Environment=development,该服务使用 _default 文件夹中的配置文件
  • 运行 hugo,Hugo 设置 Environment=production,合并 _default + production
  • staging 下的配置在两种环境下都会合并

重写默认配置文件

1
2
$ hugo --config debugconfig.toml
$ hugo --config a.toml,b.toml,c.toml

Markdown 标记配置

以下是Hugo中所有与标记相关的配置及其默认设置:

Goldmark

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
markup:
  goldmark:
    extensions: # markdown 扩展标记
      definitionList: true # 页面定义列表,换行以 [:+空格] 开头,编辑定义文本
      footnote: true # 脚注,文本中的标记将变成上标数字;将放在文档末尾脚注列表中的脚注定义。Text[^1]
      linkify: true # 链接
      linkifyProtocol: https
      strikethrough: true # 删除线。~~~text~~~
      table: true # 表格
      taskList: true # 有序和无序列表
      typographer: true # `text` 或 ``text`` 标记一段文本
    parser:
      attribute: # 启用自定义属性
        block: false # 块级元素解析。引用、列表、代码块添加 HTML 属性
        title: true # 标题解析。### 标题3
      autoHeadingID: true # 自动添加 h1-h6 id 属性
      autoHeadingIDType: github # github 的标题 id 策略
      wrapStandAloneImageWithinParagraph: true # 图片独占一行
    renderer:
      hardWraps: false # Goldmark忽略段落中的换行符。设置为 true 将换行呈现为 <br> 元素
      unsafe: false # Goldmark不会呈现原始HTML和潜在的危险链接。如果有很多内联 HTML 或 JavaScript,开启
      xhtml: false # 

注:更多细节参考 Goldmark

Highlight

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
markup:
  highlight:
    anchorLineNos: false
    codeFences: true
    guessSyntax: false
    hl_Lines: ""
    hl_inline: false
    lineAnchors: ""
    lineNoStart: 1
    lineNos: false
    lineNumbersInTable: true
    noClasses: true
    noHl: false
    style: monokai
    tabWidth: 4

注:参考 高亮语法

Table Of Content

1
2
3
4
5
markup:
  tableOfContents:
    endLevel: 3
    ordered: false # 是否生成有序列表而不是无序列表
    startLevel: 2

注:只在 Goldmark 渲染器生效

Markdown Render Hooks

查看 Markdown Render Hooks

合并主题配置

_merge 支持三个值:

  1. none:不合并
  2. shallow:浅合并,添加主题中配置,但在项目未配置的 keys
  3. deep:深合并,主题中的配置添加并覆盖项目中的配置
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
build:
  _merge: none
caches:
  _merge: none
cascade:
  _merge: none
frontmatter:
  _merge: none
imaging:
  _merge: none
languages:
  _merge: none
  en:
    _merge: none
    menus:
      _merge: shallow
    params:
      _merge: deep
markup:
  _merge: none
mediatypes:
  _merge: shallow
menus:
  _merge: shallow
minify:
  _merge: none
module:
  _merge: none
outputformats:
  _merge: shallow
params:
  _merge: deep
permalinks:
  _merge: none
privacy:
  _merge: none
related:
  _merge: none
security:
  _merge: none
sitemap:
  _merge: none
taxonomies:
  _merge: none

注:不需要像上面这样这么冗长的配置,默认继承更高级别的 _merge

配置字典

这些配置也可在使用 hugo hugo server 命令时,通过 --configName 指定覆盖

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
# 有值的都是默认值
baseURL: "http://localhost:1313/" # 服务器地址,必须协议开头,/ 结尾
title: "" # 站点标题
dataDir: "data"
assetDir: "assets"
themesDir: "themes"
publishDir: "public"
contentDir: "content"
archetypeDir: "archetypes"
defaultContentLanguage: "en"
paginatePath: "page" # 分页路由
refLinksErrorLevel: "ERROR" # 程序错误等级,任何错误都会退出。另一个值为 WARNING
refLinksNotFoundURL: "" # 未找到页面的路由重定向
titleCaseStyle: "AP" # 每个标题单词大写开头
timeout: "30s" # 超时停止页面生成,避免递归死循环
paginate: 10 # 分页数量
summaryLength: 70 # 页面宽度
rssLimit: -1

pluralizeListTitles: true # 如果没有_index.md,页面列表标题为所在目录的目录名:首字母大写 + 复数
watch: false # 不监听文件变化,开发模式默认 true,生成模式默认 false
noChmod: false # 不同步页面权限
noTimes: false # 不同步页面修改时间
relativeURLs: false # 使所有 URL 相对于内容根目录
uglyURLs: false # 丑化生成的文件路径,/filename.html => /filename/.index.html
hasCJKLanguage: false # 是否是中日韩语言
buildDrafts: false
buildExpired: false
buildFuture: false
cleanDestinationDir: false # 生成时,删除静态目录中没有源文件的文件
removePathAccents: false # 删除生成路由中的空格
canonifyURLs: false # 将相对 URL 转换为绝对 URL
enableEmoji: false # 允许监视文件系统的更改并根据需要重新创建
enableGitInfo: false # 允许为每个页面启用 .GitInfo 对象(如果Hugo站点由Git版本控制)
enableRobotsTXT: false # 允许生成 robots.txt 文件
enableInlineShortcodes: false # 允许行内短代码
enableMissingTranslationPlaceholders: false # 如果缺少翻译,允许显示占位符而不是默认值或空字符串
disableAliases: false # 将禁用别名重定向的生成
disableLiveReload: false # 禁止开发热更新
disablePathToLower: false # url 转小写
disableHugoGeneratorInject: false # 禁止在主页的 HTML 头中注入生成器元标记
defaultContentLanguageInSubdir: false # 在 subdir 中渲染默认内容语言,如 content/en/,/ => /en/
disableKinds: [] # 禁用指定种类的文件生成。此列表中允许的值: "page", "home", "section", "taxonomy", "term", "RSS", "sitemap", "robotsTXT", "404"
disableLanguages: [] # 禁用的语言
copyright: "" # 网站的版权声明,通常显示在页脚中
googleAnalytics: "" # Google Analytics tracking ID
languageCode: "" # 语言代码
newContentEditor: "" # 创建新内容时要使用的编辑器
timeZone: # 设置时区,一般不用设置,取决于系统时间
theme: # 使用的主题列表
languages: # 国际化配置
cascade: # 默认传递的 map
imaging: # 配置图片处理规则
markup: # 标记语言配置
menus: # 菜单配置
minify: # 打包配置
module: # 模块配置
mediaTypes: # 设置页面需要使用的媒体类型
outputFormats: # 媒体数据输出格式配置
params: # 全局配置参数
permalinks: # 动态路由配置
frontmatter: # 默认的头部注释配置
sectionPagesMenu: # 设置为 "main" 时,配置所有页面在都在侧栏展示
outputs: # 页面输出格式
  page:
  home:
  section:
  taxonomy:
  term:
  
taxonomies: # 在整个站点中使用自定义分类之前,必须在站点配置中定义默认分类以外的分类
  category: categories
  series: series
  tag: tags
  
sitemap: # 在站点配置中设置更改频率和优先级的默认值,以及生成的文件的名称
  changefreq: monthly # 页面可能更改的频率。有效值:always/hourly/daily/weekly/monthly/yearly/never
  filename: sitemap.xml
  priority: -1 # 页面相对于站点上任何其他页面的优先级。有效值的范围从 0.0 到 1.0,默认为 -1,忽略
  
related: # 查询关联性配置,如果未配置,默认为下面的属性
  includeNewer: false # 在相关内容列表中包含比当前页面更新的页面
  indices: # 查询权重优先级
  - name: keywords
    weight: 100
  - name: date
    weight: 10
  threshold: 80 # 0-100之间的值。较低的值将提供更多的匹配,但可能不那么相关
  toLower: false # 查询都转换为小写匹配,轻微的性能损失以获得更准确的结果
  
security: # 内置安全策略,一般不需要更改
  enableInlineShortcodes: false
  exec:
    allow:
    - ^dart-sass-embedded$
    - ^go$
    - ^npx$
    - ^postcss$
    osEnv:
    - (?i)^((HTTPS?|NO)_PROXY|PATH(EXT)?|APPDATA|TE?MP|TERM|GO\w+)$
  funcs:
    getenv:
    - ^HUGO_
    - ^CI$
  http:
    methods:
    - (?i)GET|POST
    urls:
    - .*
    
caches: # 配置各种资源缓存的目录及存在时间
  assets:
    dir: :resourceDir/_gen # 存储此缓存文件的绝对路径。允许的起始占位符是 :cacheDir 和 :resourceDir
    maxAge: -1 # 持续时间,-1表示永远,0有效地关闭该缓存,其他时间如:"10s"、"10m"、"10h" 等
  getcsv:
    dir: :cacheDir/:project
    maxAge: -1
  getjson:
    dir: :cacheDir/:project
    maxAge: -1
  getresource:
    dir: :cacheDir/:project
    maxAge: -1
  images:
    dir: :resourceDir/_gen
    maxAge: -1
  modules:
    dir: :cacheDir/modules
    maxAge: -1
    
build:
  # 关闭将j sconfig.json 写入 /assets文 件夹并映射运行 js.Build 的导入,用于 VS Code 的 JS 智能导航
  noJSConfigInAssets: false
  # 何时将 /resources/_gen 中的缓存资源用于 PostCSS 和 ToCSS,支持 fallback、always、never
  useResourceCacheWhen: "fallback" 
  # 启用后,名为hugo_stats.json的文件将被写入项目根目录
  # 其中包含一些关于构建的聚合数据,例如发布的 HTML 实体列表,用于进行 CSS 修剪
  writeStats: false

模块

hugo modules 是 Hugo 的核心构建模块:

  • 初始化的站点项目就是一个模块
  • static content layouts data assets i18n archetypes 都是一个小模块
  • 可以任意组合模块,形成大的虚拟联合文件系统

注:当使用 hugo mod生成你的站点(启用 go modules 模式),需要用到这些配置,如无需要可跳过

配置模块

顶级模块配置

1
2
3
4
5
6
7
8
module:
  noProxy: none
  proxy: direct # 定义用于下载远程模块的代理服务器
  noVendor: "" # 一个可选的 Glob 模式匹配模块路径
  vendorClosest: false # 选择就近的 vendored 模块
  private: '*.*' # 专用的路径: 逗号分隔的glob列表匹配
  replacements: "" # 需替换的,模块路径到目录映射/切片列表(逗号分隔),例如: github.com/bep/my-theme->./
  workspace: "off" # 要使用的工作区文件。这将启用 Go 工作区模式

hugoVersion

1
2
3
4
5
module:
  hugoVersion:
    extended: false # 是否需要 Hugo 的扩展版本
    max: "" # 最高版本
    min: "" # 最低版本

imports

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
module:
  imports:
  - disable: false # 如果启用,在 go.* 文件中保留任何版本信息时禁用模块
    ignoreConfig: false # 如果启用,则不会加载任何模块配置文件,例如config.toml
    ignoreImports: false # 如果启用,将不遵循模块导入
    noMounts: true # 不要在此导入中装入任何文件夹
    noVendor: true # 从不提供此导入(仅在主项目中允许)
    # 可以是有效的Go Module模块路径,也可以是存储在主题文件夹中的模块目录名
    path: github.com/gohugoio/hugoTestModules1_linux/modh1_2_1v
  - path: my-shortcodes

mounts

注意:

  1. 添加 mounts 时,将忽略相关目标根目录的默认 mounts
  2. 不应该同时拥有这两个配置:contentDir 、staticDir
    • 如果你添加了一个 mounts 部分,你应该删除旧的 contentDir 、staticDir 等设置
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
module:
  mounts:
  - source: content
    target: content
  - source: static
    target: static
  - source: layouts
    target: layouts
  - source: data
    target: data
  - source: assets
    target: assets
  - source: i18n
    target: i18n
  - source: archetypes
    target: archetypes

使用模块

初始化模块

1
$ hugo mod init your_project_path

添加主题

1
2
3
module:
  imports:
  - path: github.com/spf13/hyde

更新模块

1
2
3
$ hugo mod get -u # 更新所有
$ hugo mod get -u github.com/gohugoio/myShortcodes ... # 递归更新
$ hugo mod get github.com/gohugoio/myShortcodes@v1.0.7 # 更新指定版本

查看依赖图

1
$ hugo mod graph

主题组件

1
2
3
4
5
6
# 可以嵌套使用主题组件
# 项目 -> 短代码 -> 基本主题 -> hyde 主题
theme:
- my-shortcodes
- base-theme
- hyde

内容管理

内容组织

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
└── content
    └── about
    |   └── index.md  # https://example.com/about/
    ├── posts
    |   ├── firstpost.md   # https://example.com/posts/firstpost/
    |   ├── happy
    |   |   └── ness.md  # https://example.com/posts/happy/ness/
    |   └── secondpost.md  # https://example.com/posts/secondpost/
    └── quote
        ├── first.md       # https://example.com/quote/first/
        └── second.md      # https://example.com/quote/second/

注:其中的 https://example.com/ 就是 config.toml 中配置的 baseURL

路由解析

索引页:_index.md

_index.md 在 Hugo 中扮演着特殊的角色,将主题和内容添加到列表模板中

  • 这些模板包括节模板、分类模板、分类术语模板和主页模板

  • 可以使用 .Site.GetPage 函数获取内容和元数据的引用

1
2
3
4
5
6
7
                               url ("/posts/my-first-hugo-post/")
                   ⊢------------^----------⊣
       baseurl     section     slug
⊢--------^--------⊣⊢-^--⊣⊢-------^---------⊣
                 permalink
⊢--------------------^---------------------⊣
https://example.com/posts/my-first-hugo-post/index.html

baseurlconfig.toml 中配置的 baseUrl

section:项目目录位置指定,如 /posts 表示在 content/posts 目录下

slug:默认为文件名,当元数据注释中的 slug 指定,将会替换

重定义规则

指定页面路由

1
2
3
4
5
---
title: A new post with the filename old-post.md
slug: "new-post"
---
# 将会生成 https://example.com/posts/new-post/

指定模板

1
2
3
4
5
6
---
title: My Post
type: new
layout: mylayout
---
# 将会使用 layouts/new/mylayout.html 渲染该页面

指定页面 URL

1
2
3
4
5
---
title: Old URL
url: /blog/new-url/
---
# 将会生成 https://example.com/blog/new-url/

目录解析

一个页面支持两种目录解析

  • 叶子页面
  • 集合页面
叶子页面 集合页面
用法 单个页面的内容和附件集合 章节页面附件集合(主页、章节、分类术语、分类列表)
索引文件名 index.md _index.md
允许的资源 页面和非页面(如图像、PDF 等) 仅限非页面(如图像、PDF 等)
资源位置 当前目录 _index.md 所在顶级目录
布局类型 single list
嵌套 不允许嵌套其他页面 允许在其下嵌套集合和页面
content/posts/test/index.md content/posts/_index.md
非索引页面文件 仅作为页面资源访问 仅作为常规页面访问

叶子页面

目录示例

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
# 目录示例
content/
├── about # 叶子页面
│   ├── index.md
├── posts # 分类叶子页面
│   ├── post1
│   │   ├── content1.md
│   │   ├── content2.md
│   │   ├── image1.jpg
│   │   ├── image2.png
│   │   └── index.md
│   └── post2
│       └── index.md
└── another-category # 集合页面
    ├── ..
    └── collection # 集合中的集合
        ├── ..
        └── another-post
            └── index.md

无头捆绑

无头捆绑是配置为不在任何地方发布,指的是含头部注释 headless=true.md 文件

  • 不会生成 HTML 在 public 目录
  • 不作为页面部分,如 .Site.RegularPages

但是可以通过 go模板语法,获取到这部分内容,例如:

1
2
3
4
5
6
7
{{ $headless := .Site.GetPage "/some-headless-file" }}
{{ $reusablePages := $headless.Resources.Match "author*" }}
<h2>Authors</h2>
{{ range $reusablePages }}
    <h3>{{ .Title }}</h3>
    {{ .Content }}
{{ end }}

这种文件使用场景:

  1. 共享媒体库
  2. 重复使用内容

集合页面

集合页面目录至少包含一个 _index.md 文件

  • 可以将任何文件类型用作内容资源,只要它是可识别的内容类型

目录示例

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
content/
├── collection-1
│   ├── content1.md
│   ├── content2.md
│   ├── image1.jpg
│   ├── image2.png
│   └── _index.md
└── collection-2
    ├── _index.md
    └── a-post
        └── index.md

内容解析

可以将任何文件放在 /content 目录下,但是会根据文件头部注释和文件扩展名决定是否处理,例如:

  • Markdown 转 HTML
  • 执行短代码
  • 应用布局

可被解析的内容列表

扩展名 描述
Goldmark md, markdown, goldmark 可以将默认处理程序设置为其他内容,请参阅 配置标记
Emacs Org-Mode org 参阅 go-org
AsciiDoc asciidocext, adoc, ad 需安装 Asciidoctor
RST rst 需要安装 RST
Pandoc pandoc, pdc 需要安装 Pandoc
HTML html, htm 视为内容文件,具有布局,短代码等,它必须具有头部注释。如果没有,它将按原样复制

注:如要处理上述除 GoldmarkHTML 的内容,参考 官方文档

图表

Goat

支持 Goat 图表生成,但是 Typora 不支持预览解析,下面的内容通过 Hugo 将会生成 goat 图表:

1 2 3 4 1 2 3 4 1 2 3 4 1 2 3 4 1 2 3 4 1 2 3 4

Mermaid

对于 Mermaid 图表,Typora 支持预览解析,但是 Hugo 不原生支持,下面的内容会生成原始代码,而不是图表:

1
2
3
4
5
6
flowchart LR

A[Hard] -->|Text| B(Round)
B --> C{Decision}
C -->|One| D[Result 1]
C -->|Two| E[Result 2]

如果需要,可以自己添加渲染模板:layouts/_default/_markup/render-codeblock-mermaid.html

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
<div class="mermaid">
  {{- .Inner | safeHTML }}
</div>
{{ .Page.Store.Set "hasMermaid" true }}
{{ if .Page.Store.Get "hasMermaid" }}
  <script src="https://cdn.jsdelivr.net/npm/mermaid/dist/mermaid.min.js"></script>
  <script>
    mermaid.initialize({ startOnLoad: true });
    console.log("mermaid is initialized");
  </script>
{{ end }}

然后便可以渲染 mermaid 图表了

flowchart LR A[Hard] -->|Text| B(Round) B --> C{Decision} C -->|One| D[Result 1] C -->|Two| E[Result 2]

头部注释

支持格式

官方视频介绍(英文):

Hugo 通过页面前的头部注释,获取元数据信息,展示预览内容、封面、发布时间、作者等

支持 toml yamljson

  • toml:+++ 开始和结束
  • yaml:--- 开始和结束。推荐使用
  • json:一对 {} 之间
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
title: 页面标题
categories:
- 分类1
- 分类2
date: "2012-04-06"
description: 一些页面描述
slug: 重写页面末尾路由
tags:
- 标签1
- 标签2
- 标签3
- 标签4

注释字典

预定义

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
name: "string:文件名覆盖"
title: "string: 标题"
description: "string: 紧跟标题的描述,预览页面会显示"
summary: "slice: 页面摘要使用的文本"
linkTitle: "string: 创建指向内容的链接"
aliases: "slice: 在输出目录结构中创建的一个或多个别名(重命名旧路径)"
audio: "slice: 音频文件路径"
cascade: "map: 传递指定头部注释"
date: "string: 分配给此页面的日期时间"
lastmod: "string: 上次修改内容的日期时间"
publishDate: "string: 如果在将来,除非配置 --buildFuturehugo,否则不显示"
expiryDate: "string: 如果在过去,除非配置 --buildExpired,否则不显示"
draft: "bool: 是否是草稿,如果是,除非配置 --buildDrafts,否则不显示"
headless: "bool: 不展示的页面,可用与公共资源和页面公共内容"
images: "slice: 与页面相关的图像的路径数组"
isCJKLanguage: "bool: 明确将内容视为中日韩语言"
keywords: "slice: 元数据关键字,可用于 SEO"
layout: "选择布局模板。如果指定,将在布局目录中查找与内容部分对应的同名布局"
markup: "rst/md: 实验功能,指定文档类型,默认是 md"
outputs: "slice: 指定特定于内容的输出格式"
resources: "string: 用于配置页面捆绑包资源"
series: "slice: 此页面所属的系列数组,作为分类的子集"
slug: "string: 输出 URL 的尾部,未指定时为文件名,如果是 index 则为所在目录名"
type: "string: 内容类型;如果未在前言中指定,值为所在目录名"
url: "string: 从 Web 根目录到内容的完整路径"
videos: "slice: 与页面相关的视频的一系列路径"
weight: "int: 排序,值越大优先级越高"
tags: "slice: 页面标签"
categories: "slice: 页面分类"

注:为方便描述,以上字段的支持的值和类型都在字符串描述中。slice 表示字符串数组

用户定义

可以任意添加字段,这些用户定义的键值被放置在一个 .Params 变量中,以便在模板中使用

可以分别通过 .Params.include_toc.Params.show_comments 访问以下字段:

1
2
include_toc: true
show_comments: false

头部注释传递

通过自定义变量 cascade 可以向下传递给后面的页面

可以通过 _target 控制传递的内容和目标

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
cascade:
- _target:
    kind: "page" # 匹配类型为 page
    lang: "en" # 匹配语言为 en
    path: "/blog/**" # /blog/ 目录下的所有
    environment: "development" # 匹配开发模式
  background: "yosemite.jpg"
- _target:
    kind: "section"
  background: "goldenbridge.jpg"
title: "Blog"

说明:

  • kind lang path environment 以外的其他配置将被忽略

  • 在上面的例子中 section 类型的 layouts 调用 .Params.background 时,都返回 "goldenbridge.jpg",除非:

    1. 页面有自己的 background 注释
    2. 有距离更近的祖先节点传递了 background

打包选项

定义 Hugo 在构建网站时,必须如何处理给定的页面

它们存储在名为 _build 的保留头部注释对象中,默认值如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
_build:
  # always: 包含着所有集合页面中,site.RegularPages,$page.Pages 可获取
  # never: 不在任何一个集合页面中
  # local: 包含在本地集合中,$page.RegularPage/$page.Page/.Page/.RegularPages,无标题可导航页面
  list: always
  # 发布页面捆绑的相关资源
  publishResources: true
  # always: 视为已发布的页面,包含其输出文件(index.html等)和 permalink
  # never: 不在任何一个集合页面中
  # link: 不会生成在磁盘中,但是会生成一个真实的永久链接
  render: always 

注:任何页面,无论其生成选项如何,.GetPage 方法始终可用

说明用例

引用页面

项目需要一个 Who we are 内容文件,用于头部注释和正文,以供主页使用,但无需其他地方

1
2
3
4
5
# content/who-we-are.md`
title: Who we are
_build:
 list: false
 render: false
1
2
3
4
5
6
{{/* layouts/index.html */}}
<section id="who-we-are">
{{ with site.GetPage "who-we-are" }}
  {{ .Content }}
{{ end }}
</section>

推荐列表

网站需要展示推荐内容,而不发布任何内容文件

为了避免在每个推荐上设置构建选项,可以在推荐部分的内容文件上使用级联传递(cascade

1
2
3
4
5
6
7
_build:
  render: true
cascade:
  _build:
    list: true
    render: false
title: Testimonials
1
2
3
4
5
6
7
8
{{/* layouts/_defaults/testimonials.html */}}
<section id="testimonials">
{{ range first 5 .Pages }}
  <blockquote cite="{{ .Params.cite }}">
    {{ .Content }}
  </blockquote>
{{ end }}
</section>

页面资源

页面资源:图像、其他页面、文档等,具有页面相关 URL 和自己的元数据

页面资源只能从页面捆绑包访问

虽然这些目录的根目录中有 index.md_index.md 文件,但是页面资源仅可用于与其绑定的页面

例如:第一个页面是一个页面包,可以访问10个页面资源,包括音频、数据、文档、图像和视频。尽管第二个页面也是一个页面捆绑包,但它没有页面资源,无法直接访问与第一个文章相关的页面资源

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
content
└── post
    ├── first-post
    │   ├── images
    │   │   ├── a.jpg
    │   │   ├── b.jpg
    │   │   └── c.jpg
    │   ├── index.md (root of page bundle)
    │   ├── latest.html
    │   ├── manual.json
    │   ├── notice.md
    │   ├── office.mp3
    │   ├── pocket.mp4
    │   ├── rating.pdf
    │   └── safety.txt
    └── second-post
        └── index.md (root of page bundle)

属性

属性名 描述
ResourceType 资源的媒体类型的主要类型。例如,MIME 类型 image/jpeg 的文件具有 ResourceType 图像
Name 默认值为文件名(相对于所属页面)。可以设置在头部注释
Title 默认值为文件名(相对于所属页面)。可以设置在头部注释
Permalink 绝对 URL 地址
RelPermalink 相对 URL 地址
Content 页面的内容
MediaType MIME 类型,例如:image/jpeg
MediaType.MainType MIME 主类型,例如:application/pdf application
MediaType.SubType MIME 子类型,例如 PPT 的子类型为:application/pdf pdf vnd.mspowerpoint
MediaType.Suffixes MIME 类型后缀切片
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
{{ with .Resources.GetMatch "script.js" }}
  <script>{{ .Content | safeJS }}</script>
{{ end }}

{{ with .Resources.GetMatch "style.css" }}
  <style>{{ .Content | safeCSS }}</style>
{{ end }}

{{ with .Resources.GetMatch "img.png" }}
  <img src="data:{{ .MediaType }};base64,{{ .Content | base64Encode }}">
{{ end }}

方法

ByType

返回给定类型的页面资源

1
{{ .Resources.ByType "image" }}

Match

返回所有匹配类型的页面资源

1
{{ .Resources.Match "images/*" }}

GetMatch

返回第一个匹配到的类型的页面资源

1
{{ .Resources.GetMatch "images/*" }}

匹配规则

1
2
3
4
5
6
7
8
// Using Match/GetMatch to find this images/sunset.jpg ?
.Resources.Match "images/sun*" 
.Resources.Match "**/sunset.jpg" 
.Resources.Match "images/*.jpg" 
.Resources.Match "**.jpg" 
.Resources.Match "*" 🚫
.Resources.Match "sunset.jpg" 🚫
.Resources.Match "*sunset.jpg" 🚫

资源元数据

记录页面使用的绑定资源的信息

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
resources:
- name: header # .Resources.Name/.Resources.GetMatch "header" 可以获取到
  src: images/sunset.jpg
- params:
    icon: photo
  src: documents/photo_specs.pdf
  title: Photo Specifications
- src: documents/guide.pdf
  title: Instruction Guide
- src: documents/checklist.pdf
  title: Document Checklist
- src: documents/payment.docx
  title: Proof of Payment
- name: pdf-file-:counter # pdf-file-1 pdf-file-2 pdf-file-3
  params:
    icon: pdf
  src: '**.pdf'
- params: # 所有 .docx 文档包含 .icon=word
    icon: word
  src: '**.docx'

:counter

假设有下面的资源定义

1
2
3
4
5
resources:
- src: '*specs.pdf'
  title: 'Specification #:counter'
- name: pdf-file-:counter
  src: '**.pdf'

解析后

文件名 解析 name 解析 title
checklist.pdf` pdf-file-1.pdf checklist.pdf
guide.pdf pdf-file-2.pdf guide.pdf
other_specs.pdf pdf-file-3.pdf Specification #1
photo_specs.pdf pdf-file-4.pdf Specification #2

图像处理

支持缩放、修剪、旋转、滤镜和格式转换

图像资源

要处理图像,必须将图像作为页面资源或全局资源访问

页面资源

页面资源是页面捆绑包中的文件。页面捆绑包是根目录中包含 index.md_index.md 文件的目录

1
2
3
4
5
content/
└── posts/
    └── post-1/           <-- page bundle
        ├── index.md
        └── sunset.jpg    <-- page resource

全局资源

满足以下条件之一的目录,目录内文件作为全局资源:

  • assets 目录,或者配置 mounts.assets 的目录,或者远程资源

通过以下方式访问全局资源

1
2
{{ $image := resources.Get "images/sunset.jpg" }}
{{ $image := resources.GetRemote "https://gohugo.io/img/hugo-logo.png" }}

图像渲染

一旦将图像作为页面资源或全局资源进行了访问

使用 PermalinkRelPermlinkWidthHeight 在模板中进行渲染

例1:如果资源未找到,会抛出错误

1
2
{{ $image := .Resources.GetMatch "sunset.jpg" }}
<img src="{{ $image.RelPermalink }}" width="{{ $image.Width }}" height="{{ $image.Height }}">

例2:如果资源未找到,跳过渲染(方式1)

1
2
3
4
{{ $image := .Resources.GetMatch "sunset.jpg" }}
{{ with $image }}
  <img src="{{ .RelPermalink }}" width="{{ .Width }}" height="{{ .Height }}">
{{ end }}

例3:如果资源未找到,跳过渲染(方式2)

1
2
3
{{ with .Resources.GetMatch "sunset.jpg" }}
  <img src="{{ .RelPermalink }}" width="{{ .Width }}" height="{{ .Height }}">
{{ end }}

图像处理方法

元数据(Exif、IPTC、XMP等)在图像转换期间不保留

对原始图像使用 Exif 方法从 JPEG 或 TIFF 图像中提取 Exif 元数据

Resize

将图像调整为指定的宽度和/或高度

如果同时指定宽度和高度,则生成的图像将不成比例地缩放,除非原始图像具有相同的纵横比

1
2
3
4
5
6
7
8
{{/* Resize to a width of 600px and preserve aspect ratio */}}
{{ $image := $image.Resize "600x" }}

{{/* Resize to a height of 400px and preserve aspect ratio */}}
{{ $image := $image.Resize "x400" }}

{{/* Resize to a width of 600px and a height of 400px */}}
{{ $image := $image.Resize "600x400" }}

Fit

缩小图像以适应给定的尺寸,同时保持纵横比

1
2
{{/* 必须同时提供宽度和高度 */}}
{{ $image := $image.Fit "600x400" }}

Fill

裁剪并调整图像大小以匹配给定尺寸

1
2
{{/* 必须同时提供宽度和高度,使用定位选项更改裁剪框定位点 */}}
{{ $image := $image.Fill "600x400" }}

Crop

裁剪图像以匹配给定尺寸,而不调整大小

1
2
{{/* 必须同时提供宽度和高度,使用定位选项更改裁剪框定位点 */}}
{{ $image := $image.Crop "600x400" }}

Filter

应用一个或多个滤镜给指定图像

1
{{ $image := $image.Filter (images.GaussianBlur 6) (images.Pixelate 8) }}

使用 pipes ,Hugo 将按顺序应用

1
{{ $image := $image | images.Filter (images.GaussianBlur 6) (images.Pixelate 8) }}

创建滤镜链,然后重用

1
2
3
{{ $filters := slice  (images.GaussianBlur 6) (images.Pixelate 8) }}
{{ $image1 := $image1.Filter $filters }}
{{ $image2 := $image2.Filter $filters }}

Colors

返回图像主要颜色切片,格式为 hex,图像越小性能越高

1
{{ $colors := $image.Colors }}

Exif

返回包含图像元数据的 Exif 对象

为了防止在处理没有 Exif 数据的图像时出错,请使用 with 语句包装访问

1
2
3
4
5
6
7
8
{{ with $image.Exif }}
  Date: {{ .Date }}
  LatLong: {{ .Lat }}, {{ .Long }}
  Tags:
  {{ range $k, $v := .Tags }}
    TAG: {{ $k }}: {{ $v }}
  {{ end }}
{{ end }}

或者单独访问 Exif 字段,使用 lang.FormatNumber 函数根据需要设置字段的格式

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
{{ with $image.Exif }}
  <ul>
    {{ with .Date }}<li>Date: {{ .Format "January 02, 2006" }}</li>{{ end }}
    {{ with .Tags.ApertureValue }}<li>Aperture: {{ lang.FormatNumber 2 . }}</li>{{ end }}
    {{ with .Tags.BrightnessValue }}<li>Brightness: {{ lang.FormatNumber 2 . }}</li>{{ end }}
    {{ with .Tags.ExposureTime }}<li>Exposure Time: {{ . }}</li>{{ end }}
    {{ with .Tags.FNumber }}<li>F Number: {{ . }}</li>{{ end }}
    {{ with .Tags.FocalLength }}<li>Focal Length: {{ . }}</li>{{ end }}
    {{ with .Tags.ISOSpeedRatings }}<li>ISO Speed Ratings: {{ . }}</li>{{ end }}
    {{ with .Tags.LensModel }}<li>Lens Model: {{ . }}</li>{{ end }}
  </ul>
{{ end }}

Exif 属性

属性 描述
.Date 图像创建时间/日期,格式化通过 time.Format 函数
.Lat GPS 纬度
.Long GPS 经度
.Tags 图像标签集合,可以通过 图像配置 指定包含项或排除项

图像选项

尺寸

所有尺寸都以 px 为单位

旋转

旋转按给定的角度逆时针旋转,假设一个 600x400 的图像需要逆时针旋转 90°,缩小 50%

1
{{ $image = $image.Resize "300x r90" }}

如果不需要缩放,使用 printf 获取原宽高:

1
2
3
4
5
{{ with .Resources.GetMatch "sunset.jpg" }}
  {{ with .Resize (printf "%dx%d r90" .Height .Width) }}
    <img src="{{ .RelPermalink }}" width="{{ .Width }}" height="{{ .Height }}">
  {{ end }}
{{ end }}

锚点

使用 Crop Fill 对图像进行裁剪时,需要指定裁剪的位置,例如将 400x200 从左上角裁剪 200x100大小:

1
2
3
{{/* 可选值: TopLeft, Top, TopRight, Left, Center, Right */}}
{{/* 可选值: BottomLeft, Bottom, BottomRight, Smart */}}
{{ $image.Crop "200x100 TopLeft" }}

注:当裁剪时应用了旋转,锚点为旋转后的方向

格式

对图像格式进行转换,例如: .jpg => .webp

1
2
3
4
5
6
7
8
{{/* 可选值: bmp, gif, jpeg, jpg, png, tif, tiff, webp */}}
{{ $image.Resize "600x webp" }}
// 如果不需要缩放
{{ with .Resources.GetMatch "sunset.jpg" }}
  {{ with .Resize (printf "%dx%d webp" .Width .Height) }}
    <img src="{{ .RelPermalink }}" width="{{ .Width }}" height="{{ .Height }}">
  {{ end }}
{{ end }}

质量

适用于 JPEGWebP 图像,q 值越高,质量越好,取值范围 1-100。默认值 75,可以通过 图像配置

1
{{ $image.Resize "600x webp q50" }}

暗示

适用于和 WebP 图像,暗示图像内容,默认值 photo,可以通过 图像配置

描述
drawing 具有高对比度细节的手绘或线条图
icon 彩色小图像
photo 带自然光的室外照片
picture 室内照片,如肖像
text 主要是文本的图像
1
{{ $image.Resize "600x webp picture" }}

背景色

将图像从支持透明度的格式(如PNG)转换为不支持透明度的文件格式(如JPEG)时,可以指定生成图像的背景色

支持 #00f#0000ff,可以通过 图像配置

1
{{ $image.Resize "600x jpg #b31280" }}

重采样滤镜

可以指定调整图像大小时使用的重采样滤镜,默认值是 Box,可以通过 图像配置 。常用的重采样滤镜包括:

滤镜 描述
Box 适用于降尺度的简单快速平均滤镜
Lanczos 用于照片图像的高质量重采样滤镜,产生清晰的结果
CatmullRom 锐立方滤镜,比 Lanczos 滤波器更快,同时提供类似的结果
MitchellNetravali CatmullRom 相比,立方滤镜可产生更平滑的结果,且振铃伪影更少
Linear 双线性重采样滤波器,产生平滑输出,比 MitchellNetravali 更快
NearestNeighbor 最快的重采样滤镜,无抗锯齿
1
{{ $image.Resize "600x400 Lanczos" }}

图像处理案例

处理模板:layouts/shortcodes/imgproc.html

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
{{ $img := .Page.Resources.GetMatch (printf "*%s*" (.Get 0)) }}
{{ $command := .Get 1 }}
{{ $options := .Get 2 }}
{{ if eq $command "Fit"}}
  {{ $img = $img.Fit $options }}
{{ else if eq $command "Resize"}}
  {{ $img = $img.Resize $options }}
{{ else if eq $command "Fill"}}
  {{ $img = $img.Fill $options }}
{{ else if eq $command "Crop"}}
  {{ $img = $img.Crop $options }}
{{ else }}
  {{ errorf "无效的图片处理命令,必须是以下之一: Crop, Fit, Fill 或 Resize"}}
{{ end }}
<figure style="padding: 0.25rem; margin: 2rem 0; background-color: #cccc">
  <img style="max-width: 100%; width: auto; height: auto;" src="{{ $img.RelPermalink }}" width="{{ $img.Width }}" height="{{ $img.Height }}">
  <figcaption>
  <small>
    {{ with .Inner }}
      {{ . }}
    {{ else }}
      .{{ $command }} "{{ $options }}"
    {{ end }}
  </small>
  </figcaption>
</figure>

然后在 .md 文件写下面的代码

{{ < imgproc banana Resize “300x” /> }}

说明:

  • 该文档使用 Hugo 搭建,上面代码括号加粗,避免被 Hugo 解析而报错
  • 上面代码使用了自闭合(类似 HTML),如果还有其他短代码,可以嵌套使用

图像配置

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
imaging:
  anchor: Smart
  bgColor: '#ffffff'
  hint: photo
  quality: 75
  resampleFilter: Box
  exif:
    disableDate: false
    disableLatLong: false
    excludeFields: ""
    includeFields: ""

提示:

  1. Hugo将处理过的图像缓存在resources目录中。如果在源代码管理中包含此目录,Hugo将不必在 CI/CD 工作流中重新生成图像(例如,GitHub Pages、GitLab Pages、Netlify等),这将加快构建速度
  2. 如果更改图像处理方法或选项,或者重命名或删除图像,资源目录将包含未使用的图像。要删除未使用的图像,请使用以下命令执行垃圾收集:hugo --gc

短代码

官方视频介绍(英文):

短代码是内容文件中调用内置或自定义模板的简单片段,.html 文件 {{}} 包裹的代码

什么是短代码

Markdown 的内容格式很简单,但有些场景下,需要在 md 中写 HTML 语法

Hugo 创建了短代码来规避在 Markdown 中嵌入 HTML,让 Markdown 保持简洁

短代码是 Markdown 内容文件中的一个简单片段,Hugo将使用预定义的模板呈现该片段

短代码可以随时更新,在站点生成时,很容易合并到更改

提示:

  • 实际上,短代码搭配模板文件使用
  • 如果有文档离开 Hugo 也可阅读的需求,不建议在 Markdown 中使用短代码
  • 如果使用 Hugo 生成网站,即使不在 Markdown 中使用短代码,也需要学习 go 模板文件语法

短代码使用

本文所在博客不准备使用短代码,使用请移步 官网:短代码的使用

内容相关性

Hugo 根据 头部注释参数 来识别页面的相关内容

可以将其调整为所需的索引和参数集,或者使用默认的 内容相关性配置

相关内容列表

假如要列出 5 个相关页面(共享相同的日期或关键字参数)

只需在单个页面模板中包含类似于此部分的内容即可 layouts/partials/related.html

1
2
3
4
5
6
7
8
9
{{ $related := .Site.RegularPages.Related . | first 5 }}
{{ with $related }}
<h3>相关内容</h3>
<ul>
 {{ range . }}
 <li><a href="{{ .RelPermalink }}">{{ .Title }}</a></li>
 {{ end }}
</ul>
{{ end }}

.Related 方法接受一个参数,该参数可以是 .Pageoptions 映射。有以下选项:

选项 描述
indices 用于搜索的指数
document 用于搜索相关内容的文档
namedSlice 用于搜索的关键字
fragments 包含一个特殊关键字列表,用于配置为 Fragments 类型的索引

示例:

1
2
3
4
5
6
7
{{ $page := . }}
{{ $opts := 
  "indices" (slice "tags" "keywords")
  "document" $page
  "namedSlices" (slice (keyVals "tags" "hugo" "rocks") (keyVals "date" $page.Date))
  "fragments" (slice "heading-1" "heading-2")
}}

提示:在 Hugo 0.111.0中改进并简化了这个特性

  • 之前:在此之前,我们有三种不同的方法:RelatedRelatedToRelatedIndices
  • 之后:只有 Related

内容相关性配置

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
related:
  includeNewer: true
  threshold: 60
  toLower: false
  indices:
    - name: tags # 索引名称。此值直接映射到页面参数
      type: fragments # basic(默认) 或 fragments
      applyFilter: true # 查看后面
      cardinalityThreshold: 0 # 0-100,将其设置为 50 将删除索引中 50% 以上文档中使用的所有关键字
      weight: 100
      pattern: "200601" # 目前仅与日期相关,可能希望列出时间上接近的内容
      toLower: false

    - name: categories
      type: fragments
      applyFilter: true
      weight: 200

提示:上面的 type applyFilter cardinalityThresholdHugo v0.111.0 以上支持

如果 applyFilter=true,则结果中每一页上的 .HeadingsFiltered 将反映过滤的标题。如果想显示相关内容列表中的标题,这很有用:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
{{ $related := .Site.RegularPages.Related . | first 5 }}
{{ with $related }}
  <h2>相关内容</h2>
  <ul>
    {{ range $i, $p := . }}
      <li>
        <a href="{{ .RelPermalink }}">{{ .Title }}</a>
        {{ with .HeadingsFiltered }}
          <ul>
            {{ range . }}
              {{ $link := printf "%s#%s" $p.RelPermalink .ID | safeURL }}
              <li>
                <a href="{{ $link }}">{{ .Title }}</a>
              </li>
            {{ end }}
          </ul>
        {{ end }}
      </li>
    {{ end }}
  </ul>
{{ end }}

页面分区

Hugo 生成一个与内容相匹配的节点树

content/ 目录下的一级目录形成一个顶级分区,如果是 叶子页面 ,不会形成分区

如果分区内存在目录 Foo 嵌套很深,需要在 _index.md 文件创建 Foo 的目录

嵌套分区

1
2
3
4
5
6
7
8
content
└── blog        # <-- 是分区,因为 first-level 目录在 content/ 下
    ├── funny-cats
    │   ├── mypost.md
    │   └── kittens         # <-- 是分区,因为含有 _index.md
    │       └── _index.md
    └── tech                # <-- 是分区,因为含有 _index.md
        └── _index.md

示例:面包屑导航

使用可用的部分变量和方法,您可以构建强大的导航 layouts/partials/breadcrumb.html

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
<ol class="nav navbar-nav">
<ul>
{{- range .Ancestors.Reverse }}
<li><a href="{{ .Permalink }}">{{ .Title }}</a></li>
{{- end }}
<li class="active" aria-current="page">
<a href="{{ .Permalink }}">{{ .Title }}</a>
</li>
</ul>
</ol>

页面属性和方法

查看 分区页面的属性和方法

页面列表

Hugo将自动为每个根节创建一个页面,列出该节中的所有内容

有关自定义这些页面呈现方式的详细信息,移步 列表模板

页面分区模板

默认情况下,页面分区中创建的所有内容都将使用与根节名称匹配的内容类型

例如,Hugo 将假设 posts/post-1.md 具有 posts 内容类型。如果页面使用 archetype,Hugo 将根据archetype/posts.md 中的内容生成主题

页面原型

页面原型是创建新内容时使用的模板,其中包含预配置的主题,也可能是网站内容类型的内容处置。将在运行hugo new 时使用

当运行 hugo new 命令时,会找到项目中最合适的原型模板。如果项目不包含任何原型文件,将在主题中查找,例如:

1
$ hugo new posts/my-first-post.md

将会在下面的目录寻找原型

  1. archetypes/posts.md
  2. archetypes/default.md
  3. themes/my-theme/archetypes/posts.md
  4. themes/my-theme/archetypes/default.md

创建原型模板

archetypes/newsletter.md

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
---
title: "{{ replace .Name "-" " " | title }}"
date: {{ .Date }}
draft: true
---

**Insert Lead paragraph here.**

## New Cool Posts

{{ range first 10 ( where .Site.RegularPages "Type" "cool" ) }}
* {{ .Title }}
{{ end }}

当你创建一个新的页面

1
$ hugo new newsletter/the-latest-cool.stuff.md

将会基于原型模板创建一个新的内容文件

注意:

  • 只有当 .Site 在原型文件中使用时,才会构建网站,这对于大型网站来说可能会很耗时

  • 完整的 .Site 和所有的 Hugo 模板函数都可以在任何的 archetype 文件使用

基于目录的原型

Hugo 0.49 开始,可以使用完整的目录作为原型模板。给定此原型目录:

1
2
3
4
5
6
7
archetypes
├── default.md
└── post-bundle
    ├── bio.md
    ├── images
    │   └── featured.jpg
    └── index.md
1
$ hugo new --kind post-bundle posts/my-post

将在 /content/posts/my-post 中创建一个新文件夹,其中包含与 post-bundle 原型文件夹中相同的文件集

所有内容文件(index.md 等)都可以包含模板逻辑,并将接收正确的 .Site

页面分类

Hugo 支持页面内容之间逻辑关系的分类,通过用户自定义

示例:电影网

假设正在制作一个关于电影的网站。可能希望包括以下分类:

  • Actors
  • Directors
  • Studios
  • Genre
  • Year
  • Awards

然后,在每一部电影中,为这些分类法中的每一个指定术语(即,在每部电影内容文件的标题中)

根据这些术语,Hugo 将自动为每个 ActorsDirectorsStudiosGenreYearAwards 创建页面,每个页面列出与特定 ActorsDirectorsStudiosGenreYearAwards 相匹配的所有电影

分类配置

默认分类,在整个站点中使用自定义分类之前,必须在站点配置中定义默认分类以外的分类:

1
2
3
4
taxonomies: 
  category: categories
  tag: tags
  series: series 

注:当站点配置 taxonomies 后,上面的默认配置将会失效,使用用户指定的配置

如果不希望 Hugo 创建任何分类

1
2
3
4
5
6
disableKinds:
- taxonomy
- term
- home
- page
- section

在页面头部注释中使用

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
categories:
- Development
categories_weight: 44
project_url: https://github.com/gohugoio/hugo
series:
- Go Web Dev
slug: hugo
tags:
- Development
- Go
- fast
- Blogging
tags_weight: 22
title: 'Hugo: A fast and flexible static site generator'

注意:以上示例中 categories_weight tags_weight 越大,优先级越高,在同类页面中排序越靠前

页面摘要

Hugo 生成内容摘要,作为摘要视图中的简短版本,.Summary 获取

自动拆分摘要

默认情况下,Hugo 会自动将内容的前 70 个单词作为摘要,并将其存储到 .Summary 页面变量中,以便在模板中使用

  • 可以通过在站点配置中设置 summaryLength 来自定义摘要长度
  • 可以通过使用 plainifysafeHTML 函数,自定义 HTML 标记中的摘要如何加载
  • 在中日韩语言的页面,需设置 hasCJKLanguage=true 以准确地计算摘要字数

手动拆分摘要

通过添加 <--more--> 分隔符,划分需要添加到摘要的内容

  • 摘要分隔符之前的内容将用作该内容的摘要,并存储在 .Summary 页面变量中,所有 HTML 格式保持不变
  • 对于 Org 模式的内容 ,使用 #more
  • 摘要分割并非 Hugo 独有,在其他文献中,也称为 更多标签摘录分隔符

头部注释摘要

在页面头部注释中使用 summary 配置页面摘要

说明:摘要优先级为 手动拆分摘要 > 头部注释摘要 > 自动拆分摘要

链接引用

通过在短代码中使用 refrelref 创建绝对和相对链接,本文档所在站点不使用,了解移步 官方:链接引用

URL 管理

Hugo 支持永久链接、别名、链接规范化,以及处理相对 URL 和绝对 URL 的多种选项

即绝对 URL

构建网站的默认Hugo目标目录是 public/,可以通过在站点配置中指定不同的 publishDir 来更改此值

站点配置中的 permalink 选项允许您根据每个节调整目录路径(即URL)

  • 这将更改文件写入的位置,并将更改页面的生成位置,以便模板引用
  • RelPermalink 将接受由于此选项中的映射而进行的调整

即相对 URL

动态配置

1
2
permalinks:
  posts: /:year/:month/:title/:slug

说明::year :month 根据页面中的 date 注释的时间替换,:title:slug 分别为 titleslug 注释

所有支持的动态配置

描述
:title title 注释
:slug slug 注释
:section 页面所属分区
:sections[1:last] 页面所属除第一个和最后一个的全部分区
:filename 文件名
:slugorfilename 文件名或者 slug 注释
:year date 注释时间中的年份(4位数)
:month date 注释时间中的月份(2位数)
:monthname date 注释时间中的月份名
:day date 注释时间中的月份天数(1-2位数)
:weekday date 注释时间中的周期(0-6)
:weekdayname date 注释时间中的周期名
:yearday date 注释时间中的年份天数(1-3位数)

别名

在页面头部注释中配置 aliases,会创建新的相对和绝对 URL,之前的自动变为旧链接

  • 访问时通过旧链接重定向到新链接

  • 通过站点配置 disableAliases=false 禁用别名

直接指定

在页面头部注释中配置 url,直接指定当前页面的相对 URL

Pretty URLs & Ugly URLs

  1. Pretty URLs :例如 https://example.com/about/
  2. Ugly URLs:例如 https://example.com/about.html

注:默认为 Pretty URLs,如果开启 Ugly URLs,在站点配置中配置 uglyURLs=true

关联 URLs

默认情况下,Hugo 保持所有相对URL不变,当从本地文件系统浏览时,这可能会有问题

在站点配置中设置 relativeURL=true 将导致 Hugo 重写所有相对 URL,使其与当前内容相对

  • 例如 /posts/first/ 页面包含链接 /about/,将会改为 ../../about/

菜单

Hugo 提供简单但功能强大的菜单系统

  • 如果需要更简单的菜单,站点配置中设置 sectionPagesMenu=true,所有的页面都会在侧边菜单显示

  • 通过 .Site.Menus 访问所有菜单,.Site.Menus.main 访问主菜单

1
2
3
4
5
6
7
8
menu:
  main:
  - identifier: about
    name: 关于
    params:
      icon: highlight-menu-item
    url: /about/
    weight: -110

静态文件

静态文件目录默认在 static/,可以通过站点配置 staticDir 进行覆盖

  • static/ 目录下的文件会原样复制到生成站点的根目录

目录

目录默认通过标题生成,可以通过配置配置目录标题显示等级,或者自定义模板

目录配置

1
2
3
4
5
markup:
  tableOfContents:
    endLevel: 5 # 目录显示最低等级
    ordered: true # 是否在目录前显示序号
    startLevel: 1 # 目录显示最高等级

模板示例:基本目录

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
{{/* layout/_default/single.html */}}
{{ define "main" }}
<main>
    <article>
    <header>
        <h1>{{ .Title }}</h1>
    </header>
        {{ .Content }}
    </article>
    <aside>
        {{ .TableOfContents }}
    </aside>
</main>
{{ end }}

模板示例:部分显示目录

下面的示例中,只有头部注释 toc=true 且文章字数大于 400 才显示目录

1
2
3
4
5
6
7
8
9
{{/* layouts/partials/toc.html */}}
{{ if and (gt .WordCount 400 ) (.Params.toc) }}
<aside>
    <header>
    <h2>{{.Title}}</h2>
    </header>
    {{.TableOfContents}}
</aside>
{{ end }}

评论

Hugo 附带了一个内部 Disqus 模板

Hugo 提供了对 Disqus 的支持,Disqus 是一种通过 JavaScript 向网站提供评论和社区功能的第三方服务。

主题可能已经支持 Disqus,但如果不支持,很容易通过 Hugo 内置的 Disqus 部分 添加到模板中

添加 DIsqus

Hugo 提供了将 Disqus 加载到模板中所需的所有代码。在将Disqus添加到网站之前,需要设置一个帐户

1
disqusShortname: yourDisqusShortname

对大多数站点来说,上面的配置已经足够,但是仍然可以在页面头部注释设置以下内容:

  • disqus_identifier
  • disqus_title
  • disqus_url

Hugo 内置 Disqus 模板

{{ template “_internal/disqus.html” . }}

提示:请留意 .

替代方案

下面是一些 Disqus 的替代选择

国际化

Hugo支持同时使用多种语言创建网站

  • 可以在站点配置的 languages 部分中定义可用语言
  • 可以在站点配置的 disableLanguages 配置禁用的语言

配置

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
defaultContentLanguage: zh-cn
languages:
  zh-cn:
    baseURL: https://zh-cn.example.com
    languagedirection: 中文
    title: 我的博客
    weight: 2
  en:
    baseURL: https://en.example.com
    params:
      linkedin: https://linkedin.com/whoever
    title: My blog
    weight: 1
  1. 语言块中未定义的任何内容都将返回到该键的全局值
  2. defaultContentLanguage 设置项目的默认语言。如果未设置,默认语言将为 en
  3. 如果默认语言需要像其他语言一样在其自己的语言代码(/en)之下呈现,设置defaultContentLanguageISubdir=true
  4. 使用小写语言代码,即使是使用区域语言
  5. 可以配置不同语言不同的主机地址,将会在 public 下生成 /zh-cn/en

页面翻译

通过文件名

  1. /content/about.zh-cn.md
  2. /content/about.en.md

注:文件名不含 language code,Hugo 认为是配置的默认语言

通过目录

1
2
3
4
5
6
7
8
9
languages:
  en:
    contentDir: content/english
    languageName: English
    weight: 10
  zh-cn:
    contentDir: content/zh-cn
    languageName: 中文
    weight: 20

注:可以是项目中任何有效的路径,不一定必须在 content/

绕过默认链接

有下面三个页面:

  1. /content/about.en.md
  2. /content/om.zh-cn.md
  3. /content/presentation/propos.fr.md

在三个页面的头部注释都设置 translationKey=about,它们的链接都将视为已翻译的链接,将有如下特性

  • 不考虑其名称或位置
  • 所有翻译的页面都将共享相同的 URL(除了语言子目录)
  • 页面捆绑资源共享,Markdown、HTML等页面文件除外
    • 如果文件名重复,优先当前目录,然后根据语言设置的优先权重
    • 资源文件同样遵循 language code 文件名,如 banana.en.jpg

参考使用

模板文件使用

创建一个页面已翻译链接列表,模板文件类似下面这样:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
{{/* layouts/partials/i18nlist.html */}}
{{ if .IsTranslated }}
<h4>{{ i18n "translations" }}</h4>
<ul>
  {{ range .Translations }}
  <li>
    <a href="{{ .Permalink }}">{{ .Lang }}: {{ .Title }}{{ if .IsPage }} ({{ i18n "wordCount" . }}){{ end }}</a>
  </li>
  {{ end }}
</ul>
{{ end }}

或者列出所有允许的页面链接:

1
2
3
4
5
6
{{/* layouts/partials/allLanguages.html */}}
<ul>
{{ range $.Site.Home.AllTranslations }}
<li><a href="{{ .Permalink }}">{{ .Language.LanguageName }}</a></li>
{{ end }}
</ul>

注:例子中 i18n "translations" 中,假设当前页面语言为 en,在语言目录 i18n/en 中配置的 home.other 的值

i18n 函数

假设当前站点语言为 en,当前页面字数为 100,预计阅读时长为 1 minutes

国际化目录文件 i18n/en,含有下面内容:

1
2
3
4
5
6
7
home:
  other: Home
wordCount:
  other: This article has {{ .WordCount }} words.
readingTime:
  one: One minute to read
  other: {{.Count}} minutes to read

现在有下面的短代码:

1
2
3
{{ i18n "home" }}
{{ i18n "wordCount" . }}
{{ i18n "readingTime" .ReadingTime }}

返回值分别会是:

1
2
3
Home
This article has 100 words.
one minutes to read # 如果阅读时长为 2: 2 minutes to read

菜单国际化

不使用 i18n

有下面的语言配置:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
defaultContentLanguage: en
languages:
  de:
    languageName: Deutsch
    menu:
      main:
      - name: Startseite
        url: /
        weight: 0
    weight: 10
  en:
    languageName: English
    menu:
      main:
      - name: Home
        url: /
        weight: 0
    weight: 0

模板文件使用:

1
2
3
4
5
6
7
8
<ul>
    {{- $currentPage := . -}}
    {{ range .Site.Menus.main -}}
    <li class="{{ if $currentPage.IsMenuCurrent "main" . }}active{{ end }}">
        <a href="{{ .URL | absLangURL }}">{{ .Name }}</a>
    </li>
    {{- end }}
</ul>

注:将生成一个菜单链接,显示文本为 Home

使用 i18n

有下面的语言配置:

1
2
3
4
5
6
7
defaultContentLanguage: en
menu:
  main:
  - identifier: about
    name: About me
    url: about
    weight: 1

i18n/en 文件:

1
2
about:
  other: About me in i18n

模板文件使用:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
<ul>
    {{- $currentPage := . -}}
    {{ range .Site.Menus.main -}}
    <li class="{{ if $currentPage.IsMenuCurrent "main" . }}active{{ end }}">
        <a href="{{ .URL | absLangURL }}">{{ i18n .Identifier | default .Name}}</a>
        {{/* 或者 */}}
        <a href="{{ .URL | absLangURL }}">{{ T .Identifier | default .Name}}</a>
    </li>
    {{- end }}
</ul>

注:将生成一个菜单链接,显示文本为 About me in i18n

语法高亮

语法高亮配置

注:使用语法高亮需要在 Markdown 文档使用短代码,这将导致文档离开 Hugo 可读性变差,因此:

模板

模板的作用是告诉 Hugo 如何根据文档(如 .md)生成特定的 HTML 文件

Hugo 的模板语法分为2种基础模板:

  • html/templata
  • text/template

模板语法

基本语法

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
{{ . }} 					// 当前上下文
{{ $. }}					// 全局上下文
{{ .Title }}				// 获取当前上下文属性 Title
{{ .Params.bar }}  	// 链式获取
{{ $address }} 		// 预定义一个变量

{{ add 1 2 }}         // 函数调用,相当于 add(1,2)
 
// 一对 () 表示一段组合表达式,end 表示结束一个条件分支
{{ if or (isset .Params "alt") (isset .Params "caption") }} Caption {{ end }} 
// 可以换行
{{ if or
  (isset .Params "alt")
  (isset .Params "caption")
}}

// 多行字符串
{{ $msg := `Line one.
Line two.` }}

变量

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
<title>{{ .Title }}</title>	// 获取头部注释 title 的值,作为 <title> 标签的内容

{{ $address := "123 Main St." }}  // 定义变量 $address 并赋值为 "123 Main St.""
{{ $address }} // 使用 

{{ $var := "Hugo Page" }}
{{ if .IsHome }}
    {{ $var = "Hugo Home" }} // 重新赋值
{{ end }}
Var is {{ $var }}

函数

1
2
{{ add 1 2 }} // 3
{{ lt 1 2 }} // true

说明:上面的例子调用了两个 Hugo 内置函数

  • add:返回累加和
  • lt:第一个参数是否小于第二个参数,是返回 true,否返回 false

更多内置函数参阅 内置函数

模板嵌套

当需要嵌套使用一个模板时,需要向其传递它需要访问的数据:

  • 如果传递的是当前上下文,在后面加上点 .
  • 所有模板都在 layouts/ 目录下

partial:例如嵌套一个 layouts/partials/header.html

1
{ partial "header.html" . }}

template

在更老的 Hugo 版本中,template 函数用于包含部分模板。现在它只在调用 内部模板 时有用:

1
{{ template "_internal/opengraph.html" . }}

说明:内部模板一般以 _ 开头,且不在 layouts/ 下——因为该目录下的都是自定义模板

循环语句

通过 range 可以循环迭代 go 语言的 map array slice

  • 需要 {{ end }} 标记循环结束的位置

示例1:基本使用

1
2
3
{{ range $array }}
    {{ . }} // . 表示 $array 里面的元素
{{ end }}

示例2:变量取值

1
2
3
{{ range $elem_val := $array }} // 取出 $array 里的元素并赋值给 $elem_val 变量
    {{ $elem_val }}
{{ end }}

示例3:变量取值、取索引

1
2
3
{{ range $elem_index, $elem_val := $array }}
   {{ $elem_index }} -- {{ $elem_val }}
{{ end }}

示例4:map 遍历

1
2
3
{{ range $elem_key, $elem_val := $map }}
   {{ $elem_key }} -- {{ $elem_val }}
{{ end }}

示例5:空值判断

1
2
3
4
5
{{ range $array }}
    {{ . }}
{{else}}
    // 如果为空,进这里
{{ end }}

条件语句

通过 if else with or andnot 提供条件分支逻辑处理

  • 其中 if with 为一个条件分支的起始,需要 {{ end }} 标记条件分支结束的位置
  • 表达式是以下三种情况视为 false
    1. 布尔值 false
    2. 数值 0
    3. 长度为 0 的数组、切片、映射、字符串

示例1ifwith

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
// 如果设置了头部注释 title,使用 <h4> 标签渲染
{{ if isset .Params "title" }}
    <h4>{{ index .Params "title" }}</h4>
{{ end }}

// 同上
{{ with .Params.title }} // 假如 with 换成 if,未设置 title 将会报错
    <h4>{{ . }}</h4>
{{ end }}

// 如果设置了头部注释 description,渲染 description。否则渲染 summary
{{ with .Param "description" }}
    {{ . }}
{{ else }}
    {{ .Summary }}
{{ end }}

示例2if else ifelse

1
2
3
4
5
6
7
{{ if (isset .Params "description") }}
    {{ index .Params "description" }}
{{ else if (isset .Params "summary") }}
    {{ index .Params "summary" }}
{{ else }}
    {{ .Summary }}
{{ end }}

示例3andor

1
2
3
4
{{ if (and (or (isset .Params "title") (isset .Params "caption")) (isset .Params "attr")) }}
 
 // 相当于 js 的
 if ( (params.title || params.caption) && params.attr )

示例4not

1
2
3
{{ if not .Params.notoc }}
	<a href="#" id="toc-toggle">{{.Title}}</a>
{{ end }}

管道

Go模板最强大的组件之一是能够一个接一个地堆叠操作。这是通过使用管道来完成的。概念很简单:

  • 每个管道的输出变成下一个管道的输入
  • 应用场景就是 简化函数嵌套调用简化条件判断表达式

由于 Go 模板的语法非常简单,管道对于将函数调用链接在一起至关重要:

  • 限制:只能处理单个值,并且该值将成为下一个管道的最后一个参数

示例1:简化函数嵌套调用

1
2
3
{{ shuffle (seq 1 5) }}

{{ (seq 1 5) | shuffle }} // 等同于上面的代码

示例2:简化条件判断表达式

1
2
3
4
5
6
7
8
{{ if or (or (isset .Params "title") (isset .Params "caption")) (isset .Params "attr") }}
Stuff Here
{{ end }}

// 等同于上面的代码
{{ if isset .Params "caption" | or isset .Params "title" | or isset .Params "attr" }}
Stuff Here
{{ end }}

{{- -}}

假设头部注释 title=Hello, World!,有下面的模板:

1
2
3
4
5
6
7
<div>
  {{ .Title }}
</div>

<div>
  {{- .Title -}}
</div>

将会输出:

1
2
3
4
5
<div>
  Hello, World!
</div>

<div>Hello, World!</div>

注释

模板中有两种注释可以使用:

  1. Go 注释
  2. HTML 注释
1
2
Bonsoir, {{/* {{ add 0 + 2 }} */}}Eliott.
{{ "<!-- This is an HTML comment -->" | safeHTML }}

注意:Hugo 首先加载的 Go 代码,然后渲染 HTML

  • 如果HTML注释包含了 Go 模板代码,可能导致构建失败

使用参数

可以使用两种参数:

  1. 页面参数,这是定义在 头部注释 里的
  2. 全局参数,这是定义在 站点配置 params 里的

示例1:页面参数

my_one_page.md

1
2
3
4
5
6
---
title: Roadmap
lastmod: 2017-03-05
date: 2013-11-18
notoc: true
---

my_template.html

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
{{ if not .Params.notoc }}
<aside>
  <header>
    <a href="#{{.Title | urlize}}">
    <h3>{{.Title}}</h3>
    </a>
  </header>
  {{.TableOfContents}}
</aside>
<a href="#" id="toc-toggle"></a>
{{ end }}

示例2:页面参数

config.yaml

1
2
3
4
params:
  copyrighthtml: Copyright &#xA9; 2017 John Doe. All Rights Reserved.
  sidebarrecentlimit: 5
  twitteruser: spf13

my_template.html

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
{{ /* 渲染 footer */}}
{{ if .Site.Params.copyrighthtml }}
    <footer>
        <div class="text-center">{{.Site.Params.CopyrightHTML | safeHTML}}</div>
    </footer>
{{ end }}

{{ /* 渲染 twitter 头像和链接 */}}
{{ with .Site.Params.twitteruser }}
    <div>
        <a href="https://twitter.com/{{.}}" rel="author">
          <img src="/images/twitter.png" width="48" height="48" title="Twitter: {{.}}" alt="Twitter">
          </a>
    </div>
{{ end }}
  
{{ /* 渲染站点前五页面链接 */}}
<nav>
  <h1>Recent Posts</h1>
  <ul>
  {{- range first .Site.Params.SidebarRecentLimit .Site.Pages -}}
      <li><a href="{{.RelPermalink}}">{{.Title}}</a></li>
  {{- end -}}
  </ul>
</nav>

模板应用规则

一个网站肯定是包含许多不同的页面的,这些不同的页面的 HTML 结构也都不一样

因此,模板文件将会有许多,Hugo 将按照一定的规则,选用特定的模板进行文档的 HTML 生成

Kind

页面类型只有两种:

  1. 单页:会选择 layouts/_default/single.html 模板
  2. Home:特殊的单页
  3. 集合页:与 页面分区 页面分类 有关,会选择 layouts/_default/list.html 模板

Layout

可在页面头部注释 layout 设置,会在 layouts/ 找指定的模板

Output Format

参阅 自定义输出格式 ,输出格式由 文件名后缀 组成

  • 文件名:头部注释 name 的值;或非 index_index 文件名;或 index.md _index.md 的所在目录名

  • 后缀:头部注释 output 定义的输出格式

  • Hugo 会使用匹配度最高模板

  • 如果头部注释 output 定义了多个格式,将会匹配第一个

Language

假设默认语言是 en,匹配优先级如下:

  1. index.en.amp.html
  2. index.amp.html
  3. index.en.html:因为没有指定文件名,所以优先级低于 index.amp.html

Type

页面头部注释 type 指定,如果未设置,值为所在目录名

Section

页面分区 页面分类 相关

Theme

在 Hugo 中,模板会在项目的 layouts/ 和应用主题的 layouts 找到匹配度最高的应用:

  • 如果存在多条匹配度相同的,有下面的优先级:
    1. 项目内的 layouts/ 优先
    2. 主题列表定义在前面的优先

示例

示例很多,但是实际用不上那么多的规则,一般查看所使用的主题的模板,自行修改就好了

这里就不一一列举了,如果需要查阅,下面是官方示例:

自定义输出格式

Hugo 可以有多种格式输出内容,包括 日历事件电子书格式谷歌AMPJSON搜索索引,或任何自定义文本格式 本节介绍如何正确配置媒体类型和输出格式的网站,以及在哪里为自定义输出创建模板

媒体类型

媒体类型(MIME)在网络上传输包含两部分标识:文件格式格式内容,即 Type 和 文件后缀:

类型 后缀
application/json json
application/manifest+json webmanifest
application/octet-stream
application/pdf pdf
application/rss+xml xml rss
application/toml toml
application/xml xml
application/yaml yaml yml
font/otf otf
font/ttf ttf
image/bmp bmp
image/gif gif
image/jpeg jpg jpeg jpe jif jfif
image/png png
image/svg+xml svg
image/webp webp
text/calendar ics
text/css css
text/csv csv
text/html html
text/javascript js jsm mjs
text/jsx jsx
text/markdown md markdown
text/plain txt
text/tsx tsx
text/typescript ts
text/x-sass sass
text/x-scss scss
video/3gpp 3gpp 3gp
video/mp4 mp4
video/mpeg mpg mpeg
video/ogg ogv
video/webm webm
video/x-msvideo avi

自定义媒体类型

媒体类型支持自定义新增和修改,在 站点配置 中配置:

1
2
3
4
5
6
7
mediaTypes:
  text/enriched:
    suffixes:
    - enr
  text/html:
    suffixes:
    - asp

说明:上面的配置添加了一个新的媒体类型 ext/enriched,修改了媒体类型 text/html 的后缀为 asp

输出格式定义

下面是所有 Hugo 内置的输出格式定义表:

名称 媒体类型 基本名 rel 纯文本 HTML 丑链接 可链接
HTML text/html index canonical 🚫
AMP text/html index amphtml 🚫
CSS text/css styles stylesheet 🚫 🚫
CSV text/csv index alternate 🚫 🚫
Calendar text/calendar index alternate 🚫 🚫
JSON application/json index alternate 🚫 🚫
MARKDOWN text/markdown index alternate 🚫 🚫
ROBOTS text/plain robots alternate 🚫 🚫
RSS application/rss+xml index alternate 🚫 🚫 🚫 🚫
Sitemap application/xml sitemap sitemap 🚫 🚫 🚫 🚫
WebAppManifest application/manifest+json manifest manifest 🚫 🚫

注意:除上面的定义输出格式外,还有两项:pathprotocol

  • 只有 AMPpath=amp,其他默认为空
  • 只有 Calendarprotocol=webcal://,其他默认为空

如果需要修改内置的定义格式,可以在 站点配置 中配置:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
outputFormats:
  MyEnrichedFormat:
    name: Enriched # 输出格式标识符。这用于定义页面所需的输出格式
    baseName: myindex # 基本文件名,默认值:index
    isPlainText: true # 使用 Go 的纯文本模板解析器,默认值:false
    mediaType: text/enriched # 必须匹配已定义媒体类型的 Type
    protocol: bep:// # 替换 http:// 或 https:// 为设置的值
    path: enr # 子路径保存输出文件
    rel: alternate # 在 link 标签创建 rel 值,默认值:alternate
    noUgly: true # 如果站点设置 uglyURLs=true,设置 true 关闭当前格式的 uglyURLs
    permalinkable: true # 返回 .Permalink 和  .RelPermalink 
    isHTML: false # 仅用于 HTML 格式相关情况,默认值:false
    weight: 100 # 设置非0值,用于列表排序
    # 不需要其他格式,页面的头部注释 outputs 包含当前格式这个配置会忽略,默认值:false
    notAlternative: false 

页面输出格式

一个页面可以被渲染成多种输出格式

默认输出格式

通过头部注释 kind 配置,下面是映照表:

kind 值 默认输出格式 示例
page HTML index.html
home HTML, RSS /posts/first.md => posts/first/index.html
section HTML, RSS /posts/index.md => posts/index.html
taxonomy HTML, RSS tags分类 => tags/index.html
term HTML, RSS tags=React的页面 => tags/React/index.html

自定义输出格式

站点配置 头部注释 中配置:

1
2
3
4
5
6
7
outputs:
  home:
  - HTML
  - AMP
  - RSS
  page:
  - HTML

渲染输出格式

1
2
3
{{ range .AlternativeOutputFormats -}}
	<link rel="{{ .Rel }}" type="{{ .MediaType.Type }}" href="{{ .Permalink | safeURL }}">
{{ end -}}

由于 ``.Permalink.RelPermalink只是返回outputs` 第一个定义的生成地址,对于其他格式的地址,可以通过下面两种方式:

  • .OutputFormats 内置页面函数

single.json

1
2
3
4
{{ .RelPermalink }} > /that-page/
{{ with  .OutputFormats.Get "json" -}}
{{ .RelPermalink }} > /that-page/index.json
{{- end }}

注意:使用 .RelPermalink.Permalink 需要配置当前格式的 permalinkable=true

  • ref/relref 内置全局函数
1
2
{{ ref "blog/neat.md" "amp" }}
{{ relref "about.md#who" "amp" }}

输出格式模板

参考官网: 不同输出格式的模板应用规则

基础模板

官方视频介绍(英文):

基础模板可以用于定义站点的布局,通过 block 关键字定义一个插槽

可以根据需要在不同的模板中,通过 define 关键字应用不同的 HTML 内容

  • 下面的代码定义了一个基础模板:

layouts/_default/baseof.html

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>{{ block "title" . }}
      <!-- Blocks may include default content. -->
      {{ .Site.Title }}
    {{ end }}</title>
  </head>
  <body>
    <!-- Code that all your templates share, like a header -->
    {{ block "main" . }}
      <!-- The part of the page that begins to differ between templates -->
    {{ end }}
    {{ block "footer" . }}
    <!-- More shared code, perhaps a footer but that can be overridden if need be in  -->
    {{ end }}
  </body>
</html>
  • 应用HTML在 main 位置:

layouts/_default/list.html

1
2
3
4
5
6
7
8
9
{{ define "main" }}
  <h1>Posts</h1>
  {{ range .Pages }}
    <article>
      <h2>{{ .Title }}</h2>
      {{ .Content }}
    </article>
  {{ end }}
{{ end }}

Render Hooks

渲染钩子允许自定义模板覆盖 markdown 渲染功能

支持的 Hooks:

  1. 图片
  2. 链接
  3. 标题
  4. 代码块: v0.93.0

注意: 只有 Goldmark 渲染器支持

Hooks 模板文件

必须为下面的路径和文件名,在项目或者主题目录下

1
2
3
4
5
6
7
8
9
layouts/
└── _default/
    └── _markup/
        ├── render-codeblock-bash.html
        ├── render-codeblock.html
        ├── render-heading.html
        ├── render-image.html
        ├── render-image.rss.xml
        └── render-link.html

渲染链接和图片

在 Hooks 上下文中,链接和图片含有下面的上下文变量:

变量名 描述
.Page 当前页面
.Destination URL
.Title HTML title 属性
.Text HTML innerText
.PlainText ``.Text` 的纯文本内容

示例:渲染链接

假设 markdown 中有这样的一个链接:

1
[Text](https://www.gohugo.io "Title")

那么在 layouts/_default/_markup/render-link.html 中可以定义为:

1
<a href="{{ .Destination | safeURL }}"{{ with .Title}} title="{{ . }}"{{ end }}{{ if strings.HasPrefix .Destination "http" }} target="_blank" rel="noopener"{{ end }}>{{ .Text | safeHTML }}</a>

说明:上面的代码表示只有 http 开头的链接才会被 Hugo 渲染

示例:渲染图片

1
![Text](https://gohugo.io/images/hugo-logo-wide.svg "Title")
1
2
3
4
{{/* layouts/_default/_markup/render-image.html */}}
<p class="md__image">
  <img src="{{ .Destination | safeURL }}" alt="{{ .Text }}" {{ with .Title}} title="{{ . }}"{{ end }} />
</p>

渲染标题

在 Hooks 上下文中,标题含有下面的上下文变量:

变量名 描述
.Page 当前页面
.Level 标题等级 1-6
.Anchor HTML 生成的锚点
.Text HTML innerText
.PlainText .Text 的纯文本内容
.Attributes map 类型的标题属性(如 id class),link 类型的标题始终为空
.isBlock v0.108.0 是图像,且markup.goldmark.parser.wrapStandAloneImageWithinParagraph=false,返回true
.Ordinal v0.108.0 当前文档中所有图像标题的从零开始的序数

示例

1
### 标题3
1
2
{{/* layouts/_default/_markup/render-heading.html */}}
<h{{ .Level }} id="{{ .Anchor | safeURL }}">{{ .Text | safeHTML }} <a href="#{{ .Anchor | safeURL }}"></a></h{{ .Level }}>

将会被渲染为HTML:

1
<h3 id="section-a">标题3<a href="#标题3"></a></h3>

渲染代码块

需要 Hugo @v0.93.0 以上版本,在 Hooks 上下文中,代码块含有下面的上下文变量:

变量名 描述
.Page 当前页面
.Type 代码块使用语言,如 java
.Inner 代码块中的代码
.Options 色度高亮处理选项
.Attributes 从 Markdown 传入的属性
.Position 文件名和位置(行数,列),日志打印中会很有用
.Ordinal 当前文档中所有代码块从零开始的序数

列表模板

官方视频介绍(英文):

列表在Hugo中具有特定的含义和用法,用于呈现站点主页、部分页面、分类列表或标签列表等

列表模板是用于在单个HTML页面中呈现多个内容片段的模板

site-hierarchy

注:首页的列表模板有专用的一个模板, 列表模板应用规则

可以通过官方提供的视频更详细了解(英文):

默认列表模板

集合页面(Section)列表和分类标签列表默认都是 layouts/_default/list.html

如果应用了主题,如果前面的未找到,还会去主题的 themes/<THEME>/layouts/_default/list.html 下寻找

列表模板应用规则

列表渲染_index.md

有这样的一个内容目录:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
.
...
├── content
|   ├── posts
|   |   ├── _index.md
|   |   ├── post-01.md
|   |   └── post-02.md
|   └── quote
|   |   ├── quote-01.md
|   |   └── quote-02.md
...

其中,content/posts/_index.md 的内容:

1
2
3
4
5
6
7
8
9
---
title: My Go Journey
date: 2017-03-23
publishdate: 2017-03-24
---

I decided to start learning Go in March 2017.

Follow my journey through this new blog.

然后,在 layouts/_default/list.html 模板的内容为:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
{{ define "main" }}
<main>
    <article>
        <header>
            <h1>{{.Title}}</h1>
        </header>
        <!-- "{{.Content}}" pulls from the markdown content of the corresponding _index.md -->
        {{.Content}}
    </article>
    <ul>
    <!-- Ranges through content/posts/*.md -->
    {{ range .Pages }}
        <li>
            <a href="{{.Permalink}}">{{.Date.Format "2006-01-02"}} | {{.Title}}</a>
        </li>
    {{ end }}
    </ul>
</main>
{{ end }}

最终生成站点的 HTML 为 example.com/posts/index.html

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
<!--top of your baseof code-->
<main>
    <article>
        <header>
            <h1>My Go Journey</h1>
        </header>
        <p>I decided to start learning Go in March 2017.</p>
        <p>Follow my journey through this new blog.</p>
    </article>
    <ul>
        <li><a href="/posts/post-01/">Post 1</a></li>
        <li><a href="/posts/post-02/">Post 2</a></li>
    </ul>
</main>
<!--bottom of your baseof-->

如果不使用_index.md

Hugo 渲染列表 _index.md 不是必须的,例如:首页、集合、分类、标签等页面

  • 当未找到 _index.md 是,列表渲染不含 .Content并且只有 .Title 等头部注释的元素
  • 例如上面 quote 目录下的两个集合页面,最终渲染为这样的列表:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
<!--baseof-->
<main>
    <article>
        <header>
        <!-- 使用的是上下文中的 .Title,因为没有 _index.md 提供 title 注释字段 -->
            <h1>Quotes</h1>
        </header>
    </article>
    <ul>
        <li><a href="https://example.com/quote/quotes-01/">Quote 1</a></li>
        <li><a href="https://example.com/quote/quotes-02/">Quote 2</a></li>
    </ul>
</main>
<!--baseof-->

注意:Quotes 来源是 quote 目录名首字母大写+复数,可以通过站点配置 pluralizeListTitles=false 设置取消

示例

Section

layouts/section/posts.html

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
{{ partial "header.html" . }}
{{ partial "subheader.html" . }}
<main>
  <div>
   <h1>{{ .Title }}</h1>
        <ul>
        <!-- Renders the li.html content view for each content/posts/*.md -->
            {{ range .Pages }}
                {{ .Render "li"}}
            {{ end }}
        </ul>
  </div>
</main>
{{ partial "footer.html" . }}

Taxonomy

分类、标签等分类法的列表,layouts/_default/taxonomy.html

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
{{ define "main" }}
<main>
  <div>
   <h1>{{ .Title }}</h1>
   <!-- 遍历与特定分类法术语关联的每个内容文件并呈现 summary.html 内容视图 -->
    {{ range .Pages }}
        {{ .Render "summary"}}
    {{ end }}
  </div>
</main>
{{ end }}

列表排序

Hugo 中默认的优先级排序是:Weight > Date > LinkTitle > FilePath

layouts/partials/default-order.html

1
2
3
4
5
6
7
8
<ul>
    {{ range .Pages }}
        <li>
            <h1><a href="{{ .Permalink }}">{{ .Title }}</a></h1>
            <time>{{ .Date.Format "Mon, Jan 2, 2006" }}</time>
        </li>
    {{ end }}
</ul>

weight

layouts/partials/by-weight.html,weight 越小排序越靠前:

1
2
3
4
5
6
7
8
<ul>
    {{ range .Pages.ByWeight }}
        <li>
            <h1><a href="{{ .Permalink }}">{{ .Title }}</a></h1>
            <time>{{ .Date.Format "Mon, Jan 2, 2006" }}</time>
        </li>
    {{ end }}
</ul>

date

layouts/partials/by-date.html

1
2
3
4
5
6
7
8
9
<ul>
    <!-- orders content according to the "date" field in front matter -->
    {{ range .Pages.ByDate }}
        <li>
            <h1><a href="{{ .Permalink }}">{{ .Title }}</a></h1>
            <time>{{ .Date.Format "Mon, Jan 2, 2006" }}</time>
        </li>
    {{ end }}
</ul>

publishDate

layouts/partials/by-publish-date.html

1
2
3
4
5
6
7
8
9
<ul>
    <!-- orders content according to the "publishdate" field in front matter -->
    {{ range .Pages.ByPublishDate }}
        <li>
            <h1><a href="{{ .Permalink }}">{{ .Title }}</a></h1>
            <time>{{ .Date.Format "Mon, Jan 2, 2006" }}</time>
        </li>
    {{ end }}
</ul>

expiryDate

layouts/partials/by-expiry-date.html

1
2
3
4
5
6
7
8
<ul>
    {{ range .Pages.ByExpiryDate }}
        <li>
            <h1><a href="{{ .Permalink }}">{{ .Title }}</a></h1>
            <time>{{ .Date.Format "Mon, Jan 2, 2006" }}</time>
        </li>
    {{ end }}
</ul>

lastmod

layouts/partials/by-last-mod.html

1
2
3
4
5
6
7
8
9
<ul>
    <!-- orders content according to the "lastmod" field in front matter -->
    {{ range .Pages.ByLastmod }}
        <li>
            <h1><a href="{{ .Permalink }}">{{ .Title }}</a></h1>
            <time>{{ .Date.Format "Mon, Jan 2, 2006" }}</time>
        </li>
    {{ end }}
</ul>

length

layouts/partials/by-length.html

1
2
3
4
5
6
7
8
9
<ul>
    <!-- 页面长度排序短的在前 -->
    {{ range .Pages.ByLength }}
        <li>
            <h1><a href="{{ .Permalink }}">{{ .Title }}</a></h1>
            <time>{{ .Date.Format "Mon, Jan 2, 2006" }}</time>
        </li>
    {{ end }}
</ul>

title

layouts/partials/by-title.html

1
2
3
4
5
6
7
8
9
<ul>
    <!-- ranges through content in ascending order according to the "title" field set in front matter -->
    {{ range .Pages.ByTitle }}
        <li>
            <h1><a href="{{ .Permalink }}">{{ .Title }}</a></h1>
            <time>{{ .Date.Format "Mon, Jan 2, 2006" }}</time>
        </li>
    {{ end }}
</ul>

linkTitle

layouts/partials/by-link-title.html

1
2
3
4
5
6
7
8
9
<ul>
    <!-- 根据前面内容的linkTitle字段升序排列内容如果没有设置 "linkTitle" 字段则使用 "title" 字段的内容并将该值用于.LinkTitle-->
    {{ range .Pages.ByLinkTitle }}
        <li>
            <h1><a href="{{ .Permalink }}">{{ .LinkTitle }}</a></h1>
            <time>{{ .Date.Format "Mon, Jan 2, 2006" }}</time>
        </li>
    {{ end }}
</ul>

Parameter

  1. 根据指定的参数进行排序,首先找页面头部注释的 <field>
  2. 没找到则寻找 .Site.Params.<field> 的默认值
  3. 都没找到,将会排在末尾

layouts/partials/by-rating.html

1
2
3
4
<!-- 通过头部注释 "rating" 排序 -->
{{ range (.Pages.ByParam "rating") }}
  <!-- ... -->
{{ end }}

如果目标字段嵌套在另一个字段下面,则可以使用 . 表示法访问该字段

layouts/partials/by-nested-param.html

1
2
3
{{ range (.Pages.ByParam "author.last_name") }}
  <!-- ... -->
{{ end }}

Reverse

layouts/partials/by-date-reverse.html

1
2
3
4
5
6
7
8
<ul>
    {{ range .Pages.ByDate.Reverse }}
        <li>
            <h1><a href="{{ .Permalink }}">{{ .Title }}</a></h1>
            <time>{{ .Date.Format "Mon, Jan 2, 2006" }}</time>
        </li>
    {{ end }}
</ul>

列表分组

Hugo 提供内置的函数对站点页面进行分组

Page Field

layouts/partials/by-page-field.html

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
<!-- 根据页面内容分组在这个例子中".Key" 是集合标题 -->
{{ range .Pages.GroupBy "Section" }}
<h3>{{ .Key }}</h3>
<ul>
    {{ range .Pages }}
    <li>
    <a href="{{ .Permalink }}">{{ .Title }}</a>
    <div class="meta">{{ .Date.Format "Mon, Jan 2, 2006" }}</div>
    </li>
    {{ end }}
</ul>
{{ end }}

注意:在前面的内容中,可以使用 {{ .Title }} 获取 _index.mdtitle 字段

同样可以使用 .GetPage 函数

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
<!-- Groups content according to content section.-->
{{ range .Pages.GroupBy "Section" }}
<!--  _index.md  "title" 注释 -->
{{ with $.Site.GetPage "section" .Key }}
<h3>{{.Title}}</h3>
{{ else }}
<!-- 如果没有 _index.md使用 ".Key" 的值默认是大写复数的所在目录名 -->
<h3>{{ .Key | title }}</h3>
{{ end }}
<ul>
    {{ range .Pages }}
    <li>
    <a href="{{ .Permalink }}">{{ .Title }}</a>
    <div class="meta">{{ .Date.Format "Mon, Jan 2, 2006" }}</div>
    </li>
    {{ end }}
</ul>
{{ end }}

date

layouts/partials/by-page-date.html

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
<!-- "date" 字段分组 -->
{{ range .Pages.GroupByDate "2006-01" }}
<h3>{{ .Key }}</h3>
<ul>
    {{ range .Pages }}
    <li>
    <a href="{{ .Permalink }}">{{ .Title }}</a>
    <div class="meta">{{ .Date.Format "Mon, Jan 2, 2006" }}</div>
    </li>
    {{ end }}
</ul>
{{ end }}

注:在 v0.97.0+ 中,.GroupByDate 参数的时间格式与 .time.Format 函数 相同,并且 .Key 会被国际化

publishDate

layouts/partials/by-page-publish-date.html

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
<!-- "publishDate" 字段分组 -->
{{ range .Pages.GroupByPublishDate "2006-01" }}
<h3>{{ .Key }}</h3>
<ul>
    {{ range .Pages }}
    <li>
    <a href="{{ .Permalink }}">{{ .Title }}</a>
    <div class="meta">{{ .PublishDate.Format "Mon, Jan 2, 2006" }}</div>
    </li>
    {{ end }}
</ul>
{{ end }}

注:在 v0.97.0+ 中,.GroupByPublishDate 参数的时间格式与 .time.Format 函数 相同,并且 .Key 会被国际化

lastmod

layouts/partials/by-page-lastmod.html

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
<!-- "lastmod" 字段分组 -->
{{ range .Pages.GroupByLastmod "2006-01" }}
<h3>{{ .Key }}</h3>
<ul>
    {{ range .Pages }}
    <li>
    <a href="{{ .Permalink }}">{{ .Title }}</a>
    <div class="meta">{{ .Lastmod.Format "Mon, Jan 2, 2006" }}</div>
    </li>
    {{ end }}
</ul>
{{ end }}

注:在 v0.97.0+ 中,.GroupByLastmod 参数的时间格式与 .time.Format 函数 相同,并且 .Key 会被国际化

expiryDate

layouts/partials/by-page-expiry-date.html

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
<!-- "expiryDate" 字段分组 -->
{{ range .Pages.GroupByExpiryDate "2006-01" }}
<h3>{{ .Key }}</h3>
<ul>
    {{ range .Pages }}
    <li>
    <a href="{{ .Permalink }}">{{ .Title }}</a>
    <div class="meta">{{ .ExpiryDate.Format "Mon, Jan 2, 2006" }}</div>
    </li>
    {{ end }}
</ul>
{{ end }}

注:在 v0.97.0+ 中,.GroupByExpiryDate 参数的时间格式与 .time.Format 函数 相同,并且 .Key 会被国际化

Page Parameter

layouts/partials/by-page-param.html

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
<!-- "param_key" 字段分组 -->
{{ range .Pages.GroupByParam "param_key" }}
<h3>{{ .Key }}</h3>
<ul>
    {{ range .Pages }}
    <li>
    <a href="{{ .Permalink }}">{{ .Title }}</a>
    <div class="meta">{{ .Date.Format "Mon, Jan 2, 2006" }}</div>
    </li>
    {{ end }}
</ul>
{{ end }}

Page Parameter in Date Format

layouts/partials/by-page-param-as-date.html

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
<!-- 按月和 "param_key" 分组 -->
{{ range .Pages.GroupByParamDate "param_key" "2006-01" }}
<h3>{{ .Key }}</h3>
<ul>
    {{ range .Pages }}
    <li>
    <a href="{{ .Permalink }}">{{ .Title }}</a>
    <div class="meta">{{ .Date.Format "Mon, Jan 2, 2006" }}</div>
    </li>
    {{ end }}
</ul>
{{ end }}

Reverse

分组后,按字母数字顺序(A-Z, 1-100)和日期的反向时间顺序

虽然这些是逻辑默认值,但它们并不总是理想的顺序

有两种不同的语法可以更改 Hugo 的默认顺序:

1.反转方法
1
2
{{ range (.Pages.GroupBy "Section").Reverse }}
{{ range (.Pages.GroupByDate "2006-01").Reverse }}
2.可选参数
1
2
{{ range .Pages.GroupByDate "2006-01" "asc" }}
{{ range .Pages.GroupBy "Section" "desc" }}

Order Within Groups

由于分组会返回 {{ .Key }} 上下文变量和一个页面切片,上面所有的分组排序都是可用的

下面的例子最终排序为:

  1. 每个月一组,
  2. 每个组 date 升序
  3. 组内标题字母排序

layouts/partials/by-group-by-page.html

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
{{ range .Pages.GroupByDate "2006-01" "asc" }}
<h3>{{ .Key }}</h3>
<ul>
    {{ range .Pages.ByTitle }}
    <li>
    <a href="{{ .Permalink }}">{{ .Title }}</a>
    <div class="meta">{{ .Date.Format "Mon, Jan 2, 2006" }}</div>
    </li>
    {{ end }}
</ul>
{{ end }}

列表过滤

通过 where 函数 过滤,通过 first 函数 限制数量

首页模板

官方视频介绍(英文):

网站首页通常与其他页面不同,因此,Hugo 为首页单独约定一个唯一模板

在首页模板中,可以使用所有的 站点变量 页面变量

官方参考视频(英文):

首页模板应用规则

参考官方文档

编辑首页

Hugo 约定 content/_index.md 为首页,首页类似于 列表模板

layouts/index.html

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
{{ define "main" }}
    <main aria-role="main">
      <header class="homepage-header">
        <h1>{{.Title}}</h1>
        {{ with .Params.subtitle }}
        <span class="subtitle">{{.}}</span>
        {{ end }}
      </header>
      <div class="homepage-content">
        <!-- 这里会渲染 content/_index.md 的内容 -->
        {{.Content}}
      </div>
      <div>
        {{ range first 10 .Site.RegularPages }}
            {{ .Render "summary"}}
        {{ end }}
      </div>
    </main>
{{ end }}

集合页面模板

在集合目录下的 _index.md 编辑头部注释和内容

模板应用顺序参考官方文档

页面类型

页面默认输出格式

示例:默认

layouts/_default/section.html

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
{{ define "main" }}
  <main>
      {{ .Content }}
          <ul class="contents">
          {{ range .Paginator.Pages }}
              <li>{{.Title}}
                  <div>
                    {{ partial "summary.html" . }}
                  </div>
              </li>
          {{ end }}
          </ul>
      {{ partial "pagination.html" . }}
  </main>
{{ end }}

示例:.GetPage

通过 .GetPage 函数 定义渲染模板,假设目录关系为:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
.
└── content
    ├── blog
    │   ├── _index.md # "title: My Hugo Blog" in the front matter
    │   ├── post-1.md
    │   ├── post-2.md
    │   └── post-3.md
    └── events #Note there is no _index.md file in "events"
        ├── event-1.md
        └── event-2.md

在模板中使用:

1
<h1>{{ with .Site.GetPage "section" "blog" }}{{ .Title }}{{ end }}</h1>

最终生成的 HTML:

1
<h1>My Hugo Blog</h1>

注意:在上面的例子中

  • 如果未定义 _index.md文件, .Site.GetPage 将返回 nil

  • 如果 content/blog/_index.md 不存在,将会返回格式化的目录名

如果在模板中获取 events 目录下的集合页面:

1
<h1>{{ with .Site.GetPage "section" "events" }}{{ .Title }}{{ end }}</h1>

最终生成的 HTML:

1
<h1>Events</h1>

分类法模板

默认情况下,在页面头部注释中定义 categories tags series 会默认生成 HTML 在 /categories /tags /series 目录

分类方法

所有方法在 .Site.Taxonomies.<string> 下面

其中 string 是站点配置 taxonomies 中定义的分类名,默认情况下是: categories tags series

含有以下方法:

方法 描述
.Get(name) 返回类名为 name 的 WeightedPages
.Count(name) 返回类名为 name 的 WeightedPages 数量
.Alphabetical 返回 OrderedTaxonomy 以字母的顺序
.ByCount 返回 OrderedTaxonomy 以每个类别的数量,大的在前

OrderedTaxonomy

在 Go 中,map 是无序的,因此有序的 OrderedTaxonomy 类型定义为切片:

1
2
3
4
[]struct {
    Name          string
    // ...
}

还有以下方法:

方法 描述
.Term 分类名称
.WeightedPages weight 排好序的分类集
.Count 分类数量
.Pages 该分类所有页面
.Reverse 顺序反转

WeightedPages

对于 WeightedPages ,是 WeightedPage 的切片,可访问的方法:

方法 描述
.Count(name) 返回类名为 name 的 WeightedPages 数量
.Pages 该分类所有页面

分类顺序

字母顺序

所有分类按照字母排序

1
2
3
4
5
<ul>
    {{ range .Data.Terms.Alphabetical }}
            <li><a href="{{ .Page.Permalink }}">{{ .Page.Title }}</a> {{ .Count }}</li>
    {{ end }}
</ul>

weight优先级

1
2
3
4
5
6
7
8
+++
tags = [ "a", "b", "c" ]
tags_weight = 22
categories = ["d"]
title = "foo"
categories_weight = 44
+++
Front Matter with weighted tags and categories

说明:上面的例子,获取所有分类时 tags 的分类会在 categories 之前

分类渲染

Tags

显示 tags 标签的所有分类,使用 .GetTerms

1
2
3
4
5
<ul>
    {{ range (.GetTerms "tags") }}
        <li><a href="{{ .Permalink }}">{{ .LinkTitle }}</a></li>
    {{ end }}
</ul>

或者,使用 Site.Taxonomies.tags

1
2
3
4
5
<ul>
    {{ range .Site.Taxonomies.tags }}
            <li><a href="{{ .Page.Permalink }}">{{ .Page.Title }}</a> {{ .Count }}</li>
    {{ end }}
</ul>

或者,使用 .GetPage

1
2
3
4
5
6
7
8
{{ $taxo := "tags" }}
<ul class="{{ $taxo }}">
    {{ with ($.Site.GetPage (printf "/%s" $taxo)) }}
        {{ range .Pages }}
            <li><a href="{{ .Permalink }}">{{ .Title}}</a></li>
        {{ end }}
    {{ end }}
</ul>

series

显示 series=golang 分类的所有页面:

1
2
3
4
5
<ul>
    {{ range .Site.Taxonomies.series.golang }}
        <li><a href="{{ .Page.RelPermalink }}">{{ .Page.Title }}</a></li>
    {{ end }}
</ul>

显示所有分类方式,并且显示每个分类方式下的所有分类:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
<section id="menu">
    <ul>
        {{ range $key, $taxonomy := .Site.Taxonomies.featured }}
        <li>{{ $key }}</li>
        <ul>
            {{ range $taxonomy.Pages }}
            <li hugo-nav="{{ .RelPermalink}}"><a href="{{ .Permalink}}">{{ .LinkTitle }}</a></li>
            {{ end }}
        </ul>
        {{ end }}
    </ul>
</section>

单页模板

单页模板应用于具体的单独页面,一般是最终展示的具有详细内容的页面,例如文章

模板应用可以参阅, 单页模板应用规则 ,例如 layouts/posts/single.html 应用在 content/posts/ 下的所有单页:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
{{ define "main" }}

<section id="main">
  <h1 id="title">{{ .Title }}</h1>
  <div>
        <article id="content">
           {{ .Content }}
        </article>
  </div>
</section>
<aside id="meta">
    <div>
    <section>
      <h4 id="date"> {{ .Date.Format "Mon Jan 2, 2006" }} </h4>
      <h5 id="wordcount"> {{ .WordCount }} Words </h5>
    </section>
      {{ with .GetTerms "topics" }}
        <ul id="topics">
          {{ range . }}
            <li><a href="{{ .RelPermalink }}">{{ .LinkTitle }}</a></li>
          {{ end }}
        </ul>
      {{ end }}
      {{ with .GetTerms "tags" }}
        <ul id="tags">
          {{ range . }}
            <li><a href="{{ .RelPermalink }}">{{ .LinkTitle }}</a></li>
          {{ end }}
        </ul>
      {{ end }}
    </div>
    <div>
        {{ with .PrevInSection }}
          <a class="previous" href="{{.Permalink}}"> {{.Title}}</a>
        {{ end }}
        {{ with .NextInSection }}
          <a class="next" href="{{.Permalink}}"> {{.Title}}</a>
        {{ end }}
    </div>
</aside>
{{ end }}

视图模板

Hugo可以呈现内容的其他视图,这在列表和摘要视图中特别有用

以下是视图模板的常见用例:

  1. 希望在主页上显示每种类型的内容,但只有有限的摘要视图
  2. 在分类法列表页面上显示页面列表

创建视图内容

下面的目录表示,为 content/posts/content/project/ 目录下的单页,创建两个视图模板

  1. li.html
  2. summary.html
1
2
3
4
5
6
7
8
9
▾ layouts/
    ▾ posts/
        li.html
        single.html
        summary.html
    ▾ project/
        li.html
        single.html
        summary.html

同样也可以为所有单页定义为默认:

1
2
3
4
5
▾ layouts/
  ▾ _default/
      li.html
      single.html
      summary.html

所有视图模板按照下面的优先级:

  1. /layouts/<TYPE>/<VIEW>.html
  2. /layouts/_default/<VIEW>.html
  3. /themes/<THEME>/layouts/<TYPE>/<VIEW>.html
  4. /themes/<THEME>/layouts/_default/<VIEW>.html

示例:列表视图

列表模板 layouts/_default/list.html 渲染了 summary.html 视图模板

1
2
3
4
5
6
7
8
<main id="main">
  <div>
  <h1 id="title">{{ .Title }}</h1>
  {{ range .Pages }}
    {{ .Render "summary"}}
  {{ end }}
  </div>
</main>

layouts/_default/summary.html

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
<article class="post">
  <header>
    <h2><a href='{{ .Permalink }}'> {{ .Title }}</a> </h2>
    <div class="post-meta">{{ .Date.Format "Mon, Jan 2, 2006" }} - {{ .FuzzyWordCount }} Words </div>
  </header>
  {{ .Summary }}
  <footer>
  <a href='{{ .Permalink }}'><nobr>Read more </nobr></a>
  </footer>
</article>

数据模板

官方视频介绍(英文):

Hugo 支持 YAML XML JSONTOML 格式的数据加载,数据模板约定在 data 目录下

格式约定

数据文件夹应该存储额外的数据,以供 Hugo 在生成站点时使用。

数据文件不是用来生成独立页面的,它们应该作为补充内容文件:

  1. 头部注释的字段太多时,继承在数据文件中
  2. 显示大数据集的模板,如图表

所有文件后缀必须是下列之一:yml yaml json xml toml

访问数据文件通过 .Site.Data.<filename> 或者 .Site.Data <filename>,所有文件名必须是 字母 或者 _ 开头,例如:

数据文件 模板代码
x123.json {{ index .Site.Data "x123" }}
_123.json {{ index .Site.Data "_123" }}
x-123.json {{ index .Site.Data "x-123" }}

示例

访问整个数据文件

存在这样一个数据文件 data/jazz/bass/jacopastorius.yaml

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
discography:
- 1974 - Modern American Music … Period! The Criteria Sessions
- 1974 - Jaco
- 1976 - Jaco Pastorius
- 1981 - Word of Mouth
- 1981 - The Birthday Concert (released in 1995)
- 1982 - Twins I & II (released in 1999)
- 1983 - Invitation
- 1986 - Broadway Blues (released in 1998)
- 1986 - Honestly Solo Live (released in 1990)
- 1986 - Live In Italy (released in 1991)
- 1986 - Heavy'n Jazz (released in 1992)
- 1991 - Live In New York City, Volumes 1-7.
- 1999 - Rare Collection (compilation)
- '2003 - Punk Jazz: The Jaco Pastorius Anthology (compilation)'
- 2007 - The Essential Jaco Pastorius (compilation)

可以这样访问到:

1
2
3
{{ range $.Site.Data.jazz.bass }}
   {{ partial "artist.html" . }}
{{ end }}

然后在模板 lauouts/partials/artist.html 中:

1
2
3
4
5
<ul>
{{ range .discography }}
  <li>{{ . }}</li>
{{ end }}
</ul>

访问某个字段

data/user.yaml

1
2
3
4
5
6
Achievements:
- Can create a Key, Value list from Data File
- Learns Hugo
- Reads documentation
Name: User0123
Short Description: He is a **jolly good** fellow.

模板文件中:

1
2
3
<div>Short Description of {{.Site.Data.user.Name}}: 
    <p>{{ index .Site.Data.user "Short Description" | markdownify }}</p>
</div>

获取远程数据

通过 getJSONgetCSV 内置函数获取远程数据

基本使用

1
2
{{ $dataJ := getJSON "url" }}
{{ $dataC := getCSV "separator" "url" }}

前缀和传参

1
2
{{ $dataJ := getJSON "url prefix" "arg1" "arg2" "arg n" }}
{{ $dataC := getCSV  "separator" "url prefix" "arg1" "arg2" "arg n" }}

例如:

1
2
3
4
5
{{ $urlPre := "https://api.github.com" }}
{{ $gistJ := getJSON $urlPre "/users/GITHUB_USERNAME/gists" }}

{{/* 最终结果为 */}}
{{ $gistJ := getJSON "https://api.github.com/users/GITHUB_USERNAME/gists" }}

添加请求头

在末尾添加一个 map 类型的参数

1
{{ $data := getJSON "https://example.org/api" (dict "X-List" (slice "a" "b" "c"))  }}

参考: dict 函数 slice 函数

示例:CSV 文件

layouts/partials/get-csv.html

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
  <table>
    <thead>
      <tr>
      <th>Name</th>
      <th>Position</th>
      <th>Salary</th>
      </tr>
    </thead>
    <tbody>
    {{ $url := "https://example.com/finance/employee-salaries.csv" }}
    {{ $sep := "," }}
    {{ range $i, $r := getCSV $sep $url }}
      <tr>
        <td>{{ index $r 0 }}</td>
        <td>{{ index $r 1 }}</td>
        <td>{{ index $r 2 }}</td>
      </tr>
    {{ end }}
    </tbody>
  </table>

可选模板

可选模板一般为列表和页面模板中较小的上下文感知组件

官方视频介绍(英文):

可选模板按照下列顺序应用:

  1. layouts/partials/*<PARTIALNAME>.html
  2. themes/<THEME>/layouts/partials/*<PARTIALNAME>.html

使用

假设目录关系为:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
layouts/
└── partials/
    ├── footer/
    │   ├── scripts.html
    │   └── site-footer.html
    ├── head/
    │   ├── favicons.html
    │   ├── metadata.html
    │   ├── prerender.html
    │   └── twitter.html
    └── header/
        ├── site-header.html
        └── site-nav.html

那么使用模板的代码为:

1
2
3
{{ partial "header/site-header.html" . }}
{{ partial "footer/scripts.html" . }}
......

说明:结尾的 . 表示将当前上下文传入可选模板

返回值

1
2
{{/* layouts/partials/GetFeatured.html */}}
{{ return first . (where site.RegularPages "Params.featured" true) }}
1
2
3
4
{{/* layouts/index.html */}}
{{ range partial "GetFeatured.html" 5 }}
  [...]
{{ end }}

说明:上面的例子返回前 5 个头部注释 featured=true 的页面

内联

1
2
3
4
5
6
Value: {{ partial "my-inline-partial.html" . }}

{{ define "partials/my-inline-partial.html" }}
{{ $value := 32 }}
{{ return $value }}
{{ end }}

缓存

1
{{ partialCached "footer.html" . .Params.country .Params.province }}

说明:上面的例子缓存 layouts/partials/footer.html 模板,并保存了当前上下文Params.countryParams.province 变量

短代码模板

Hugo 提供短代码模板,在 .md 文件中,使用这些短代码,将会渲染短代码模板在内容中

为保持本地 Markdown 文件的可读性,本文站点不使用短代码,有需求可参考官方文档: 短代码模板

或者官方视频介绍(英文):

本地文件模板

有时候,你可能并不想按照 Hugo 的约定定义和使用模板,这时你可以:

  1. 使用 readDir 函数 加载一个目录下的所有模板
  2. 使用 readFile 函数 加载某个模板

readDir 函数

返回一个 os.FileInfo 数组,它将文件的路径作为单个字符串参数

  • 这个路径可以指向你网站的任何目录(即,在你服务器的文件系统上找到的目录)。

  • 路径是绝对路径还是相对路径都可以

readFile 函数

从磁盘读取文件,并将其转换为字符串,以供其他 Hugo 函数操作或按原样添加

  • readFile 接受 文件/文件路径 作为传递给函数的参数。

  • 在模板中使用 readFile 函数,请确保路径相对于Hugo项目的根目录

  • 在短代码中使用 readFile 函数,请确保路径为绝对路径

404 模板

示例 layouts/404.html

1
2
3
4
5
6
7
{{ define "main"}}
    <main id="main">
      <div>
       <h1 id="title"><a href="{{ "" | relURL }}">Go Home</a></h1>
      </div>
    </main>
{{ end }}

菜单模板

定义了 站点菜单 之后,使用 菜单变量和方法 渲染菜单:

  1. 菜单可以是自动生成、头部注释定义、或者站点配置
  2. 菜单可以扁平化或者层级化
  3. 使用国际化

示例

定义一个菜单模板 layouts/partials/menu.html

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
{{- $page := .page }}
{{- $menuID := .menuID }}

{{- with index site.Menus $menuID }}
  <nav>
    <ul>
      {{- partial "inline/menu/walk.html" (dict "page" $page "menuEntries" .) }}
    </ul>
  </nav>
{{- end }}

{{- define "partials/inline/menu/walk.html" }}
  {{- $page := .page }}
  {{- range .menuEntries }}
    {{- $attrs := dict "href" .URL}}
    {{- if $page.IsMenuCurrent .Menu . }}
      {{- $attrs = merge $attrs (dict "class" "active" "aria-current" "page") }}
    {{- else if $page.HasMenuCurrent .Menu .}}
      {{- $attrs = merge $attrs (dict "class" "ancestor" "aria-current" "true") }}
    {{- end }}
    <li>
      <a
        {{- range $k, $v := $attrs }}
          {{- with $v }}
            {{- printf " %s=%q" $k $v | safeHTMLAttr }}
          {{- end }}
        {{- end -}}
      >{{ or (T .Identifier) .Name | safeHTML }}</a>
      {{- with .Children }}
        <ul>
          {{- partial "inline/menu/walk.html" (dict "page" $page "menuEntries" .) }}
        </ul>
      {{- end }}
    </li>
  {{- end }}
{{- end }}

使用菜单模板 layouts/_default/single.html

1
2
{{ partial "menu.html" (dict "menuID" "main" "page" .) }}
{{ partial "menu.html" (dict "menuID" "footer" "page" .) }}

菜单参数

layouts/partials/menu.html

1
2
3
4
5
{{- range site.Menus.main }}
  <a {{ with .Params.class -}} class="{{ . }}" {{ end -}} href="{{ .URL }}">
    {{ .Name }}
  </a>
{{- end }}

分页模板

分页模板使用两个站点配置:

  1. paginate:分页数量,默认 10
  2. pagiantePath:分页的 HTML 生成路径,默认 path

分页渲染

分页模板的渲染使用 .Paginator 上下文变量,仅支持非单页页面

有两种方式渲染:

  1. .Paginator.Pages
    • {{ range (.Paginator 5).Pages }}
  2. .Paginate.Pages
    • {{ range (.Paginate ( first 50 .Pages.ByTitle )).Pages }}
    • {{ range (.Paginate .RegularPagesRecursive).Pages }}

如果是分组页面:

1
{{ range (.Paginate (.Pages.GroupByDate "2006")).PageGroups  }}

分页生成

.Paginator 包含足够的,分页逻辑处理需要的,分页信息

简单使用

1
2
3
4
{{ template "_internal/pagination.html" . }}
{{ range .Paginator.Pages }}
   {{ .Title }}
{{ end }}

分页过滤

1
2
3
4
5
{{ $paginator := .Paginate (where .Pages "Type" "posts") }}
{{ template "_internal/pagination.html" . }}
{{ range $paginator.Pages }}
   {{ .Title }}
{{ end }}

说明:.Paginate 的返回值是一个 .Paginator 对象

.Paginator 对象属性

属性名 描述
PageNumber 当前页数量
URL 当前页相对 URL
Pages 当前页所有页面
NumberOfElements 当前页所有元素数量
HasPrev 是否有上一页
HasNext 是否有下一页
Next 指向下一页的 Paginator
First 第一页的 Paginator
Last 最后一页的 Paginator
Pagers 所有页面的 Paginator
PageSize 每页大小
TotalPages 页面总数
TotalNumberOfElements 所有页面元素总数

RSS 模板

Hugo提供了RSS 2.0模板,几乎不需要配置,也可以创建自己的RSS模板, RSS模板应用规则

RSS 配置

默认情况下,Hugo 将生成无限数量的 RSS 文件

  • 通过在 站点配置 中设置 rssLimit 的数值,可以限制内置RSS模板中包含的文章数量

  • 如果指定,以下配置的值也将包含在RSS输出中:

1
2
3
4
5
author:
  name: My Name Here
copyright: This work is licensed under a Creative Commons Attribution-ShareAlike 4.0
  International License.
languageCode: en-us

RSS 种子引用

header.html 模板中,可以在 <head> 标签中指定 RSS 种子:

1
2
3
{{ range .AlternativeOutputFormats -}}
    {{ printf `<link rel="%s" type="%s" href="%s" title="%s" />` .Rel .MediaType.Type .Permalink $.Site.Title | safeHTML }}
{{ end -}}

说明:RSS 种子有多种格式

如果只需要 RSS 的链接:

1
2
3
{{ with .OutputFormats.Get "rss" -}}
    {{ printf `<link rel="%s" type="%s" href="%s" title="%s" />` .Rel .MediaType.Type .Permalink $.Site.Title | safeHTML }}
{{ end -}}

生成结果:

1
<link rel="alternate" type="application/rss+xml" href="https://example.com/index.xml" title="Site Title">

Sitemap 模板

Hugo 的内置 Sitemap 模板符合 Sitemaps Protocol v0.9 ,使用内置的 sitemap.xml 模板生成文件

  • 对于单语言项目:在 publishDir 的根目录中生成 sitemap.xml 文件

  • 对于多语言项目:

    1. publishDir 的根目录中生成 sitemap.xml 文件
    2. 同时在每个语言的根目录生成 sitemap.xml 文件,例如 public/en/sitemap.xml

Sitemap 配置

在站点配置 config.yaml中的配置及默认值:

1
2
3
4
sitemap:
  changefreq: "" # 更新频率,支持的值:always, hourly, daily, weekly, monthly, yearly, never
  filename: sitemap.xml # 生成的文件名,默认值:sitemap.xml
  priority: -1 # 一个页面到另一个页面的优先级,取值范围:0-1,默认值:-1(忽略)

在页面头部注释中重写:

1
2
3
4
5
6
---
sitemap:
  changefreq: weekly
  priority: 0.8
title: News
---

重写内置模板

通过在下面两个位置定义模板重写内置的 sitemap.xml

  1. layouts/sitemap.xml
  2. layouts/_default/sitemap.xml

通过在下面两个位置定义模板重写内置的 sitemapindex.xml

  1. layouts/sitemapindex.xml
  2. layouts/_default/sitemapindex.xml

当对非单页页面进行排序时,使用 .Sitemap.ChangeFreq.Sitemap.Priority 分别访问页面的更新频率和优先级

禁用 Sitemap 生成

1
2
3
# config.yaml
disableKinds: # 支持的值:"page", "home", "section", "taxonomy", "term", "RSS", "sitemap", "robotsTXT", "404"
- sitemap

Robots.txt

搜索引擎通过该文件,可允许进行站点所有内容爬取,进行搜索引擎优化

生成 robots.txt 文件与其他模板的生成一样,通过站点配置 enableRobotsTXT=true 开启

内置模板

Hugo 内置有 robots.txt,只有以下内容

1
User-agent: *

模板应用规则

  1. /layouts/robots.txt
  2. /themes/<THEME>/layouts/robots.txt

示例:不爬取链接

1
2
3
4
User-agent: *
{{ range .Pages }}
Disallow: {{ .RelPermalink }}
{{ end }}

内置模板

Hugo 附带了一组样板模板,涵盖了静态网站最常见的用例

虽然下面的内部模板的调用类似于 可选模板 ,但它们不遵循可选模板的应用规则

Google Analytics

这部分内容用于 Google 对站点进行内容分析,利于 Google 搜索优化

支持谷歌 Analytics,支持 Google Analytics 4 和谷歌通用分析,下面是配置开启和模板使用:

Google Analytics 4 (gtag.js)

1
googleAnalytics: G-MEASUREMENT_ID

谷歌通用分析(analytics.js)

1
googleAnalytics: UA-PROPERTY_ID

使用模板

1
2
3
4
{{/* 谷歌通用分析 */}}
{{ template "_internal/google_analytics_async.html" . }}
{{/* Google Analytics 4 */}}
{{ template "_internal/google_analytics.html" . }}

注:如果要重写内置模板,使用 .Site.Config.Services.GoogleAnalytics.ID 访问配置的 ID

Disqus

这个内置模板用于站点评论,disqusShortname 的值来自 Disqus 官网注册的免费服务

站点配置中配置开启

1
disqusShortname: your-disqus-shortname # .Site.DisqusShortname 变量可以访问到这个配置

对大多数站点来说,上面的配置已经足够,但是仍然可以在页面头部注释设置以下内容:

  • disqus_identifier
  • disqus_title
  • disqus_url

使用内置 Disqus 模板

在需要出现评论的地方,在模板文件中添加下面的代码

1
{{ template "_internal/disqus.html" . }}

区分开发与生产环境

可以注意到,当在本地主机上运行Hugo web服务器(即通过Hugo server)时

启用 Disqus 会导致在相关的Disqus帐户上创建不必要的评论内容

可以通过创建 layouts/partials/disqus.html 扩展评论功能的加载:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
<div id="disqus_thread"></div>
<script type="text/javascript">

(function() {
    // Don't ever inject Disqus on localhost--it creates unwanted
    // discussions from 'localhost:1313' on your Disqus account...
    if (window.location.hostname == "localhost")
        return;

    var dsq = document.createElement('script'); dsq.type = 'text/javascript'; dsq.async = true;
    var disqus_shortname = '{{ .Site.DisqusShortname }}';
    dsq.src = '//' + disqus_shortname + '.disqus.com/embed.js';
    (document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(dsq);
})();
</script>
<noscript>Please enable JavaScript to view the <a href="https://disqus.com/?ref_noscript">comments powered by Disqus.</a></noscript>
<a href="https://disqus.com/" class="dsq-brlink">comments powered by <span class="logo-disqus">Disqus</span></a>

然后加载评论:

1
{{ partial "disqus.html" . }}

Open Graph

这个内置模板遵从 Open Graph协议

作用是使页面成为社交图中的丰富对象的元数据,这种格式被用于 Facebook 和其他一些网站

内置 Open Graph 模板的页面元数据生成规则

  1. titledescription 作为标题和描述的元数据
  2. images 的前6个图像作为图像元数据,如果未定义,在 页面绑定资源 中匹配含 feature cover thumbnail 名称的图片

例如,有下面的配置:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
# config.yaml
params:
  description: Text about my cool site
  images:
  - site-feature-image.jpg
  title: My cool site
taxonomies:
  series: series
  
# content/blog/my-post.md 的头部注释
audio: []
date: "2006-01-02"
description: Text about this post
images:
- post-cover.png
series: []
tags: []
title: Post title
videos: []

其他可选的元数据设置:

  1. date publishDate lastmod 作为日期元数据
  2. audio videos 作为音视频元数据,同 images 获取前6个
  3. tags 作为标签元数据,同 images 获取前6个
  4. series 指定系列元数据

使用 Open Graph 内置模板

1
{{ template "_internal/opengraph.html" . }}

Twitter Cards

用于在页面中生成富媒体内容,当 Twitter 引用站点的页面时,将会显示媒体丰富的链接卡片

例如有下面的配置:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# config.yaml
params:
  description: Text about my cool site
  images:
  - site-feature-image.jpg
  
# content/blog/my-post.md 的头部注释
description: Text about this post
images:
- post-cover.png
title: Post title
  • titile 生成链接标题
  • description 生成描述,如果未指定使用 summary
  • images 生成封面图片:
    1. 如果页面头部注释未指定,首先在 页面绑定资源 中匹配含 feature cover thumbnail 名称的图片
    2. 如果未匹配到,匹配 config.yaml 中配置的图片
    3. 如果都没找到,生成无图片的链接卡片,图片内容 summary_large_image 替代

站点配置:

1
2
social:
  twitter: GoHugoIO # .Site.Social.twitter 可访问这个配置

生成页面将会添加 meta

1
<meta name="twitter:site" content="@GoHugoIO"/>

使用内置模板:

1
{{ template "_internal/twitter_cards.html" . }}

模板 Debugging

使用下面的内置函数输出变量的值:

1
2
3
4
5
6
7
8
9
{{ printf "%#v" $.Site }}

{{ printf "%#v" .Permalink }}

{{ printf "%#v" . }}

{{ range .Pages }}
    {{ printf "%#v" . }}
{{ end }}

注:也可以使用 warnf 替代 printf

内置函数

.GetPage

.AddDate YEARS MONTHS DAYS:增加时间

1
2
3
4
5
6
7
8
9
{{ $d := "2023-01-31" | time.AsTime }}
{{ $d.AddDate 0 1 0 | time.Format "2006-01-02" }} --> 2023-03-03

{{ $d := "2024-01-31" | time.AsTime }}
{{ $d.AddDate 0 1 0 | time.Format "2006-01-02" }} --> 2024-03-02

{{ $d := "2024-02-29" | time.AsTime }}
{{ $d.AddDate 1 0 0 | time.Format "2006-01-02" }} --> 2025-03-01
See Also

说明:接受三个参数,分别为

  • 当是月份时,增加的天数为当月最大天数

.Format

.Format FORMAT:格式化时间

支持头部注释的这三个字段:date publishDate lastmod

1
2
{{/* 假设 PublishDate 为 2017年3月2日 */}}
{{ .PublishDate.Format "2006-01-02" }} =>  2017-03-02

注: 更多内容参考官方文档

.Get

.Get INDEX.Get KEY:获取短代码入参

1
{{ $quality := default "100" (.Get 0) }}

说明:上面的代码获取短代码第一个参数

.GetPage

.GetPage PATH:获取指定目录下的页面

1
2
3
4
5
6
7
{{ with .Site.GetPage "/blog" }}{{ .Title }}{{ end }}

{{ with .Site.GetPage "/blog/my-post.md" }}{{ .Title }}{{ end }}

{{ with .Site.GetPage "/blog" }}
{{ with .GetPage "my-post.md" }}{{ .Title }}{{ end }}
{{ end }}

注:在 Hugo v0.45 之前,需要指定页面 Kind

例如:{{ .Site.GetPage "section" "blog" }}

示例:标签链接

1
2
3
4
5
6
<ul class="most-popular-tags">
{{ $t := .Site.GetPage "/tags" }}
{{ range first 2 $t.Data.Terms.ByCount }}
    <li>{{ . }}</li>
{{ end }}
</ul>

.HasMenuCurrent

PAGE.HasMenuCurrent MENU MENUENTRY:返回布尔值,在 菜单模板 中查看示例

.IsMenuCurrent

PAGE.IsMenuCurrent MENU MENUENTRY:返回布尔值,在 菜单模板 中查看示例

.Param

.Param KEY:返回站点配置 params 或页面头部注释的某个字段对应的值

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# config.yaml
params:
  foo: true
  
# content/about.md
---
date: "2023-01-01"
foo: false
draft: false
title: About
---
1
2
{{/* layouts/_default/single.html */}}
{{ or .Params.foo $.Site.Params.foo }}

.Render

.Render LAYOUT:渲染一个视图模板,在 视图模板 查看示例

.RenderString

.RenderString MARKUP:将 Markdown 标记语言转化为 HTML 字符串

1
2
3
4
5
{{ $optBlock := dict "display" "block" }}
{{ $optOrg := dict "markup" "org" }}
{{ "**Bold Markdown**" | $p.RenderString }}
{{  "**Bold Block Markdown**" | $p.RenderString  $optBlock }}
{{  "/italic org mode/" | $p.RenderString  $optOrg }}

.Scratch

Scratch是Hugo的一个特性,旨在方便地在Go模板世界中操作数据

它是一个 PageShortcode 方法,结果数据将被附加到给定的上下文

或者它可以作为存储在变量中的唯一实例存在

使用方式

Hugo v0.43+ 有两种使用方式

1. Page/Shortcode 中使用

1
2
3
4
{{ .Scratch.Set "greeting" "bonjour" }}
{{ range .Pages }}
  {{ .Scratch.Set "greeting" (print "bonjour" .Title) }}
{{ end }}

2. newScratch

1
2
{{ $data := newScratch }}
{{ $data.Set "greeting" "hola" }}

实例方法

.Set

1
{{ $scratch.Set "greeting" "Hello" }}

.Get

1
2
3
{{ $scratch.Set "greeting" "Hello" }}
----
{{ $scratch.Get "greeting" }} > Hello

.Add

字符串:

1
2
3
4
{{ $scratch.Add "greetings" "Hello" }}
{{ $scratch.Add "greetings" "Welcome" }}
----
{{ $scratch.Get "greetings" }} > HelloWelcome

数值:

1
2
3
4
{{ $scratch.Add "total" 3 }}
{{ $scratch.Add "total" 7 }}
----
{{ $scratch.Get "total" }} > 10

切片:

1
2
3
4
{{ $scratch.Add "greetings" (slice "Hello") }}
{{ $scratch.Add "greetings" (slice "Welcome" "Cheers") }}
----
{{ $scratch.Get "greetings" }} > []interface {}{"Hello", "Welcome", "Cheers"}

.SetInMap

1
2
3
4
{{ $scratch.SetInMap "greetings" "english" "Hello" }}
{{ $scratch.SetInMap "greetings" "french" "Bonjour" }}
----
{{ $scratch.Get "greetings" }} > map[french:Bonjour english:Hello]

.DeleteInMap

1
2
3
4
5
6
{{ .Scratch.SetInMap "greetings" "english" "Hello" }}
{{ .Scratch.SetInMap "greetings" "french" "Bonjour" }}
----
{{ .Scratch.DeleteInMap "greetings" "english" }}
----
{{ .Scratch.Get "greetings" }} > map[french:Bonjour]

.GetSortedMapValues

1
2
3
4
{{ $scratch.SetInMap "greetings" "english" "Hello" }}
{{ $scratch.SetInMap "greetings" "french" "Bonjour" }}
----
{{ $scratch.GetSortedMapValues "greetings" }} > [Hello Bonjour]

.Delete

1
2
3
{{ $scratch.Set "greeting" "Hello" }}
----
{{ $scratch.Delete "greeting" }}

.Values

返回原始备份的 map,只能在 newScratch 创建的实例中使用

.Store

Page.Store 方法返回一个 Scratch 来存储和操作数据

但是与 .Scratch 方法相反,这个 Scratch 在服务器重建时不会重置

返回的实例有如下方法:

.Set

1
{{ .Store.Set "greeting" "Hello" }}

.Get

1
2
3
{{ .Store.Set "greeting" "Hello" }}
----
{{ .Store.Get "greeting" }}  Hello

.Add

字符串:

1
2
3
4
{{ .Store.Add "greetings" "Hello" }}
{{ .Store.Add "greetings" "Welcome" }}
----
{{ .Store.Get "greetings" }}  HelloWelcome

数值:

1
2
3
4
{{ .Store.Add "total" 3 }}
{{ .Store.Add "total" 7 }}
----
{{ .Store.Get "total" }}  10

切片:

1
2
3
4
{{ .Store.Add "greetings" (slice "Hello") }}
{{ .Store.Add "greetings" (slice "Welcome" "Cheers") }}
----
{{ .Store.Get "greetings" }}  []interface {}{"Hello", "Welcome", "Cheers"}

.SetInMap

1
2
3
4
{{ .Store.SetInMap "greetings" "english" "Hello" }}
{{ .Store.SetInMap "greetings" "french" "Bonjour" }}
----
{{ .Store.Get "greetings" }}  map[french:Bonjour english:Hello]

.DeleteInMap

1
2
3
4
5
{{ .Store.SetInMap "greetings" "english" "Hello" }}
{{ .Store.SetInMap "greetings" "french" "Bonjour" }}
{{ .Store.DeleteInMap "greetings" "english" }}
----
{{ .Store.Get "greetings" }}  map[french:Bonjour]

.GetSortedMapValues

1
2
3
4
{{ .Store.SetInMap "greetings" "english" "Hello" }}
{{ .Store.SetInMap "greetings" "french" "Bonjour" }}
----
{{ .Store.GetSortedMapValues "greetings" }}  [Hello Bonjour]

.Delete

1
2
3
{{ .Store.Set "greeting" "Hello" }}
----
{{ .Store.Delete "greeting" }}

.Unix

所有时间类型的实例都支持,返回时间戳,有四种精度:

  1. .Unix:秒
  2. .UnixMilli:毫秒
  3. .UnixMicro:微秒
  4. .UnixNamo:纳秒

absLangURL

absLangURL INPUT:根据输入,返回带当前语言的绝对路径

1
2
3
4
5
6
7
8
{{/* baseURL= https://example.org/ */}}
{{ absLangURL "" }}              "https://example.org/en/"
{{ absLangURL "articles" }}      "https://example.org/en/articles"
{{ absLangURL "style.css" }}     "https://example.org/en/style.css"
{{/* baseURL= https://example.org/docs/ */}}
{{ absLangURL "" }}              "https://example.org/docs/en/"
{{ absLangURL "articles" }}      "https://example.org/docs/en/articles"
{{ absLangURL "style.css" }}     "https://example.org/docs/en/style.css"

如果 input 以 / 开头:

1
2
3
4
5
6
7
8
{{/* baseURL= https://example.org/ */}}
{{ absLangURL "/" }}              "https://example.org/en/"
{{ absLangURL "/articles" }}      "https://example.org/en/articles"
{{ absLangURL "/style.css" }}     "https://example.org/en/style.css"
{{/* baseURL= https://example.org/docs/ */}}
{{ absLangURL "/" }}              "https://example.org/en/"
{{ absLangURL "/articles" }}      "https://example.org/en/articles"
{{ absLangURL "/style.css" }}     "https://example.org/en/style.css"

absURL

absLangURL ,区别在于没有 /en

after

after INDEX COLLECTION:用于切片,忽略前几个元素

1
2
3
4
5
{{ $data := slice "one" "two" "three" "four" }}
{{ range after 2 $data }}
    {{ . }}
{{ end }}
 ["three", "four"]

anchorize

anchorize INPUT:处理 Markdown 标题锚点,去除特殊字符,空格转为 -

1
2
3
4
5
6
{{ anchorize "This is a header" }} --> "this-is-a-header"
{{ anchorize "This is also    a header" }} --> "this-is-also----a-header"
{{ anchorize "main.go" }} --> "maingo"
{{ anchorize "Article 123" }} --> "article-123"
{{ anchorize "<- Let's try this, shall we?" }} --> "--lets-try-this-shall-we"
{{ anchorize "Hello, 世界" }} --> "hello-世界"

append

COLLECTION | append VALUE [VALUE]...COLLECTION | append COLLECTION:合并切片

1
2
3
{{ $s := slice "a" "b" "c" }}
{{ $s = $s | append "d" "e" }}
 ["a" "b" "c" "d" "e"]
1
2
3
{{ $s := slice "a" "b" "c" }}
{{ $s = $s | append (slice "d" "e") }}
 ["a" "b" "c" "d" "e"]

apply

apply COLLECTION FUNCTION [PARAM...]:给切片/映射添加一个给定的函数

页面头部注释:

1
2
3
---
names: [ "Derek Perkins", "Joe Bergevin", "Tanner Linsley" ]
---

添加 urlize 方法:

1
{{ apply .Params.names "urlize" "." }}

遍历时使用:

1
2
3
{{ range .Params.names }}
	{{ . | urlize }}
{{ end }}

base64

base64Decode INPUTbase64Encode INPUT

1
2
<p>Hello world = {{ "Hello world" | base64Encode }}</p>
<p>SGVsbG8gd29ybGQ = {{ "SGVsbG8gd29ybGQ=" | base64Decode }}</p>

chomp

chomp INPUTstrings.Chomp INPUT:删除所有尾随换行符

1
{{chomp "<p>Blockhead</p>\n"}}  "<p>Blockhead</p>"

complement

complement COLLECTION [COLLECTION]...collections.Complement COLLECTION [COLLECTION]...:返回不属于前面集合的元素

1
2
3
4
5
{{ $c1 := slice 3 }}
{{ $c2 := slice 4 5  }}
{{ $c3 := slice 1 2 3 4 5 }}

{{ complement $c1 $c2 $c3 }}  [1 2]

cond

cond CONTROL VAR1 VAR2:三元运算符

1
{{ cond (eq (len $geese) 1) "goose" "geese" }}

countrunes

countrunes INPUTstrings.CountRunes INPUT:返回字符串 rune 类型切片

1
2
{{ "Hello, 世界" | countrunes }}
{{/* 输出 8 长度的 rune 切片 */}}

countwords

countwords INPUT:返回字符串单词长度

1
{{ "Hugo is a static site generator." | countwords }}  6

crypto.FNV32a

crypto.FNV32a STRING:返回给定字符串的 32 位 FNV 值

1
{{ crypto.FNV32a "Hello world" }}  1498229191

default

default DEFAULT INPUT:如果第二个值为 nil,返回第一个值

1
2
{{ index .Params "font" | default "Roboto" }}
{{ default "Roboto" (index .Params "font") }}

delimit

delimit COLLECTION DELIMIT LAST:循环遍历任何数组、切片或映射,并返回由分隔符分隔的所有值组成的字符串

某个页面头部注释为:

1
2
3
4
---
title: I love Delimit
tags: [ "tag1", "tag2", "tag3" ]
---

那么:

1
2
Tags: {{ delimit .Params.tags ", " }}  Tags: tag1, tag2, tag3
Tags: {{ delimit .Params.tags ", " ", and " }}  Tags: tag1, tag2, and tag3

dict

dict KEY VALUE [KEY VALUE]...:定义一个 map

duration

duration TIME_UNIT DURATION_NUMBER:返回 time.Duration 结构体

1
2
{{ printf "There are %.0f seconds in one day." (duration "hour" 24).Seconds }}
<!-- Output: There are 86400 seconds in one day. -->

支持的单位:

时间周期 支持单位
hours hour h
minutes minute m
seconds second s
milliseconds millisecond ms
microseconds microsecond us µs
nanoseconds nanosecond ns

echoParam

echoParam DICTIONARY KEY:输出映射的某个字段值

emojify

emojify INPUT:通过 Emoji 表情处理器运行一个字符串

例如: I :heart: Hugo! => I ❤️ Hugo!

eq

eq ARG1 ARG2:判断两个值是否相等

errorf & warnf

errorf FORMAT INPUTwarnf FORMAT INPUT:输出错误/警告日志

1
2
{{ errorf "Failed to handle page %q" .Path }}
{{ warnf "You should update the shortcodes in %q" .Path }}

fileExists

os.FileExists PATHfileExists PATH:判断目录/文件是否存在

假设目录关系为:

1
2
3
4
5
6
content/
├── about.md
├── contact.md
└── news/
    ├── article-1.md
    └── article-2.md
1
2
3
4
5
6
7
{{ os.FileExists "content" }} --> true
{{ os.FileExists "content/news" }} --> true
{{ os.FileExists "content/news/article-1" }} --> false
{{ os.FileExists "content/news/article-1.md" }} --> true
{{ os.FileExists "news" }} --> true
{{ os.FileExists "news/article-1" }} --> false
{{ os.FileExists "news/article-1.md" }} --> true

findRE

findRE PATTERN INPUT [LIMIT]strings.FindRE PATTERN INPUT [LIMIT]:正则匹配字符串,返回匹配的字符串切片

1
2
{{ findRE `(?s)<h2.*?>.*?</h2>` .Content }}
{{ findRE `(?s)<h2.*?>.*?</h2>` .Content 1 }}

first

first LIMIT COLLECTION:返回指定数量的数组

基本使用

1
2
3
{{ range first 10 .Pages }}
    {{ .Render "summary" }}
{{ end }

结合 where 使用

1
2
3
{{ range first 5 (where site.RegularPages "Type" "in" site.Params.mainSections).ByTitle }}
   {{ .Content }}
{{ end }}

float

float INPUT:通过给定的值创建一个浮点数

1
{{ float "1.23" }}  1.23

ge

ge ARG1 ARG2:返回 ARG1 >= ARG2 的布尔值

getenv

os.Getenv VARIABLEgetenv VARIABLE:返回环境变量

1
2
{{ os.Getenv "HOME" }} --> /home/victor
{{ os.Getenv "USER" }} --> victor

group

PAGES | group KEY:页面集合分组

1
2
{{ $new := .Site.RegularPages | first 10 | group "New" }}
{{ $old := .Site.RegularPages | last 10 | group "Old" }}

gt

gt ARG1 ARG2:返回 ARG1 > ARG2 的布尔值

hasPrefix

hasPrefix STRING PREFIX:判断字符串前缀

1
{{ hasPrefix "Hugo" "Hu" }}  true

highlight

transform.Highlight INPUT LANG [OPTIONS]highlight INPUT LANG [OPTIONS]:渲染高亮代码块

参考语法高亮

hmac

crypto.HMAC HASH_TYPE KEY MESSAGE [ENCODING]hmac HASH_TYPE KEY MESSAGE [ENCODING]:使用密钥对消息进行签名加密

1
2
3
4
5
6
7
8
{{ hmac "sha256" "Secret key" "Secret message" }}
5cceb491f45f8b154e20f3b0a30ed3a6ff3027d373f85c78ffe8983180b03c84

{{ hmac "sha256" "Secret key" "Secret message" "hex" }}
5cceb491f45f8b154e20f3b0a30ed3a6ff3027d373f85c78ffe8983180b03c84

{{ hmac "sha256" "Secret key" "Secret message" "binary" | base64Encode }}
XM60kfRfixVOIPOwow7Tpv8wJ9Nz+Fx4/+iYMYCwPIQ=

htmlEscape

htmlEscape INPUT:返回带有转义字符的 HTML 字符串

1
{{ htmlEscape "Hugo & Caddy > WordPress & Apache" }}  "Hugo &amp; Caddy &gt; WordPress &amp; Apache"

htmlUnescape

htmlUnescape INPUT:与 htmlEscape 相反

1
{{ htmlUnescape "Hugo &amp; Caddy &gt; WordPress &amp; Apache" }}  "Hugo & Caddy > WordPress & Apache"

hugo

访问 Hugo 关系数据, 参考官方文档

humanize

humanize INPUT:返回人类易读的字符串

1
2
3
4
{{humanize "my-first-post"}}  "My first post"
{{humanize "myCamelPost"}}  "My camel post"
{{humanize "52"}}  "52nd"
{{humanize 103}}  "103rd"

i18n

i18n KEYT KEYlang.Translate KEY:返回国际化配置的值

参考国际化

images

提供图像过滤和一些关系处理函数:

1
2
{{ $logoFilter := (images.Overlay $logo 50 50 ) }} // 左上角起点选取50x50的图像区域
{{ $img := $img | images.Filter $logoFilter }}

添加文字:

1
2
3
4
5
6
7
8
{{ $img := resources.Get "/images/background.png"}}
{{ $img = $img.Filter (images.Text "Hugo rocks!" (dict
    "color" "#ffffff"
    "size" 60
    "linespacing" 2
    "x" 10
    "y" 20
))}}

更多关系函数参考官方文档

in

in SET ITEM:判断元素是否在切片中

index

index COLLECTION INDEXES:取出切片/映射指定的元素/字段值

1
2
3
4
5
6
7
{{ $map := dict "a" 100 "b" 200 "c" (slice 10 20 30) }}
{{ index $map "c" 1 }} => 20
{{ $map := dict "a" 100 "b" 200 "c" (dict "d" 10 "e" 20) }}
{{ index $map "c" "e" }} => 20
// 或者
{{ $slice := slice "c" "e" }}
{{ index $map $slice }} => 20

int

int INPUT:字符串转整型

intersect

intersect SET1 SET2:两个切片取交集,返回 SET1 中的元素

isset

isset COLLECTION INDEXisset COLLECTION KEY:切片/映射是否有指定的元素/字段

jsonify

jsonify INPUTjsonify OPTIONS INPUT:JSON 序列化

1
2
3
{{ dict "title" .Title "content" .Plain | jsonify }}
{{ dict "title" .Title "content" .Plain | jsonify (dict "indent" "  ") }}
{{ dict "title" .Title "content" .Plain | jsonify (dict "prefix" " " "indent" "  ") }}

可选参数,接受一个 map,支持的 Key:

Key 默认值 描述
indent "" 使用缩进
prefix "" 使用前缀
noHTMLEscape false 禁用字符串转义

lang

提供国际化处理的一些函数

FormatAccounting

lang.FormatAccounting PRECISION, CURRENCY, NUMBER:返回给定数字的,指定国家的,指定精度的,货币表示

1
{{ 512.5032 | lang.FormatAccounting 2 "NOK" }} ---> NOK512.50

FormatCurrency

lang.FormatCurrency PRECISION, CURRENCY, NUMBER:同 FormatAccounting,区别在于以货币符号表示

1
{{ 512.5032 | lang.FormatCurrency 2 "USD" }} ---> $512.50

FormatNumber

lang.FormatNumber PRECISION, NUMBER:处理数值精度

1
{{ 512.5032 | lang.FormatNumber 2 }} ---> 512.50

FormatNumberCustom

lang.FormatNumberCustom PRECISION, NUMBER, OPTIONS:同 FormatNumber,提供可选项含更丰富的功能

1
2
3
4
5
{{ lang.FormatNumberCustom 2 12345.6789 }} ---> 12,345.68
{{ lang.FormatNumberCustom 2 12345.6789 "- , ." }} ---> 12.345,68
{{ lang.FormatNumberCustom 6 -12345.6789 "- ." }} ---> -12345.678900
{{ lang.FormatNumberCustom 0 -12345.6789 "- . ," }} ---> -12,346
{{ -98765.4321 | lang.FormatNumberCustom 2 }} ---> -98,765.43

FormatPercent

lang.FormatPercent PRECISION, NUMBER:数值加 %

1
{{ 512.5032 | lang.FormatPercent 2 }} ---> 512.50%

Translate

lang.Translate ID, ARGS:获取国际化配置,同 i18n 和 T

Merge

lang.Merge FROM TO:合并来自其他语言的缺失翻译

1
{{ $pages := .Site.RegularPages | lang.Merge $frSite.RegularPages | lang.Merge $enSite.RegularPages }}

last

last INDEX COLLECTION:返回切片中倒数 INDEX 个元素之后的所有元素在一个数组中

1
2
3
{{ range last 10 .Pages }}
    {{ .Render "summary" }}
{{ end }}

le

le ARG1 ARG2:返回 ARG1 <= ARG2 的布尔值

len

len INPUT:返回字符串、数组/切片、映射、数组指针、信道长度

lower

len INPUTstrings.ToLower INPUT:字符串转小写

1
2
{{ lower "BatMan" }}  "batman"
{{ "BatMan" | lower }}  "batman"

lt

lt ARG1 ARG2:返回 ARG1 < ARG2 的布尔值

markdownify

markdownify INPUT:渲染 Markdown 字符串为 HTML

1
{{ .Title | markdownify }}

Math

用于数值计算的一些函数

函数 描述 示例
add 累加,如果一个为浮点数,结果为浮点数 {{ add 12 3 2 }}17
sub 累减,如果一个为浮点数,结果为浮点数 {{ sub 12 3 2 }}7
mul 累乘,如果一个为浮点数,结果为浮点数 {{ mul 12 3 2 }}72
div 累除,如果一个为浮点数,结果为浮点数 {{ div 12 3 2 }}2
mod 求余 {{ mod 15 3 }}0z
modBool 是否整除 {{ modBool 15 3 }}true
math.Ceil 向上取整 {{ math.Ceil 2.1 }}3
math.Floor 向下取整 {{ math.Floor 1.9 }}1
math.Log 求对数 {{ math.Log 42 }}3.737
math.Max 取最大值 {{ math.Max 12 3 2 }}12
math.Min 取最小值 {{ math.Min 12 3 2 }}2
math.Pow 求幂 {{ math.Pow 2 3 }}8
math.Round 四舍五入取整 {{ math.Round 1.5 }}2
math.Sqrt 平方根 {{ math.Sqrt 81 }}9

md5

md5 INPUT:md5 加密字符串

merge

collections.Merge MAP MAP...merge MAP MAP...:合并映射,相同字段后面的覆盖前面的

1
2
3
4
5
6
7
8
9
{{ $m1 := dict "x" "foo" }}
{{ $m2 := dict "x" "bar" "y" "wibble" }}
{{ $m3 := dict "x" "baz" "y" "wobble" "z" (dict "a" "huey") }}
// example
{{ $merged := merge $m1 $m2 $m3 }}

{{ $merged.x }}   --> baz
{{ $merged.y }}   --> wobble
{{ $merged.z.a }} --> huey

ne

ne ARG1 ARG2:返回 ARG1 != ARG2 的布尔值

now

now:返回当前时间

os.Stat

os.Stat PATH:返回文件目录/文件信息

1
2
3
4
5
6
7
8
{{ $f := os.Stat "README.md" }}
{{ $f.IsDir }}    --> false (bool)
{{ $f.ModTime }}  --> 2021-11-25 10:06:49.315429236 -0800 PST (time.Time)
{{ $f.Name }}     --> README.md (string)
{{ $f.Size }}     --> 241 (int64)

{{ $d := os.Stat "content" }}
{{ $d.IsDir }}    --> true (bool)

partialCached

partialCached LAYOUT INPUT [VARIANT...]:缓存 可选模板 ,避免多个页面重复渲染

path

路由处理的相关函数

Base

path.Base PATH:返回最后一级路由

1
2
3
4
{{ path.Base "a/news.html" }}  "news.html"
{{ path.Base "news.html" }}  "news.html"
{{ path.Base "a/b/c" }}  "c"
{{ path.Base "/x/y/z/" }}  "z"

Dir

path.Dir PATH:返回去除最后一级路由的路由

1
2
3
4
{{ path.Dir "a/news.html" }}  "a"
{{ path.Dir "news.html" }}  "."
{{ path.Dir "a/b/c" }}  "a/b"
{{ path.Dir "/x/y/z" }}  "/x/y"

Ext

path.Ext PATH:返回最后一级路由后缀(如果有)

1
{{ path.Ext "a/b/c/news.html" }}  ".html"

Join

path.Join ELEMENT...:合并多个路由

1
2
3
{{ path.Join "partial" "news.html" }}  "partial/news.html"
{{ path.Join "partial/" "news.html" }}  "partial/news.html"
{{ path.Join "foo/baz" "bar" }}  "foo/baz/bar"

Split

path.Split PATH:通过 / 切割 Base Dir

1
2
3
{{ $dirFile := path.Split "a/news.html" }}  $dirFile.Dir  "a/", $dirFile.File  "news.html"
{{ $dirFile := path.Split "news.html" }}  $dirFile.Dir  "", $dirFile.File  "news.html"
{{ $dirFile := path.Split "a/b/c" }}  $dirFile.Dir  "a/b/", $dirFile.File   "c"

reflect.IsMap

reflect.IsMap INPUT:判断是否是 map

1
2
{{ reflect.IsMap (dict "key" "value") }}  true
{{ reflect.IsMap "yo" }}  false

reflect.IsSlice

reflect.IsMap INPUT:判断是否是 slice 切片

1
2
{{ reflect.IsSlice (slice 1 2 3) }}  true
{{ reflect.IsSlice "yo" }}  false

relLangURL

relLangURL INPUT:INPUT 目录/文件,进行当前语言路径处理后,最终的相对地址

1
2
3
4
5
6
7
8
{{/* baseURL = https://example.org/ */}}
{{ relLangURL "" }}              /en/
{{ relLangURL "articles" }}      /en/articles
{{ relLangURL "style.css" }}     /en/style.css
{{/* baseURL = https://example.org/docs/ */}}
{{ relLangURL "" }}              /docs/en/
{{ relLangURL "articles" }}      /docs/en/articles
{{ relLangURL "style.css" }}     /docs/en/style.css

如果是 / 开头:

1
2
3
4
5
6
7
8
{{/* baseURL = https://example.org/ */}}
{{ relLangURL "/" }}              /en/
{{ relLangURL "/articles" }}      /en/articles
{{ relLangURL "/style.css" }}     /en/style.css
{{/* baseURL = https://example.org/docs/ */}}
{{ relLangURL "/" }}              /en/
{{ relLangURL "/articles" }}      /en/articles
{{ relLangURL "/style.css" }}     /en/style.css

relref

relref . PAGE:返回页面的相对永久链接

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
{{ relref . "about" }}
{{ relref . "about#anchor" }}
{{ relref . "about.md" }}
{{ relref . "about.md#anchor" }}
{{ relref . "#anchor" }}
{{ relref . "/blog/my-post" }}
{{ relref . "/blog/my-post.md" }}

{{ relref . (dict "path" "about.md" "lang" "fr") }}
{{ relref . (dict "path" "about.md" "outputFormat" "rss") }}

relURL

relURL INPUT:同 relLangURL,但是忽略语言处理

1
2
3
4
5
6
7
8
{{/* baseURL = https://example.org/ */}}
{{ relLangURL "" }}              /
{{ relLangURL "articles" }}      /articles
{{ relLangURL "style.css" }}     /style.css
{{/* baseURL = https://example.org/docs/ */}}
{{ relLangURL "" }}              /docs/
{{ relLangURL "articles" }}      /docs/articles
{{ relLangURL "style.css" }}     /docs/style.css

如果是 / 开头:

1
2
3
4
5
6
7
8
{{/* baseURL = https://example.org/ */}}
{{ relLangURL "/" }}              /
{{ relLangURL "/articles" }}      /articles
{{ relLangURL "/style.css" }}     /style.css
{{/* baseURL = https://example.org/docs/ */}}
{{ relLangURL "/" }}              /
{{ relLangURL "/articles" }}      /articles
{{ relLangURL "/style.css" }}     /style.css

replace

replace INPUT OLD NEW [LIMIT]strings.Replace INPUT OLD NEW [LIMIT]:替换字符串中匹配的指定子串

1
2
3
4
`{{ replace "Batman and Robin" "Robin" "Catwoman" }}`
 "Batman and Catwoman"

{{ replace "aabbaabb" "a" "z" 2 }}  "zzbbaabb"

replaceRE

replaceRE PATTERN REPLACEMENT INPUT [LIMIT]strings.ReplaceRE PATTERN REPLACEMENT INPUT [LIMIT]:同 replace,匹配为正则匹配

1
2
3
4
5
6
7
8
{{ $s := "a-b--c---d" }}
{{ replaceRE `(-{2,})` "-" $s }}  a-b-c-d

{{ $s := "a-b--c---d" }}
{{ replaceRE `(-{2,})` "-" $s 1 }}  a-b-c---d

{{ $s := "http://gohugo.io/docs" }}
{{ replaceRE "^https?://([^/]+).*" "$1" $s }}  gohugo.io

safeCSS

safeCSS INPUT:避免 CSS 字符串特殊字符被转义

safeHTML

safeHTML INPUT:避免 HTML 字符串特殊字符被转义

例如,config.yaml

1
2
copyright: © 2015 Jane Doe.  <a href="https://creativecommons.org/licenses/by/4.0/">Some
  rights reserved</a>.

使用 safeHTML 时:

1
2
{{ .Site.Copyright | safeHTML }} 
 © 2015 Jane Doe.  <a href="https://creativecommons.org/licenses/by/4.0/">Some rights reserved</a>.

不使用时:

1
2
{{ .Site.Copyright }} 
 "© 2015 Jane Doe.  &lt;a href=&#34;https://creativecommons.org/licenses by/4.0/&#34;&gt;Some rights reserved&lt;/a&gt;."

safeHTMLAttr

safeHTMLAttr INPUT:同 safeHTML,区别在于该函数用于 HTML 标签属性

例如,config.yaml

1
2
3
4
menu:
  main:
  - name: IRC
    url: "irc://irc.freenode.net/#golang"

如果不使用 safeHTMLAttr

1
2
3
{{ range site.Menus.main }}
  <a href="{{ .URL }}">{{ .Name }}</a>  <a href="#ZgotmplZ">IRC</a>
{{ end }}

正确使用:

1
2
3
{{ range site.Menus.main }}
  <a {{ printf "href=%q" .URL | safeHTMLAttr }}>{{ .Name }}</a>
{{ end }}

safeJS

safeJS INPUT:同 safeCSSsafeHTML,区别在于该函数用于 <script> 标签

safeURL

safeURL INPUT:同 safeHTMLAttr ,区别在于该函数只用于处理 URL

seq

seq LASTseq FIRST LASTseq FIRST INCREMENT LAST:生成等差数列切片

3 → [1 2 3]

1 2 4 → [1 3]

-3 → [-1 -2 -3]

1 4 → [1 2 3 4]

1 -2 → [1 0 -1 -2]

1
2
{{ range after 1 (seq 20)}}
{{ end }}

sha

sha1 INPUTsha256 INPUT:sha1 或 sha256 加密

shuffle

shuffle COLLECTION:切片元素顺序随机洗牌

singularize

singularize INPUT:去除复数形式

1
{{ "cats" | singularize }}  cat

site

site.Site:访问站点上下文

slice

slice ITEM...:生成切片

1
{{ $sliceOfStrings := slice "foo" "bar" "buzz" }}

slicestr

slicestr STRING START [END]strings.SliceString STRING START [END]:剪切字符串子串

1
2
{{slicestr "BatMan" 3}}  Man
{{slicestr "BatMan" 0 3}}  Bat

sort

sort INPUT:对映射、数组、切片进行按字母排序,返回一个切片

split

split STRING DELIM:按指定分隔符,将字符串转为切片

1
2
{{ split "tag1,tag2,tag3" "," }}  ["tag1", "tag2", "tag3"]
{{ split "abc" "" }}  ["a", "b", "c"]

string

字符串相关的所有函数

string INPUT:返回 INPUT 的字符串强制转换

Contains

strings.Contains STRING SUBSTRING: 字符串是否含指定子串

1
2
{{ strings.Contains "Hugo" "go" }}  true
{{ strings.Contains "Hugo" "Go" }}  false

ContainsAny

strings.ContainsAny STRING CHARACTERS:字符串是否含指定子串的任意一个字符

1
2
{{ strings.ContainsAny "Hugo" "gm" }}  true
{{ strings.ContainsAny "Hugo" "Gm" }}  false

Count

strings.Count SUBSTR STRING:字符串指定子串出现的次数

示例 结果
`{{ “aaabaab” strings.Count “a” }}`
`{{ “aaabaab” strings.Count “aa” }}`
`{{ “aaabaab” strings.Count “aaa” }}`
`{{ “aaabaab” strings.Count "" }}`

FirstUpper

strings.FirstUpper STRING:字符串首字母大写

1
{{ strings.FirstUpper "foo" }}  "Foo"

HasSuffix

strings.HasSuffix STRING SUFFIX:字符串是否含指定后缀

1
2
3
{{ $pdfPath := "/path/to/some.pdf" }}
{{ strings.HasSuffix $pdfPath "pdf" }}  true
{{ strings.HasSuffix $pdfPath "txt" }}  false

Repeat

strings.Repeat COUNT INPUT:指定字符串重复多次,拼接返回

1
2
{{ strings.Repeat 3 "yo" }}  "yoyoyo"
{{ "yo" | strings.Repeat 3 }}  "yoyoyo"

RuneCount

strings.RuneCount INPUT:字符串转 Go 的 rune 类型切片

TrimLeft

strings.TrimLeft CUTSET STRING:去除字符串或数值从左往右第一个匹配的指定子串,返回字符串

1
2
{{ strings.TrimLeft "a" "abba" }}  "bba"
{{ strings.TrimLeft 12 1221341221 }}  "341221"

TrimPrefix

strings.TrimPrefix PREFIX STRING:去除字符串匹配的指定前缀,只能对字符串使用

1
2
3
{{ strings.TrimPrefix "a" "aabbaa" }}  "abbaa"
{{ strings.TrimPrefix "aa" "aabbaa" }}  "bbaa"
{{ strings.TrimPrefix "aaa" "aabbaa" }}  "aabbaa"

TrimRight

strings.TrimRightPREFIX STRING:同 TrimLeft,方向相反,从右往左

1
2
{{ strings.TrimRight "a" "abba" }}  "abb"
{{ strings.TrimRight 12 1221341221 }}  "122134"

TrimSuffix

strings.TrimSuffix SUFFIX STRING:同 TrimPrefix,去除匹配的指定后缀

1
2
3
{{ strings.TrimSuffix "a" "aabbaa" }}  "aabba"
{{ strings.TrimSuffix "aa" "aabbaa" }}  "aabb"
{{ strings.TrimSuffix "aaa" "aabbaa" }}  "aabbaa"

substr

substr STRING START [LENGTH]strings.Substr STRING START [LENGTH]:从指定字符的位置提取字符串的部分,并返回指定的字符数

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
{{ substr "abcdef" 0 }}  "abcdef"
{{ substr "abcdef" 1 }}  "bcdef"

{{ substr "abcdef" 0 1 }}  "a"
{{ substr "abcdef" 1 1 }}  "b"

{{ substr "abcdef" 0 -1 }}  "abcde"
{{ substr "abcdef" 1 -1 }}  "bcde"

{{ substr "abcdef" -1 }}  "f"
{{ substr "abcdef" -2 }}  "ef"

{{ substr "abcdef" -1 1 }}  "f"
{{ substr "abcdef" -2 1 }}  "e"

{{ substr "abcdef" -3 -1 }}  "de"
{{ substr "abcdef" -3 -2 }}  "d"

symdiff

symdiff COLLECTIONcollections.SymDiff COLLECTION:返回两个集合非公共元素

1
{{ slice 1 2 3 | symdiff (slice 3 4) }}  [1 2 4]

templates.Exists

templates.Exists PATH:检查相对于 layouts 目录下的路径是否存在模板

1
2
3
4
5
6
{{ $partialPath := printf "headers/%s.html" .Type }}
{{ if templates.Exists ( printf "partials/%s" $partialPath ) }}
  {{ partial $partialPath . }}
{{ else }}
  {{ partial "headers/default.html" . }}
{{ end }}

time

time INPUT [TIMEZONE]:转化一个时间戳或时间字符串为 time.Time 结构体

1
2
3
4
5
6
7
{{ time "2016-05-28" }}  "2016-05-28T00:00:00Z"
{{ (time "2016-05-28").YearDay }}  149
{{ mul 1000 (time "2016-05-28T10:30:00.00+10:00").Unix }}  1464395400000, or Unix time in milliseconds
// 使用时区
{{ time "2020-10-20" }}  2020-10-20 00:00:00 +0000 UTC
{{ time "2020-10-20" "America/Los_Angeles" }}  2020-10-20 00:00:00 -0700 PDT
{{ time "2020-01-20" "America/Los_Angeles" }}  2020-01-20 00:00:00 -0800 PST

Format

time.Format LAYOUT INPUTdateFormat LAYOUT INPUT:结构体格式化为时间字符串

1
{{ time.Format "Monday, Jan 2, 2006" "2015-01-21" }}  "Wednesday, Jan 21, 2015"

ParseDuration

time.ParseDuration DURATION:将给定的周期时间转化为 time.Duration

1
2
{{ printf "There are %.0f seconds in one day." (time.ParseDuration "24h").Seconds }}
<!-- Output: There are 86400 seconds in one day. -->

title

title INPUTstrings.Title INPUT:字符串转大写开头

transform.Unmarshal

unmarshal [OPTIONS]transform.Unmarshal [OPTIONS]:格式化 JSON, TOML, YAML, XML 和 CSV 为数组或映射

trim

trim INPUT CUTSETstrings.Trim INPUT CUTSET:去除字符串开头和结尾匹配的所有字符

1
{{ trim "++Batman--" "+-" }}  "Batman"

truncate

truncate SIZE [ELLIPSIS] INPUTstrings.Truncate SIZE [ELLIPSIS] INPUT:剪切HTML字符串,忽略HTML标签

1
{{ "<em>Keep my HTML</em>" | safeHTML | truncate 10 }}` → <em>Keep my …</em>`

union

union SET1 SET2:返回两个 数组/切片 的并集

uniq

uniq SET:数组/切片 去重

upper

upper INPUTstrings.ToUpper INPUT:全部字符转为大写

1
2
{{ upper "BatMan" }}  "BATMAN"
{{ "BatMan" | upper }}  "BATMAN"

urlize

urlize INPUT:对URL中转义的字符解析,并将空格转换为连字符 -

urlquery

urlquery INPUT [INPUT]...:将URL参数进行字符串转义

1
2
3
4
{{ $u := urlquery "https://" "example.com" | safeURL }}
<a href="https://example.org?url={{ $u }}">Link</a>
// 结果
<a href="https://example.org?url=https%3A%2F%2Fexample.com">Link</a>

urls.Parse

urls.Parse URL:url 转为 URL 结构体

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
{{ $url := "https://example.org:123/foo?a=6&b=7#bar" }}
{{ $u := urls.Parse $url}}

{{ $u.IsAbs }}  true
{{ $u.Scheme }}  https
{{ $u.Host }}  example.org:123
{{ $u.Hostname }}  example.org
{{ $u.RequestURI }}  /foo?a=6&b=7
{{ $u.Path }}  /foo
{{ $u.Query }}  map[a:[6] b:[7]]
{{ $u.Query.a }}  [6]
{{ $u.Query.Get "a" }}  6
{{ $u.Query.Has "b" }}  true
{{ $u.Fragment }}  bar

where

where COLLECTION KEY [OPERATOR] MATCH:数组过滤

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
{{ range where .Pages "Section" "foo" }}
  {{ .Content }}
{{ end }}

{{ range where .Site.Pages "Params.series" "golang" }}
   {{ .Content }}
{{ end }}

{{ range where .Pages "Section" "!=" "foo" }}
   {{ .Content }}
{{ end }}

{ range where .Pages "Section" "!=" "foo" }}
   {{ .Content }}
{{ end }}

关系字符表:

关系字符 表达式描述
= == eq 返回给定字段的值相等的元素
!= <> ne 返回给定字段的值不相等的元素
>= ge 返回给定字段的值大于等于的元素
> gt 返回给定字段的值大于的元素
<= le 返回给定字段的值小于等于的元素
< lt 返回给定字段的值小于的元素
in 返回给定字段的值包含关系的元素
not in 返回给定字段的值非包含关系的元素
intersect 字符串/数值数组:返回元素值与给定值相等的元素

with

with INPUT:同 if 表达式,区别在于忽略表达式的 nil 引用错误

1
2
3
4
5
6
7
{{with .Site.Params.twitteruser}}
    <span class="twitter">
    <a href="https://twitter.com/{{.}}" rel="author">
    <img src="/images/twitter.png" width="48" height="48" title="Twitter: {{.}}"
     alt="Twitter"></a>
    </span>
{{end}}

说明:上面的例子当 .Paramsnil 时,直接返回 false,不会报错

内置变量

站点变量

站点变量列表:

站点变量 描述
.Site.AllPages 所有页面数组,包括所有翻译页面
.Site.BaseURL config.yaml 的 baseURL 配置
.Stie.BuildDrafts 是否打包草稿页面,默认 false
.Stie.Copyright config.yaml 中定义的网站版权的字符串
.Stie.Data 查看数据模板
.Stie.DisqusShortname config.yaml 的 disqusShortname 配置
.Stie.GoogleAnalytics config.yaml 的 googleAnalysis 配置
.Stie.Home 首页的页面变量
.Stie.IsMutiLingual 站点是否多语言
.Stie.IsServer 是否是生产环境
.Stie.Language.Lang 当前国际化语言,如:en
.Stie.Language.LanguageName 当前国际化语言名称,如:English
.Stie.Language.Weight 当前国际化语言顺序
.Stie.Language config.yaml 的 languages.<currentLanguage> 配置
.Stie.LanguageCode .Stie.Language.Lang
.Stie.LanguagePrefix 只有一种语言可用,如:/en/posts/index.md/en 表示URL 语言前缀
.Stie.Languages config.yaml 的 languages 配置
.Stie.LastChange 站点最近的一次更改时间
.Stie.Menus 站点菜单配置
.Stie.Pages 站点当前语言所有页面,时间排序
.Stie.RegularPages 站点当前语言所有 kind=page 的页面
.Stie.Sections content 目录下顶级目录的页面
.Stie.Taxonomies 分类法的页面
.Stie.Title 站点标题
.Stie.Params config.yaml 的 params 配置

页面变量

页面级变量定义在内容文件的头部注释,从内容的文件位置派生,或者从内容主体本身提取

以下是所有页面变量/方法:

页面变量/方法 描述
.Aliases aliases 注释
.Ancestors 页面的祖先,简化面包屑导航实现的复杂性
.BundleType 叶子页面 集合页面
.Content 页面内容
.Data 当前页数据
.Date date 注释
.Description description 注释
.Draft draft 注释,是否是草稿
.ExpireDate expireDate 注释
.File 页面文件系统相关数据, 查看文件变量
.Fragments 页面片段
.FuzzyWordCount 内容的大概字数
.IsHome 是否是首页
.IsNode RegularPages 页面总是为 false
.IsPage RegularPages 页面总是为 true
.IsSection 是否是 content 目录下顶级目录的 index.md_index.md
.IsTranslated 是否是翻译页面
.Keywords keywords 注释
.Kind kind 注释,当前页面的 kind 类型
.Language 当前页面的国际化语言,默认为 .Site.Language.Lang
.Lastmod 最近修改时间,未设置为 date 或
.LinkTitle linkTitle 注释
.Next Hugo 排序的下一个页面
.NextInSection 同一个Section下,Hugo排序的下一个页面
.OutputFormats 当前页面所有输出格式
.Pages 相关页面的集合,在 RegularPages 页面中为 nil
.Permalink 当前页面绝对URL, Permalinks
.Plain 当前页脱离HTML标签,纯字符串的呈现内容
.PlainWords .Plain 的字符串切片
.Prev Hugo 排序的上一个页面
.PrevInSecion 同一个Section下,Hugo排序的上一个页面
.PublishDate publishDate 注释
.RawContent 去除头部注释的,原始 Markdown 内容
.ReadingTime 当前页大概阅读时间
.Resources resources 注释,定义当前页绑定静态资源
.Ref 返回 content/**/*.md Permalinks
.RelPermalink 当前页面相对URL, RelPermalinks
.RefRef 返回 content/**/*.md RelPermalinks
.Site 站点变量
.Sites 多语言所有站点
.Sites.First 多语言排序中第一个站点
.Summary summary 注释,页面摘要
.TableOfContents 页面目录
.Title 页面标题
.Translations 当前页面的翻译列表, 国际化
.TranslationKey 用于映射当前页面的语言翻译的 key
.Truncated 布尔值,true 表示手动 summary 摘要截断;false表示自动,默认前70字
.Type 当前页所属 Section
.Weight 当前页优先级
.WordCount 当前页字数

Section变量和方法

变量/方法 描述
.CurrentSection 当前 Section
.FirstSection 当前 Section 目录下第一个目录
.InSection $anotherPage 给定页是否在当前 Section
.IsAncestor $anotherPage 当前 Section 是否是给定页的祖先
.IsDescendant $anotherPage 当前 Section 是否是给定页的后代
.Parent 当前 Section 的父级
.Section 当前 Section 所属 Section
.Sections 当前 Section 下所有 Section

页面片段

.Fragments 返回的页面片段,即含有 id 属性的HTML标签,含有以下属性:

.Headings

当前页的递归标题列表,可以用来生成目录

.Identifiers

当前页的已排序的 id 列表,可用于检查页面是否包含特定 id 或页面是否包含重复 id:

1
2
3
4
5
6
7
{{ if .Fragments.Identifiers.Contains "my-identifier" }}
    <p>Page contains identifier "my-identifier"</p>
{{ end }}

{{ if gt (.Fragments.Identifiers.Count "my-identifier")  1 }}
    <p>Page contains duplicate "my-idenfifier" fragments</p>
{{ end }}

.HeadingsMap

保存当前页的标题映射,可用于从特定标题开始目录

短代码变量

一般在 短代码模板 中使用的变量/方法:

短代码变量/方法 描述
.Name 短代码名称
.Ordinal 短代码在页面中的序数,从 0 开始
.Page 所属页面
.Parent 嵌套短代码的父级
.Position 短代码在页面中的文件名和位置,消耗性能
.IsNameParams 布尔值,当短代码使用命名参数而不是位置参数时返回 true
.Inner 开始和结束短码标记之间的内容
.Scratch 返回一个可写的 Scratch 来存储和操作,附加到短代码上下文的数据
.InnerDeindent Hugo v0.100.0+ 开始和结束短码标记之间的内容,去除缩进

分类法变量

分类模板变量

一般在 分类法模板 中使用的变量/方法:

分类模板变量/方法 描述
.Data.Singular 分类单数名称,如 tags => tag
.Data.Plural 分类复数名称,如 tags => tags
.Data.Terms 所有分类标签
.Data.Terms.Alphabetical 所有分类标签,字母排序
.Data.Terms.ByCount 所有分类标签,页面数量排序
.Data.Pages 当前分类所有页面

分类标签变量

例如 tags 分类下含有 React Hugo 分类标签,那么这些标签含有以下变量/方法:

分类标签变量/方法 描述
.Data.Singular 分类标签单数名称,如 Reacts => React
.Data.Plural 分类标签复数名称,如 Reacts => Reacts
.Data.Pages 属于该分类标签的所有页面
.Data.Term 当前分类标签

文件变量

.File 变量中访问,页面的文件系统相关数据:

文件变量/方法 描述
.File.Path 文件路径,含文件名,如:posts/foo.en.md
.File.LogicalName 文件名,如:foo.en.md
.File.TranslationBaseName 没有扩展名或可选语言标识符的文件名,如:foo.en.md => post
.File.ContentBaseName 如果文件是一个叶子页面,是一个 .TranslationBaseName 或包含文件夹的名称
.File.BaseFileName 文件名,不含扩展名
.File.Ext 文件扩展名
.File.Lang 文件的解析语言
.File.Dir 页面相对目录,如:content/posts/dir1/dir2 => posts/dir1/dir2
.File.UniqueID 文件的 MD5 hash 值

菜单变量

一般在 菜单模板 中使用的变量/方法:

菜单变量/方法 描述
.Children 子菜单集合
.Identifier 菜单的 identifier 属性值,没有则取所在目录名
.KeyName 菜单的 identifier 属性值,没有则取 name 的值
.Menu 包含菜单项的菜单标识符,string
.Name 菜单的 name 属性值,没有则同 .Title 的值
.Page Hugo 规则解析生成的菜单页
.Params 菜单的 params 属性值
.Parent 菜单的 parent 属性值
.Post 菜单的 post 属性值
.Pre 菜单的 pre 属性值
.Title 菜单的 title 属性值,,优先级 .LinkTile > .Title
.URL 菜单的相对URL
.Weight 菜单优先级,自动根据菜单标题字母排序,优先级 .LinkTile > .Title
.HasChildren 是否含子菜单
.IsEqual 是否是同个菜单
.IsSameResource 多个菜单是否来自同一个父菜单
.Page.HasMenuCurrent 当前菜单是否含有父菜单
.Page.IsMenuCurrent 当前菜单是否是活动菜单

Git变量

当前页面文件的最近一次Git提交信息,需要站点配置 enableGitInfo=true 开启

通过 .GitInfo 对象使用,对象含以下变量

Git变量/方法 描述
.AbbreviatedHash 提交Hash地址的缩写,如:866cbcc
.AuthorName 提交作者
.AuthorEmail 提交作者的邮箱
.AuthorDate 提交作者的时间
.Hash 提交Hash地址的全称
.Subject 提交注释,如:fix: bug fixed

GitInfo 另外还有 .Lastmod 变量,等同于 .GitInfo.AuthorDate

Sitemap 变量

站点地图是一个页面,对于所有的 页面变量 ,[Sitemap 模板](#Sitemap 模板) 中都能使用

以下是额外的变量:

Sitemap 变量/方法 描述
.Sitemap.ChangeFreq 页面更新频率
.Sitemap.Priority Sitemap 页面优先级
.Sitemap.Filename Sitemap 文件名

资源处理

这里的资源处理与 内容管理的页面资源 部分存在不同

这里介绍的资源处理是指的处理站点的资源,如 JS/TS Sass

Hugo Pipes

Hugo Pipes 是 Hugo 的站点资源处理函数集

所有的站点资源默认在 /assetsthemes/themeName/assets 目录下

使用 resources 命名空间使用访问/处理站点资源,如:resources.Get

resources.Get

resources.Get INPUT :获取 /assets 目录下资源并返回 Resource 对象

1
{{ $local := resources.Get "sass/main.scss" }}

resources.GetRemote

resources.GetRemote INPUT:获取远程资源并返回 Resource 对象

1
{{ $remote := resources.GetRemote "https://www.example.com/styles.scss" }}

resources.GetMatch

resources.GetMatch INPUT:获取 /assets 目录下匹配的第一个资源,并返回 Resource 对象

resources.Match

resources.Match INPUT:获取 /assets 目录下匹配的所有资源,并返回 Resource 对象数组

支持通配符 *,如:images/*.png images/**.png **.png

注意:不要直接使用 / 作为输入,如果使用 / 开头,则是项目根目录开始匹配而不是 assets/

resources.Copy

resources.Copy INPUT VAR:拷贝一个资源到变量中

1
2
{{ $resized := $image.Resize "400x400" |  resources.Copy "images/mynewname.jpg" }}
<img src="{{ $resized.RelPermalink }}">

资源元数据

当获取资源时,可以给资源添加一些元数据:

1
2
3
4
5
{{ $postResponse := resources.GetRemote "https://example.org/api"  
	(dict "method" "post" 
     "body" `{"complete": true}` 
     "headers" (dict "Content-Type" "application/json")
)}

错误处理

不一定能找到匹配的资源,你可能需要进行一些错误处理:

1
2
3
4
5
6
7
{{ with resources.GetRemote "https://gohugo.io/images/gohugoio-card-1.png" }}
  {{ with .Err }}
    {{ warnf "%s" . }}
  {{ else }}
    <img src="{{ .RelPermalink }}" width="{{ .Width }}" height="{{ .Height }}" alt="">
  {{ end }}
{{ end }}

Go Pipes

为了提高可读性,本文档的 Hugo Pipes 示例将使用Go Pipes编写:

1
2
3
4
5
{{ $style := resources.Get "sass/main.scss" | resources.ToCSS | resources.Minify | resources.Fingerprint }}
<link rel="stylesheet" href="{{ $style.Permalink }}" />
 // 使用别名
{{ $style := resources.Get "sass/main.scss" | toCSS | minify | fingerprint }}
<link rel="stylesheet" href="{{ $style.Permalink }}">

说明:上述 ToCss MinifyFingerprint 在后文中详细介绍

ToCSS

Sass 转为 CSS

语法:resources.ToCSS RESOURCE [OPTIONS]toCSS RESOURCE [OPTIONS]

1
2
3
4
5
{{ $sass := resources.Get "sass/main.scss" }}
{{ $style := $sass | resources.ToCSS }}
// 带 options
{{ $options := (dict "targetPath" "style.css" "outputStyle" "compressed" "enableSourceMap" (not hugo.IsProduction) "includePaths" (slice "node_modules/myscss")) }}
{{ $style := resources.Get "sass/main.scss" | resources.ToCSS $options }}

下面是所有 Options 列表:

可选项 类型 描述
transpiler “libsass” | “dartsass” 转换器,默认 libsass。dartsass 需安装 Embedded Dart Sass
targetPath string 如果没有设置,转换后的资源的目标路径将是文件的原始路径,其扩展名为 .css
vars map 填充键值对进 hugo:vars 命名空间,使用:@use "hugo:vars" as v@import "hugo:vars"Hugo v0.109.0+ 特性
outputStyle “nested” | “expanded” | “compact” | “compressed” 输出格式,libsass 默认 “nested”,dartsass 默认 “expanded”,且只支持 “expanded” 和 “compressed”
precision int 仅 dartsass 支持,浮点数精度
enableSourceMap bool 是否生成 source map 文件
sourceMapIncludSources bool 仅 dartsass 支持,是否将 source 嵌入 source map。Hugo v0.108.0+ 特性
includePaths string slice 其他(除 assets/)Sass 目录,必须相对于项目目录

PostProcess

允许将资源转换延迟到构建之后,通常用于转换链中的一个或多个步骤依赖于构建的结果

目前有两个限制:

  1. 只能用于 *.html 模板
  2. 不能操作返回的 Resource 对象的值

语法:resources.PostProcess RESOURCE,一个主要用例是 PostCSS 清除 CSS

PostCSS

使用任何可用的插件,通过 PostCSS 处理 CSS文件

语法:resources.PostCSS [OPTIONS] RESOURCEpostCSS [OPTIONS] RESOURCE

使用

步骤1:安装 Node

Node.js

步骤2:安装 postcss

1
npm install postcss postcss-cli autoprefixer

步骤3:配置文件

必须在项目根目录创建名为 postcss.config.js 的配置文件,示例内容如下:

1
2
3
4
5
6
7
8
module.exports = {
  plugins: [
    require('autoprefixer'),
    ...process.env.HUGO_ENVIRONMENT === 'production'
      ? [purgecss]
      : []
  ]
}

注意:如果Windows系统,并且项目的路径包含空格,则必须将PostCSS配置放在 package.json

步骤4:编写CSS

assets 目录下编写 CSS 文件

步骤5:应用CSS

在可选模板中应用

如果是 CSS 文件

1
2
3
4
5
// layouts/partials/css.html
{{ $opts := dict "config" "config-directory" "noMap" true }}
{{ with resources.Get "css/main.css" | postCSS $opts }}
  <link rel="stylesheet" href="{{ .RelPermalink }}">
{{ end }}

如果是 Sass 文件

1
2
3
4
5
// layouts/partials/css.html
{{ $opts := dict "config" "config-directory" "noMap" true }}
{{ with resources.Get "sass/main.scss" | toCSS | postCSS $opts }}
  <link rel="stylesheet" href="{{ .RelPermalink }}">
{{ end }}

可选参数

下面是所有 Options 列表:

可选参数 描述
config 指定配置文件的路径
noMap 默认 false,如果为 true,禁用行内 source maps
inlineImports 默认 false,是否开启行内导入
skipInlineImportsNotFound Hugo v0.99.0+,默认 false,未找到跳过行内导入
use 指定使用插件
parser 一个自定义 postcss 转换器
stringifier 一个自定义 postcss 字符串转换器
syntax 自定义 postcss 语法

说明:上面后4个配置 use parser stringifier syntax 用于不想编写 postcss.config.js 文件的时候

PostCSS 清除 CSS

在Hugo中,有几种方法可以使用 PostCSS 来设置 CSS 清除

如果是一个简单的项目,应该考虑走更简单的路线,放弃使用 resources.PostProcess,然后从模板中提取关键字

下面的配置将会写入一个 hugo_stats.json 文件在项目的根目录(生产环境)

1
2
3
# config.yaml
build:
  writeStats: true
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
// postcss.config.css
const purgecss = require('@fullhuman/postcss-purgecss')({
    content: [ './hugo_stats.json' ],
    defaultExtractor: (content) => {
        let els = JSON.parse(content).htmlElements;
        return els.tags.concat(els.classes, els.ids);
    }
});

module.exports = {
     plugins: [
         ...(process.env.HUGO_ENVIRONMENT === 'production' ? [ purgecss ] : [])
     ]
 };

然后,CSS 清除将会应用在项目构建完成之后,因此,你需要在 header.html 模板中做下面的事情来包含构建的CSS:

1
2
3
4
5
6
{{ $css := resources.Get "css/main.css" }}
{{ $css = $css | resources.PostCSS }}
{{ if hugo.IsProduction }}
{{ $css = $css | minify | fingerprint | resources.PostProcess }}
{{ end }}
<link href="{{ $css.RelPermalink }}" rel="stylesheet" />

PostCSS 环境变量

在 PostCSS 和 Babel 中,可以使用的 Hugo 环境变量

使用这些环境变量,可以在 JS/TS 文件中写类似这样的代码:

1
process.env.HUGO_ENVIRONMENT === 'production' ? [autoprefixer] : []

以下是 Hugo 注入的环境变量:

变量 描述
HUGO_ENVIRONMENT HUGO_ENV hugo 命令 “production” ;hugo server 命令 “development”
PWD 项目绝对文件路径
HUGO_PUBLISHDIR 生成的 public 绝对文件路径

JavaScript Build

Hugo 使用 ESBuild 构建 JavaScript 文件

语法:js.Build [OPTIONS] RESOURCE

可选项可以是一个 JS 文件路径,或者下面的可选 Options:

可选参数 类型 描述
targetPath string 生成 JS/TS 文件目标路径
params map | slice 可以在 JS/TS 文件中作为 JSON 导入的参数, 示例
minify bool 压缩为 min.js
inject slice 将全局变量替换为从文件导入的变量;必须相对于 assets
shims map 更换依赖包,示例
target string 目标 ES 版本,es5es2015-es2020esnext;默认 esnext
externals string slice 外部依赖,排除永远不需要打包的依赖项,如 CDN 链接的依赖
defines map 要在构建时执行的字符串替换;是一个映射,其中每个键将被其值替换
format string 输出格式:iife cjs esm;默认 iife
sourceMap string 生成 source maps:inline external;默认不生成

示例:params导入

假设模板中如下打包 JavaScript:

1
{{ $js := resources.Get "js/main.js" | js.Build (dict "params" (dict "api" "https://example.org/api")) }}

然后在你自己的 JS 文件中,可以通过添加如下代码导入并使用这些变量:

1
import * as params from '@params';

注意:对于小的参数数据集,使用这种方式;对于大数据集,建议在 assets/ 下创建 JS 文件

示例:shims依赖映射

shims 可选配置,一个常见的用例是:在生产使用CDN,开发使用项目中的 JS 包

假设模板中如下打包 JavaScript:

1
2
{{ $shims := dict "react" "js/shims/react.js"  "react-dom" "js/shims/react-dom.js" }}
{{ $js = $js | js.Build dict "shims" $shims }}

在 shims 目录下的两个文件导出了:

1
2
3
4
5
// js/shims/react.js
module.exports = window.React;

// js/shims/react-dom.js
module.exports = window.ReactDOM;

通过上述操作,最终在 JS 文件中使用时:

1
2
import * as React from 'react'
import * as ReactDOM from 'react-dom';

说明:在上面的例子中,打包时使用 s/shims 目录下的 JS 文件替换 react react-dom 模块;在生产环境中,可以替换为 CDN 链接

示例:从/assets导入

匹配 *.{js,ts,jsx,tsx} 扩展名

1
2
3
4
import { hello } from 'my/module'; // assets/my/module/index.js
import { hello3 } from 'my/module/hello3'; // assets/my/module/hello3.js
import { hello4 } from './lib'; // 相对目路径
import * as data from 'my/module/data.json'; // 导入 JSON

Babel

Hugo 支持使用 Babel 处理 JavaScript 文件

语法:resources.Babel [OPTIONS] RESOURCEbabel [OPTIONS] RESOURCE

Hugo 需要 @babel/cli@babel/core 包来进行 Babel 打包 JS 文件:

1
2
3
4
5
6
# 全局安装
npm install -g @babel/cli @babel/core
# 局部安装
npm install @babel/cli @babel/core --save-dev
# Babel 插件
npm install @babel/preset-env --save-dev

安装好依赖后,需要在项目根目录创建 babel.config.js 文件,示例配置:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
module.exports = {
  presets: [
    [
      require("@babel/preset-env"),
      {
        useBuiltIns: "entry",
        corejs: 3,
      },
    ],
  ],
};

支持的可选 Options:

可选配置 类型 描述
config string 指定配置文件的路径
minified bool min.js 压缩
noComments bool 输出注释,默认 true
compact bool 去除多余空白字符和换行符,默认 auto
verbose bool 输出全部日志
sourceMap string 生成 source maps:inline external;默认不生成

Minify

资源压缩

语法:resources.Minify RESOURCEminify RESOURCE

1
2
{{ $css := resources.Get "css/main.css" }}
{{ $style := $css | resources.Minify }}

Concat

资源绑定

语法:resources.Concat TARGET_PATH SLICE_RESOURCES

1
2
3
{{ $plugins := resources.Get "js/plugins.js" }}
{{ $global := resources.Get "js/global.js" }}
{{ $js := slice $plugins $global | resources.Concat "js/bundle.js" }}

Fingerprint

处理给定资源,添加资源内容的散列字符串

语法:resources.Fingerprint RESOURCE [ALGORITHM]fingerprint RESOURCE [ALGORITHM]

1
2
3
{{ $js := resources.Get "js/global.js" }}
{{ $secureJS := $js | resources.Fingerprint "sha512" }}
<script src="{{ $secureJS.Permalink }}" integrity="{{ $secureJS.Data.Integrity }}"></script>

ExecuteAsTemplate

从模板创建资源

语法:resources.ExecuteAsTemplate TARGET_PATH CONTEXT RESOURCE

假设项目中存在模板 assets/sass/template.scss

1
2
3
4
5
6
7
$backgroundColor: {{ .Param "backgroundColor" }};
$textColor: {{ .Param "textColor" }};
body{
  background-color:$backgroundColor;
  color: $textColor;
}
// [...]

创建模板资源:

1
2
{{ $sassTemplate := resources.Get "sass/template.scss" }}
{{ $style := $sassTemplate | resources.ExecuteAsTemplate "main.scss" . | resources.ToCSS }}

FromString

从字符串中创建资源

语法:resources.FromString TARGET_PATH CONTENT

1
2
3
4
5
6
7
{{ $string := (printf "var rootURL = '%s'; var apiURL = '%s';" (absURL "/") (.Param "API_URL")) }}
{{ $targetPath := "js/vars.js" }}
{{ $vars := $string | resources.FromString $targetPath }}
{{ $global := resources.Get "js/global.js" | resources.Minify }}

<script src="{{ $vars.Permalink }}"></script>
<script src="{{ $global.Permalink }}"></script>

CLI

实际的开发和部署过程中,使用的 Hugo CLI 并不多,一般只需要 hugo hugo server 两个命令就够了,本文档不过多介绍,需要了解的前往官网 Hugo CLI

故障排除

前往官网 Troubleshoot

社区

关于 Hugo 生态及社区

部署

Hugo 官方提供许多托管站点的部署教程,如 GitHub、GitLab 等,如果你的网站不需要部署到服务器,可以选择 部署到托管站点

Licensed under CC BY-NC-SA 4.0
最后更新于 2023-03-27 18:57:00