Hugo 采用开源的 goldmark 作为 markdown 的解析器,兼容 GitHub-Flavored Markdown 标准规范,很多的静态网站都是使用的Hugo,例如 K8S 的主页,这里介绍常见的使用技巧。
简介
Hugo 是通过 GoLang 编写的静态网站页面,效率要比 Ruby 编写的 Jekyll 要高很多。可以直接从 Github 上下载二进制包,然后解压添加到 PATH 路径下即可。
建议安装 hugo-extended
版本,其支持 SCSS 功能,适合于对主题的开发。另外,通过 goldmark 实现了很多扩展,最常用的就是任务列表,详细的清单可以参考 MarkdownGuide 中的介绍。
通过 Hugo 可以高度化定制,在设计时尽量能做到 Markdown 语法的兼容。
基本使用
通过主题可以对页面进行定制,有很多主题可以选择,相见 themes.gohugo.io,以 hugo-geekdoc 为例,下载后直接放到项目的 themes 目录下即可。
----- 检查安装是否成功
hugo version
----- 当前目录下新建项目
hugo new site hugo
----- 主题模板加到themes目录下,在配置文件config.toml中增加theme="doks"或者通过--theme hyde参数启动
git clone --depth 1 --recursive https://github.com/h-enk/doks.git themes
----- 创建一篇文章
hugo --verbose new post/create-hugo-blog.md
----- 启动服务
hugo server --buildDrafts --bind="0.0.0.0" --port=38787
----- 生成静态文件(含draft),保存在buildDir目录下,默认是public
hugo -D
配置文件
默认采用根目录下的 hugo.toml
hugo.yaml
hugo.json
等配置,可以通过 --config a.toml,b.toml
方式指定配置文件。不过推荐使用目录,默认为 config
目录,可以在配置文件中使用 configDir
配置项修改。
hugo.toml
config/
|-_default/
| |-hugo.toml
| |-languages.toml
| |-menus.en.toml
| |-menus.cn.toml
| `-params.toml
|-develop/
| |-hugo.toml
| `-params.toml
`-production/
`-hugo.toml
目录下第一层是环境,包括 _default
默认配置,常见一般还会配置 production
目录,例如,通过 --environment production
启动时,会将 _default
中的配置与 production
合并,在模板中可以通过如下方式检查。
{{ if eq hugo.Environment "production" }}
<div> 生产模式 </div>
{{ else }}
<div> 开发模式 </div>
{{ end }}
第二层对应了配置项的顶层,常见的如 [Params]
对应 params.toml
,[Menu]
对应了 menu.toml
包括了不同语言。
基本概念
Section
基于 content
目录结构定义的页面集合,该目录下的第一级子目录都是一个 section
,如果想让一个子目录成为 section
需要在目录下面定义 _index.md
文件,这样,所有的 section
就构成一个 section tree
。
content/
`-blog/ <-- Section, 因为是content的子目录
|-funny-cats/
| |-mypost.md
| `-kittens/ <-- Section, 因为包含_index.md
| `-_index.md
`-tech/ <-- Section, 同上
`-_index.md
另外,除了通过上述 Section
对内容进行分类外,还允许 Type
自定义,如果页面中不存在,那么就会默认使用 Section
对应的值,使用时可以参考如下的查找顺序,可见模板 Type
优先级高于 Section
,从而允许更好的自定义。
还可以根据不同的页面类型进行处理,包括了 page
section
term
home
taxonomy
等,也就是 .Kind
变量,可以参考官方 Methods Kind 中的介绍。
模板
Hugo 使用 GoLang 的 html/template
库作为模版引擎,分成了三种类型模版:
single
用于单个页面的渲染。list
渲染一组相关内容,例如一个目录下的所有内容。partial
可被其它模版引用,作为模版级别的组件,例如页面头部、页面底部等。
其中 baseof.html
作为不同 Section
的根模板,有套模版查找机制,如果找不到与内容完全匹配的模板,它将向上移动一级并从那里搜索,其中,基本模版 baseof.html
的查找规则如下。
01. /layouts/section/<TYPE>-baseof.html
02. /themes/<THEME>/layouts/section/<TYPE>-baseof.html <--- 不同Type的模板
03. /layouts/<TYPE>/baseof.html
04. /themes/<THEME>/layouts/<TYPE>/baseof.html
05. /layouts/section/baseof.html
06. /themes/<THEME>/layouts/section/baseof.html <--- 不同Section模板
07. /layouts/_default/<TYPE>-baseof.html
08. /themes/<THEME>/layouts/_default/<TYPE>-baseof.html
09. /layouts/_default/baseof.html
10. /themes/<THEME>/layouts/_default/baseof.html <--- 默认值
在模板中,通过 {{ partial "xx" . }}
引入 Partials
模块,模块中对应的页信息,可通过 {{ . }}
查看,另外,参数可以通过如下方式添加 {{ partial "header.html" (dict "current" . "param1" "1" "param2" "2" ) }}
。
除了上述的 Partials
引入方式之外,还可以通过 {{ define "main" }} ... {{ end }}
在不同的类型中(例如 single
list
等)自定模块,然后以 {{ block "main" . }} ... {{ end }}
方式使用。
变量引用
在模板中通过 {{ xxx }}
方式引用变量,使用变量方式如下:
- 全局配置,如
.Site.Params.titile
对应hugo.toml
中的[Params]
或config/_default/params.toml
中配置。 - 页面参数,可以在开始指定,并通过
.Params.xxx
方式引用。 - 其它参数,包含了一些内置的参数,可以直接使用,例如
.Title
.Section
.Content
.Page
。 - 本地化参数,常见如
i18n "global-identifier"
指定,或者i18n .Site.Params.xxx
转换。 - 使用函数,例如
hugo.Environment
hugo.IsExtended
等,其它可以参考 Functions 内容。
除了上述的变量,还可以在 data
目录下保存 json
yaml
toml
xml
等格式的数据文件,并通过 .Site.Data.xxx
方式在模板中进行引用。
基本语法
模板通过 {{ }}
包裹,其中的内容称为动作 (Action),一般包含了两类:A) 数据求值,会直接输出到模板,包括直接使用变量;B) 控制结构,包括了条件、循环、函数等。
另外,可以对每行控制是否换行,通过 -
控制,例如 {{- -}}
为开始和结束均不换行,不过也可以最后压缩。
----- 注释
{{/* comment */}}
----- 访问已经存在的变量,自定义变量
{{ .Title }}
{{ $var }}
{{ $var := "Value" }}
{{ $var := `Hello
World` }}
Slice VS. Map
其中 Slice 就对应了数组,常见操作如下。
{{ $fruit := slice "Apple" "Banana" }}
{{ $fruit = append "Cherry" $fruit }}
{{ $fruit = append $fruit (slice "Pear") }}
{{ $fruit = append (slice "Cherry" "Peach") $fruit }}
{{ $fruit = uniq $fruit }}
{{ range $fruit }}
I love {{ . }}
{{ end }}
{{ range $index, $value := $fruit }}
{{ $value }} or {{ . }} is at index {{ $index }}
{{ end }}
{{ $first := first 2 $fruit }}
{{ $last := last 3 $fruit }}
{{ $third := first 3 $fruit | last 1 }}
{{ $third := index $fruit 2 }}
字典的使用方式如下。
{{ $hero := dict "firstame" "John" "lastname" "Lennon" }}
{{ $hero = merge $hero (dict "birth" "1940") }}
{{ $hero.firstame }}
{{ $firstname := index $hero "firstname" }}
{{ range $key, $value := $hero }}
{{ $key }}: {{ $value }}
{{ end }}
{{ $basis := "lastname" }}
{{ if eq $relation "friend" }}
{{ $basis = "firstname" }}
{{ end }}
Hello {{ index $hero $basis }}!
{{ range slice "firstname" "lastname" }}
{{ . }}: {{ index $hero . }}
{{ end }}
使用时,可以通过 {{ if reflect.IsSlice $value }}
或者 IsMap
判断具体的类型。
逻辑判断
通过 if
语句来判断某个值的真假,不过建议使用 with
,此时会在范围内重新绑定上下文。
{{ if .IsHome }} xxx {{ end }} // 当 IsHome 为 true 会调用,否通过 not
{{ if eq .Title "Home" }} xxx {{ end }} // 判断变量是否相等,不等 ne
{{ if and .IsHome .Params.show }} xxx {{ end }} // 多个条件同时满足,某个满足 or
{{ if strings.Contains "hugo" "go" }} xxx {{end}} // 判断是否包含指定字符串
// 如下两种方式等价,如果为空则会跳过
{{ if isset .Params "title" }}
<h4>{{ index .Params "title" }}</h4>
{{ end }}
{{ with .Params.title }}
<h4>{{ . }}</h4>
{{ end }}
// 但是 if 可是使用 else if 语句
{{ if (isset .Params "description") }}
{{ index .Params "description" }}
{{ else if (isset .Params "summary") }}
{{ index .Params "summary" }}
{{ else }}
{{ .Summary }}
{{ end }}
// 如下是一个稍微复杂的逻辑
{{ if (and (or (isset .Params "title") (isset .Params "caption")) (isset .Params "attr")) }}
<div class="caption {{ index .Params "attr" }}">
{{ if (isset .Params "title") }}
<h4>{{ index .Params "title" }}</h4>
{{ end }}
{{ if (isset .Params "caption") }}
<p>{{ index .Params "caption" }}</p>
{{ end }}
</div>
{{ end }}
如果 Param
设置了 description
属性,那么输出 Param
的 description
内容,否则输出 Summary
的内容。
{{ with .Param "description" }}
{{ . }}
{{ else }}
{{ .Summary }}
{{ end }}
迭代
对于字典数据可以通过 {{ range $idx, $var := .Site.Data.xxx }}
遍历,而数组则 {{ range $arr }}
方式遍历,同样以上述的 Data
为例,可以通过如下方式排序、过滤、获取数据。
// 这里上下文访问的是数组元素,要访问全局上下文需要使用 $. 访问
{{ range $array }}
{{ . }}
{{ end }}
// 可以声明变量、元素索引
{{ range $val := $array }}
{{ $val }}
{{ end }}
{{ range $idx, $val := $array }}
{{ $idx }} -- {{ $val }}
{{ end }}
// 为 map 元素的索引和值声明变量
{{ range $key, $val := $map }}
{{ $key }} -- {{ $val }}
{{ end }}
// 当传入的参数为空时,执行 else 语句
{{ range $array }}
{{ . }}
{{else}}
// 在 $array 为空时才会执行
{{ end }}
另外,还可以使用如下方式。
<ul>
{{ range sort .Site.Data.books.fiction "title" }}
<li>{{ .title }} ({{ .author }})</li>
{{ end }}
</ul>
{{ range where .Site.Data.books.fiction "isbn" "978-0140443530" }}
<li>{{ .title }} ({{ .author }})</li>
{{ end }}
{{ index .Site.Data.books "historical-fiction" }}
这样就可以根据不同变量进行过滤等,对于 .Site.Pages
等内置变量也相同。
如下是条件过滤的处理。
{{- if and (isset .Params "math") (eq .Params.math true) }}
{{- end -}}
过滤页面
如下是一个先过滤当前 Section 的数据,同时页面需要设置 class: "page"
选项,先按照年份聚合,然后再按照日期的倒序进行排序,显示日期以及对应的标题信息。
{{ $blogs := where (where .Site.Pages "Section" .Section) "Params.Class" "==" "page" -}}
{{ range $blogs.GroupByDate "2006" "desc" }}
<h1>{{ .Key }}</h1>
<ul>
{{ range .Pages.ByDate.Reverse }}
<li><span>{{ .Date.Format "2006-01-02" }}</span> <a href="{{ .RelPermalink }}">{{ .Title }}</a></li>
{{ end }}
</ul>
{{ end }}
页面上可用变量可以通过 Page Variables 查看,如果直接通过 {{ . }}
打印会显示文件所在路径。
其它常用使用示例如下。
{{ range .Data.Pages }} // 遍历 Data.Pages
{{ range where .Data.Pages "Section" "blog" }} // 遍历 Data.Pages,过滤 Section 为 blog 的数据
{{ range first 10 .Data.Pages }} // 遍历 Data.Pages,取前10条数据
{{ range last 10 .Data.Pages }} // 遍历 Data.Pages,取后10条数据
{{ range after 10 .Data.Pages }} // 遍历 Data.Pages,取第10条数据之后的数据
{{ range until 10 .Data.Pages }} // 遍历 Data.Pages,取第10条数据之前的数据
短代码
ShortCodes 主要是为处理一些 Markdown 不方便表示的处理逻辑,从而省略了一些原始 html
代码的编写,官方 提供了一些默认的实现,例如一些视频网站的链接、relref
等,源码可以参考 Github 。
假设有个 foobar
的 ShortCode,可以通过如下方式使用,注意,参数的包裹方式,当前暂未找到比较好的禁止渲染方法。
{{ foobar "foo" "bar" }}
Some short codes
{{ /foobar }}
上述的参数在 ShortCode 模板中有几种获取方式:通过 with .Get 0
获取,最简单直接;或者,通过 index .Params 0
获取,而其中的内容,则可以通过 .Inner
方式读取,而且会要求必须要调用。
另外,还可以参考 Hugo ShortCodes 中的一些示例。
常用函数
在模板中可以通过 {{ with .Site.Data.Resume . }} {{ .SomeData }} {{ end }}
方式引用。
高级进阶
静态文件
包括了图片、CSS、Javascript 等,通常是现有的文件,例如三方库 Bootstrap、FontAwesome 等,引用时需要放到 static
目录下,模板中需要放到对应目录下,会自动复制。
可通过 {{ "css/bootstrap.min.css" | absURL }}
这种方式引用,此时访问 http://foobar.com/css/bootstrap.min.css
是,会影射到 static/css/bootstrap.min.css
文件。
Mounts
可以通过 npm
管理三方的 JS 包,不过此时需要在配置文件中通过 module.mounts
配置。
[module]
[[module.mounts]]
source = "node_modules/katex/dist"
target = "static/katex"
然后在模板中可以通过如下方式使用。
<script type="text/javascript" src="{{ "katex/katex.min.js" | absURL }}"></script>`
<link rel="stylesheet" href="{{ "katex/katex.min.css" | absURL }}"/>
CSS
从 0.43 版本之后,Hugo 就支持了 SASS 的编译,不过只能把源文件放到 /assets/scss/
或 /themes/<NAME>/assets/scss/
目录下,然后,通过如下方式引入。
{{ $opts := dict "transpiler" "libsass" "targetPath" "css/style.css" }}
{{ with resources.Get "sass/main.scss" | toCSS $opts | minify | fingerprint }}
<link rel="stylesheet" href="{{ .RelPermalink }}" integrity="{{ .Data.Integrity }}" crossorigin="anonymous">
{{ end }}
通过 resouces.Get
获取 SCSS 文件内容,接着通过管道编译、压缩、生成指纹,这样可以给生成文件加上 hash 文件名,这样可以从 CDN 拉取最新的 CSS 而不是缓存的老文件。另外,在编译 CSS 时的配置选项除了上述,还可参考如下。
{{ $opts := (dict "outputStyle" "compressed" "enableSourceMap" true "includePaths" (slice "node_modules")) -}}
代码高亮可以通过如下命令生成,示例可以参考 Style Longer 以及 Style。
hugo gen chromastyles --style=monokai > syntax.css
注意,配置参数要设置 codeFences=true
,否则行号等信息会通过表格方式显示,会导致显示异常。
JavaScript
与 CSS 类似,可以通过如下方式引入 JS 脚本
{{ $params := dict "api" "https://example.org/api" }}
{{ with resources.Get "js/main.js" }}
{{ if hugo.IsDevelopment }}
{{ with . | js.Build }}
<script src="{{ .RelPermalink }}"></script>
{{ end }}
{{ else }}
{{ $opts := dict "minify" true "params" $params }}
{{ with . | js.Build $opts | fingerprint }}
<script src="{{ .RelPermalink }}" integrity="{{ .Data.Integrity }}" crossorigin="anonymous"></script>
{{ end }}
{{ end }}
{{ end }}
在脚本中可以通过 import * as params from '@params';
方式引用参数,甚至可以通过 Shims 方式引入 React 代码,更多功能可以参考 Hugo JS。
图片渲染
在 hugo 中定制图片格式比较麻烦,不支持类似 ![name](uri){: width="420"}
这种方式,因为支持直接使用 html
,所以,可以通过如下的方式进行配置。
<img src="picture.png" alt="some message" with="50%" />
可以在 css
中定制 img
对齐方式,不过此时就只能使用一种了。另外,官方提供了 figure
的 shortcodes 代码,可以使用,但是会导致不同的平台的兼容性问题。
还有一种方式,在 layouts/_default/_markup/render-image.html
中定制图片的渲染方式,然后通过 ![name](uri?width=100px)
这种类似方式使用,不过支持的参数需要在上述文件中配置。
更多其它的渲染 Hooks 可以参考 Render Hooks 内容。
数据传递
通过 Scratch
可以传递数据,用于在 Page
和 ShortCodes
间传递数据,如果要在模板中使用临时的数据传递,那么可以通过 newScratch
新建,如下以 Hugo 自动生成的 Scratch
为例,如下使用 .Page.Scratch
和 .Scratch
的作用相同。
{{ .Scratch.Set "hey" "Hello" }} # 还可以是 3 或者 (slice "Hello" "World")
{{ .Scratch.Get "hey" }} # 获取
{{ .Scratch.Add "hey" "Welcome" }} # 执行加法,类似Go语言,字符串拼接、数值相加、数组拼接
{{ .Scratch.GetSortedMapValues "hey" }} # 除了通过Get获取map之外,还可以返回以key排序的value值
{{ .Scratch.Delete "hey" }} # 删除
{{ .Scratch.SetInMap "hey" "Hello" "World" }} # 设置map,对应 key:value 为 Hello:World
{{ .Scratch.SetInMap "hey" "Hey" "Andy" }}
{{ .Scratch.Get "hey" }} # map[Hello:World Hey:Andy]
模板调试
可以通过 {{ printf "%#v" .Permalink }}
打印当前的变量信息。另外,如果要调试页面布局,可以在 <head>
中添加如下的内容,方便查看。
<style>
div {
border: 1px solid black;
background-color: LightSkyBlue;
}
</style>
相关文章
Hugo 默认提供了 Related Content 配置相关文章,默认通过 keywords
date
tags
进行相关性的匹配。
最佳实践
其中顶层目录包含了 archetypes
assets
content
data
i18n
static
layouts
几个,
archetypes/ 通过new子命令新建文章时的模板
config/ 默认使用hugo.[toml|yaml|json]作为配置,可以使用如下目录方式
|-_default/ 默认配置
`-production/ 全局配置
i18n/ 本地化
themes/
|-halo/ 对应的模板名
|-assets/ 模板中的资源文件
| |-images/
| |-js/ JavaScript相关脚本,详见 footer/script-footer.html
| |-scss/ SCSS文件
| | `-app.scss 顶层的SCSS文件,会包含其它目录下的文件,详见 head/head.html
| `-static/ 静态文件
| `-syntax.css 上述通过hugo gen chromastyles命令生成的CSS文件,详见 head/head.html
|-layouts/ 布局模板,其中 _default 是默认,而 partials 是不同模板的引用
|-_default/
|-blog/ 对应 blog Section 模板
|-docs/ 对应 docs Section 模板
|-partials/ 不同模板中的引用
|-resume/ 对应 resume Section 模板
| |-baseof.html 渲染用的根页面
|-shortcodes/ 短代码
|-slide/ 对应 slide Section 模板
|-404.html 生成404页面
其中 CSS 相关内容依赖如下几个文件:A) syntax.css
语法高亮,只需要压缩即可;B) main.css
核心的自定义配置,为了使用 bootstrap
变量,同时会在 scss
模板中引用,所以,无需再单独引入 bootstrap.min.css
即可;C) fontawesome.min.css
使用的图标,可以参考 Icons 中的内容。
另外,Bootstrap 的相关变量保存在 bootstrap/scss/_variables.scss
中。
标签
通过默认的 tags
keywords
配置关联文章,其中 keywords
按照文章类型添加,而 tags
则包含如下常见的分类。
topic
专题文章,会将某些文章集中梳理、展示。language
编程相关,细分为c/cpp
lua
bash
rust
java
python
golang
web
css
html
具体语言。database
数据库相关,细分为mysql
等。linux
操作系统相关,细分为kvm
network
command
vim
ebpf
等。security
安全相关,细分为ssh
tls/ssl
等。container
容器相关内容,细分为docker
、k8s
等。warehouse
大数据相关内容,细分为hudi
等。devops
运维开发相关工具,细分为git
等。algorithm
structure
算法数据结构相关。example
包含相关的示例代码、cheatsheet
常用命令行整理、software
相对命令行要更系统。
每篇文章标题下方的标签列表可以用来跳转,此时会通过模板中的 layouts/_default/list.html
进行渲染。
其它
很多项目中可以通过 Markdown 维护文档,这里的模板也支持,使用方式如下。
cd content/cn
ln -s /your/code/docs/path foobar
然后通过 /cn/foobar/
访问即可,此时文档的维护层级如下。
|-_index.md 必须存在,会使用 layouts/_default/list.html 渲染
|-basic.md basic/ 可以使用这种方式,相对链接可通过 ../metric/nginx 类似方式引用
|-metric/
| |-nginx.md metric/nginx/ 一般就是底层页面了
| `-system/ 无法引用,也可以增加 system.md 文件
| |-cpu.md metric/system/cpu/
| `-load.md metric/system/load/
|-metric.md metric/ 可以使用相对引用,引用 metric/ 目录下的文件
`-query/
`-files.md
TODO
- 侧边栏 TOC,点击锚点元素时默认是到页面顶部,如果顶部有
fixed
定位,就会遮挡部分,支持 ScrollSpy 功能。
参考
- Hugo Docs 官方文档,可以参考常见的函数,Hugo Themes 包含了很多个网站的示例。
- Mermaid 绘制流程图的方法,www.wappalyzer.com 用来分析某个网站所用的技术栈。