这是一个系列博客,记录了我从零开始构建Hugo主题https://github.com/tomowang/hugo-theme-tailwind 的过程。全系列包括四篇文章,这是第二篇:
- I. 主要介绍我构建Hugo主题的背景,我对主题的功能想法,以及开发环境的搭建
- II. Hugo主题的主要目录结构,需要了解的技术,以及我创建的主题的主体框架
- III. Hugo主题的其他功能,包括黑色主题,响应式设计,多语言,代码高亮,构建管道等
- IV. 该部分描述非代码相关的内容,包括持续集成(CI),如何提交至官方主题站点,以及SEO相关的数据等
hugo主题基础知识
在开始构建主题前,我们需要对hugo主题的一些术语、语法及目录结构有初步的了解。
基础语法
hugo使用html/template
来渲染内容,方法和变量通过{{ }}
进行访问。常用的语法如下:
- 变量渲染 -
{{ $address }}
- 方法调用 -
{{ FUNCTION ARG1 ARG2 .. }}
- 循环
{{ range $array }}
{{ . }} <!-- The . represents an element in $array -->
{{ end }}
- 条件语句 -
with
if
else
等
{{ with .Params.title }}
<h4>{{ . }}</h4>
{{ end }}
{{ if (isset .Params "description") }}
{{ index .Params "description" }}
{{ else }}
{{ .Summary }}
{{ end }}
- 管道 -
{{ .Title | .RenderString }}
- 上下文
.
以及全局上下文$.
- 使用短横
-
去除额外的空字符串
<div>
{{- .Title -}}
</div>
详细的语法描述,可以参考官方文档https://gohugo.io/templates/introduction/ 。
同时hugo还提供很多内置的变量,这些变量通过主题文件的渲染,最终生成静态页面:
- Site - 站点对象
- Pages - 页面的聚合
- Page - 单个页面对象
- Menu - 菜单列表
- Menu entry - 单个菜单对象
- Taxonomy - 单个分类对象
我们在使用主题来渲染页面内容时,需要根据对应的变量,通过属性或者方法调用,最终呈现我们想要的内容。例如我们期望展示单个文章的阅读时长,可以调用Page
变量的ReadingTime
方法(https://gohugo.io/methods/page/readingtime/
)。部分对象方法较多,具体可以参考官方文档https://gohugo.io/quick-reference/methods/
。
目录结构
我们创建了基本的hugo主题后,使用命令tree -L 2 -d .
可以看到主题目录的基本结构:
.
├── archetypes
├── assets
│ ├── css
│ └── js
├── data
├── i18n
├── layouts
│ ├── _default
│ └── partials
└── static
相关目录含义如下:
archetypes
- 创建新内容页面的模板assets
- 静态资源文件,一般会经由资源管道进行处理data
- 用于存储额外的数据,可以参考https://gohugo.io/templates/data-templates/i18n
- 存储本地化文件layouts
- 核心的模板布局文件static
- 静态文件,会被直接复制到最终public目录
其中最核心的为layouts目录,该目录决定了模板支持的页面以及页面的布局。 hugo使用一套规则来查询内容页面应该使用的模板文件。一般站点由以下几种类型的页面构成:
- 首页
- 单页
- 列表页
- 术语页
- 术语列表页
其查找顺序可以参考官方文档https://gohugo.io/templates/lookup-order/ 。官方文档介绍了复杂的查找顺序,但主题的大部分页面结构类似,所以实际情况中只需要定义少数页面就能达到期望的结果。
主题内容构建
tailwindcss
在开始构建前,需要安装tailwindcss
,我参考官方文档做了一些调整:
# install
pnpm install -D tailwindcss
# init
npx tailwindcss init
通过上面的命令安装tailwindcss
并生成tailwind.config.js
配置文件,
由于我们的页面内容在layout目录,需要调整tailwind.config.js
中的content
配置:
content: ["./layouts/**/*.html"],
将tailwindcss
的引用放在assets/css/main.css
中,
@tailwind base;
@tailwind components;
@tailwind utilities;
然后可以通过如下命令对页面内容进行监听生成实际使用的CSS文件:
npx tailwindcss -i assets/css/main.css -o ./static/main.css --watch
基础布局
基于我的需求以及前文对hugo目录结构的描述,最终我定义了如下的基础页面
baseof.html
- 承载主体页面的布局list.html
- 列表页、术语列表页single.html
- 单页,主要的内容页terms.html
- 术语页
通过baseof.html
文件,我们定义基础模板,该模板会应用在所有的页面,其核心内容如下:
<!DOCTYPE html>
<html lang="{{ or site.Language.LanguageCode site.Language.Lang }}" dir="{{ or site.Language.LanguageDirection `ltr` }}">
<head>
{{ block "title" . }}
<title>
{{ if .IsHome }}{{ $.Site.Title }}{{ with $.Site.Params.Subtitle }} —
{{ . }}{{ end }}{{ else }}{{ .Title }} ::
{{ $.Site.Title }}{{ with $.Site.Params.Subtitle }} — {{ . }}{{ end }}{{ end }}
</title>
{{ end }}
{{ partial "head.html" . }}
</head>
<body class="w-full bg-slate-50">
<header class="flex flex-none justify-center">
{{ partial "header.html" . }}
</header>
<main class="flex flex-auto justify-center">
{{ block "main" . }}{{ end }}
</main>
<footer class="flex flex-none justify-center">
{{ partial "footer.html" . }}
</footer>
</body>
</html>
我使用{{ partial "head.html" . }}
引入head
标签需要的内容,并在head.html
中引用了前文生成的CSS文件:
<!-- Theme CSS (/static/main.css) -->
<link rel="stylesheet" href="{{ "main.css" | relURL }}" />
HTML body
标签中,header
标签为头部导航,footer
标签为底部菜单,main
标签为内容主体。main
标签中{{ block "main" . }}{{ end }}
的内容,我们需要在其他页面进行定义,如简化版的list.html
代码如下:
{{ define "main" }}
<div class="flex flex-col w-full max-w-4xl lg:max-w-5xl relative">
<div class="flex flex-row">
<section class="flex flex-col w-2/3">
{{ range (.Paginate $pages).Pages }}
<article class="flex flex-col gap-y-3 p-6 mt-6 rounded-lg shadow-md bg-white">
<h2 class="text-4xl font-semibold text-slate-800">
<a href="{{ .RelPermalink }}">{{ .Title | markdownify }}</a>
</h2>
</article>
{{ end }}
</section>
<aside class="flex flex-col w-1/3 mx-3 top-0 sticky self-start">
{{ partial "sidebar.html" . }}
</aside>
</div>
{{ partial "pagination.html" . }}
</div>
{{ end }}
其中section
标签对应文章列表块,aside
标签对应侧边术语展示块。单页singhle.html
内容类似,包含了文章标题、TOC内容、文章主体等内容。tailwindcss
的好处是有现成的typography
插件可以直接渲染文章内容,而不必额外定义复杂的CSS样式。
整体的引用关系可以参考下图
为了复用一些组件,如标签列表、阅读时长、图标等,我们可以将相同的内容进行抽象,放到layouts/partials
目录中。以图标为例,我使用https://tabler.io/icons
作为图标来源,
下载对应图标的SVG文件放到assets
目录,然后定义partial
页面:
{{- $iconFile := resources.GetMatch (printf "icons/%s.svg" .) -}}
{{- if $iconFile -}}
{{- $iconFile.Content | safeHTML -}}
{{- else -}}
{{- errorf "Error: icon '%s.svg' is not found under 'assets/icons' folder" . -}}
{{- end -}}
其中.
符号为传递的图标名称参数,这样我们就能快速的在页面中进行图标的引用,如
{{ partial "icon" "calendar" }}
通过大概5天业余时间的开发(#bf54ab9 ), 最终呈现的效果如下:
当然,这是第一阶段的内容,只有白色主题(主题切换按钮点击无反应),没有多语言,也没有响应式。但是主体的页面结构已经有了,tailwindcss
在处理CSS样式时也表现了其高效、灵活的特性。