辭舊迎新扒吁,想改版自己的博客火鼻,一番google之后,選擇了hugo雕崩。
根據(jù)hugo官網(wǎng)的介紹魁索,hugo是世界上最受歡迎的靜態(tài)網(wǎng)站生成器之一,基于golang開發(fā)盼铁,已經(jīng)發(fā)布到0.65.0版本粗蔚。hugo采用開源的goldmark作為markdown的解析器,兼容GitHub-Flavored Markdown標(biāo)準(zhǔn)規(guī)范饶火。個人認(rèn)為鹏控,hugo適合用來做博客、做靜態(tài)展示型的企業(yè)網(wǎng)站等肤寝。
hugo提供了通過主題構(gòu)建網(wǎng)站的機(jī)制当辐。hugo生態(tài)已經(jīng)提供了300+的主題可以用。主題類似一種前端框架鲤看,可以幫助我們快速建站缘揪。我調(diào)研了一些主題,各有優(yōu)點(diǎn)刨摩,也有令我不滿意的地方寺晌,所以決定還是自己開發(fā)一套主題來用世吨。
開發(fā)主題需要結(jié)合實(shí)際的項(xiàng)目來做澡刹,一邊看效果一邊做調(diào)整。下面以開發(fā)個人博客為例耘婚,介紹如何DIY自己的主題罢浇。
?
安裝hugo
hugo提供了cli工具。下面是常見的安裝方法。
Homebrew
brew install hugo
Chocolatey (Windows)
choco install hugo -confirm
Scoop (Windows)
scoop install hugo
?
本文涉及的主要概念
內(nèi)容格式(Content Formats)
HTML和Markdown兩種格式都支持嚷闭。用戶可以往/content
文件夾里放置任何類型的文件攒岛,但是hugo首先會從內(nèi)容文件的元信息里找markup屬性,如果沒有找到胞锰,hugo會根據(jù)文件后綴名選擇合適的解析器來解析內(nèi)容灾锯。
?
元信息(Front Matter)
英文叫[Front Matter](https://gohugo.io/content-management/front-matter/)
,我依據(jù)個人的理解嗅榕,把這個概念翻譯成了元信息顺饮。hugo支持在內(nèi)容文件開頭以四種格式來聲明這篇文章的元信息。四種格式分別是:
- TOML: 以
+++
開頭和結(jié)尾 - YAML: 以
---
開頭和結(jié)尾凌那,這是本文介紹的主題采用的格式 - JSON: 本人覺得不常用兼雄,略過
- ORG: 本人覺得不常用,略過
?
內(nèi)容類型(Content Types)
粗淺的理解Content Types就是hugo用來組織網(wǎng)站內(nèi)容的一種方式帽蝶。hugo首先會從內(nèi)容文件的元信息里找type
屬性赦肋,如果沒有找到,hugo會認(rèn)定content
的文件夾下第一級文件夾的名稱作為其中包含的所有內(nèi)容文件的內(nèi)容類型励稳,例如content/blog/my-first-event.md
路徑下的my-first-event.md
文件的內(nèi)容類型是blog
佃乘。
?
模板(Archetypes)
模板(Archetypes)是指放在archetypes
文件夾里的文件,里面可以預(yù)定義一些元信息麦锯,也可以提前寫好一些內(nèi)容生成邏輯恕稠,或其他什么內(nèi)容。當(dāng)執(zhí)行hugo new
命令來生成內(nèi)容文件的時候扶欣,就會調(diào)用對應(yīng)內(nèi)容類型的模板文件來幫你自動生成一些內(nèi)容鹅巍。
假如以posts
作為內(nèi)容類型,生成posts
文件時模板的查找路徑依次是:
archetypes/posts.md
archetypes/default.md
themes/my-theme/archetypes/posts.md
-
themes/my-theme/archetypes/default.md
?
分類體系(Taxonomies)
分類體系(Taxonomies)表示作者對內(nèi)容的一套或多套分類料祠。比如標(biāo)簽(tags
)骆捧、類目(categories
)、歸檔(archives
)等髓绽。分類體系需在項(xiàng)目根路徑下配置文件中定義敛苇,例如在config.toml
文件中加入:
[taxonomies]
tag = "tags"
category = "categories"
archive = "archives"
如果想把內(nèi)容歸類,需要在內(nèi)容文件的元信息中分別聲明在上述分類體系中具體的類型顺呕,如:
tags: ["backend", "hugo"]
categories: ["Tech"]
archives: "2020"
?
創(chuàng)建博客工程
打開命令行枫攀,執(zhí)行如下命令:
hugo new site my-hugo-blog
生成的項(xiàng)目結(jié)構(gòu)如下:
? my-hugo-blog ll
total 8
drwxr-xr-x 3 wubin1989 staff 96B 2 25 20:32 archetypes
-rw-r--r-- 1 wubin1989 staff 82B 2 25 20:32 config.toml
drwxr-xr-x 2 wubin1989 staff 64B 2 25 20:32 content
drwxr-xr-x 2 wubin1989 staff 64B 2 25 20:32 data
drwxr-xr-x 2 wubin1989 staff 64B 2 25 20:32 layouts
drwxr-xr-x 2 wubin1989 staff 64B 2 25 20:32 static
drwxr-xr-x 2 wubin1989 staff 64B 2 25 20:32 themes
大家看到項(xiàng)目根路徑下有一個themes
文件夾,里面還是空的株茶,需要執(zhí)行如下命令来涨,生成一套主題腳手架:
? my-hugo-blog hugo new theme hugo-cxy-theme
Creating theme at /Users/wubin1989/workspace/go/src/my-hugo-blog/themes/hugo-cxy-theme
? my-hugo-blog cd themes
? themes ll
total 0
drwxr-xr-x 7 wubin1989 staff 224B 2 25 20:43 hugo-cxy-theme
? themes cd hugo-cxy-theme
? hugo-cxy-theme ll
total 16
-rw-r--r-- 1 wubin1989 staff 1.1K 2 25 20:43 LICENSE
drwxr-xr-x 3 wubin1989 staff 96B 2 25 20:43 archetypes
drwxr-xr-x 6 wubin1989 staff 192B 2 25 20:43 layouts
drwxr-xr-x 4 wubin1989 staff 128B 2 25 20:43 static
-rw-r--r-- 1 wubin1989 staff 440B 2 25 20:43 theme.toml
?
config.toml
baseURL = "/"
languageCode = "zh-cn"
title = "武斌的博客"
theme = "hugo-cxy-theme"
preserveTaxonomyNames = true
paginate = 10 #frontpage pagination
hasCJKLanguage = true
[outputs]
home = ["HTML", "RSS"]
[params]
hero_bg = "img/home-bg-road.jpg"
SEOTitle = "wubin1989的博客 | wubin1989 Blog"
description = "wubin1989,程序員, 攝影愛好者, 背包客 | 這里是 wubin1989 的博客启盛,邊走邊看蹦掐,邊讀邊寫技羔。"
keyword = "wubin1989, wubin1989, wubin1989的網(wǎng)絡(luò)日志, wubin1989的博客, wubin1989 Blog, 博客, 個人網(wǎng)站, 互聯(lián)網(wǎng), Web, Nodejs, Reactjs, SaaS, Golang, 微服務(wù), Microservice"
slogan = "跨過高山,走過四季卧抗,不忘初心藤滥,永不言棄"
brief_info = "全棧工程師/背包客/攝影愛好者"
info = "常年寫reactjs、vuejs社裆、java和golang拙绊,專注微服務(wù)架構(gòu)和devops相關(guān),喜歡旅游泳秀、爬山时呀、外語"
avatar = "img/avatar-wubin1989.jpg"
image_404 = "img/404-bg.jpg"
title_404 = "你來到了沒有知識的荒原 :("
[taxonomies]
tag = "tags"
category = "categories"
archive = "archives"
?
archetypes
hugo Cli工具支持hugo new
命令生成markdown文件,例如:
hugo --verbose new post/2020-02-24-how-to-create-your-own-hugo-theme.md
這個命令會在項(xiàng)目根目錄下的content
文件夾下生成post
文件夾晶默,然后生成2020-02-24-how-to-create-your-own-hugo-theme.md
文件谨娜。
而hugo-cxy-theme
文件夾里的archetypes
的作用就是,開發(fā)者可以在里面放入各種類型的文章的生成模板磺陡,這樣在執(zhí)行上述命令的時候趴梢,會自動生成一些定義好的元信息。
我們可以看到現(xiàn)在hugo-cxy-theme
文件夾里的archetypes
文件夾下只有一個default.md
文件币他,里面只有
+++
+++
就是什么都沒有坞靶。還記得上面在講模板的時候說過hugo查找對應(yīng)模板首是先從項(xiàng)目根路徑下的archetypes
文件夾里找。這個文件夾里也是只有一個default.md
文件蝴悉,里面的內(nèi)容是:
---
title: "{{ replace .Name "-" " " | title }}"
date: {{ .Date }}
draft: true
---
再來看生成的2020-02-24-how-to-create-your-own-hugo-theme.md
文件彰阴,里面不出所料的有如下內(nèi)容:
---
title: "2020 02 24 How to Create Your Own Hugo Theme"
date: 2020-02-25T23:16:58+08:00
draft: true
---
下面,我們想自定義自己的模板拍冠,怎么做尿这?
我們在hugo-cxy-theme
文件夾里的archetypes
里創(chuàng)建post.md
---
title: "{{ with slicestr .Name 10 }}{{replace . "-" " " | strings.TrimLeft " " | title }}{{end}}"
subtitle: ""
description: ""
date: {{ slicestr .Name 0 10 }}
author: ""
image: ""
tags: ["tag1", "tag2"]
categories: ["Tech"]
archives: "{{ slicestr .Name 0 4 }}"
---
然后我們刪掉之前創(chuàng)建的2020-02-24-how-to-create-your-own-hugo-theme.md
,重新生成庆杜,可以看到里面的內(nèi)容變成了:
---
title: "How to Create Your Own Hugo Theme"
subtitle: ""
description: ""
date: 2020-02-24
author: ""
image: ""
tags: ["tag1", "tag2"]
categories: ["Tech"]
archives: "2020"
---
layouts
跟網(wǎng)站整體框架布局相關(guān)的文件都放在layouts
里面射众。首先要修改_default
文件夾里的baseof.html
文件。這個文件里配置了網(wǎng)站的header
晃财、main
和footer
等叨橱。
<!DOCTYPE html>
<html>
{{- partial "head.html" . -}}
<body>
{{- partial "header.html" . -}}
<div class="section">
<div class="container">
<div class="columns cxy-gap">
{{- partial "side.html" . -}}
<div class="column" id="content">
{{- block "main" . }}{{- end }}
</div>
</div>
</div>
</div>
{{- partial "footer.html" . -}}
</body>
</html>
代碼里涉及的文件head.html
、header.html
断盛、side.html
和footer.html
文件都放在partial
文件夾里罗洗。
<!-- head.html -->
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="keyword" content="{{ .Site.Params.keyword }}">
<link rel="shortcut icon" href="{{ "img/favicon.ico" | relURL }}">
<title>{{ if .Title }}{{ .Title }}-{{ .Site.Params.SEOTitle }}{{ else }}{{ .Site.Params.SEOTitle }}{{ end }}</title>
<link rel="canonical" href="{{ .URL | relURL }}">
<!-- bulma CSS -->
<link rel="stylesheet" href="{{ "css/bulma.min.css" | relURL }}">
<!-- zangshang CSS -->
<link rel="stylesheet" href="{{ "css/zanshang.css" | relURL }}">
<!-- Custom CSS -->
<link rel="stylesheet" href="{{ "css/custom.css" | relURL }}">
{{ range .Site.Params.custom_css -}}
<link rel="stylesheet" href="{{ . | absURL }}">
{{- end }}
<!-- jQuery -->
<script src="{{ "js/jquery.min.js" | relURL }}"></script>
<script src="{{ "js/all.js" | relURL }}"></script>
<!-- Custom JS -->
{{ range .Site.Params.custom_js }}s
<script src="{{ . | absURL }}"></script>
{{ end }}
</head>
解釋一下這段代碼中涉及的hugo變量和函數(shù):
- 變量
-
.Site.Params.keywords
-
.Site
: 全局對象 -
.Params
:config.toml
文件里的[params]
的配置
-
-
.Title
: 文章標(biāo)題
-
- 函數(shù)
-
relURL
: 將輸入值轉(zhuǎn)換為相對路徑的url -
absURL
: 將輸入值轉(zhuǎn)換為絕對路徑的url
-
<!-- header.html -->
<nav class="navbar" role="navigation" aria-label="main navigation">
<div class="navbar-brand">
{{ if ( ne .Site.Title "" ) }}
<a class="navbar-item" href="{{ .Site.BaseURL | relLangURL }}"> {{ .Site.Title }} </a>
{{ end }}
<a role="button" class="navbar-burger burger" aria-label="menu" aria-expanded="false" data-target="navbarBasicExample">
<span aria-hidden="true"></span>
<span aria-hidden="true"></span>
<span aria-hidden="true"></span>
</a>
</div>
<div class="navbar-menu">
<div class="navbar-start">
<a class="navbar-item"href="{{ "/" | relLangURL }}">Home</a>
{{ range $name, $taxonomy := .Site.Taxonomies.categories }}
<a class="navbar-item" href="{{ "categories/" | relLangURL }}{{ $name | urlize }}">{{ $name | title }}</a>
{{ end }}
</div>
</div>
</nav>
<section class="hero is-medium cxy-hero" style="background-image: url('{{ .Site.Params.hero_bg | relURL }}')">
<div class="hero-body">
<div class="container">
<div class="has-text-centered is-size-4 has-text-white">
{{ if not .IsHome }}
<h1 class="title has-text-white">
{{ .Title }}
</h1>
{{ if and (eq .Type "post") .IsPage }}
<h2 class="subtitle has-text-white-bis">
{{ .Date.Format "2006-01-02"}}
</h2>
{{ end }}
{{ else }}
<h1 class="title has-text-white is-size-4">
{{ .Site.Params.slogan }}
</h1>
{{ end }}
</div>
</div>
</div>
</section>
解釋一下這段代碼中涉及的hugo變量和函數(shù):
- 變量
-
.Site.Taxonomies.categories
-
.Site
: 全局對象 -
.Taxonomies
:config.toml
文件里的[taxonomies]
的配置
-
-
.Site.Title
:config.toml
文件里的title
的配置 -
.Site.BaseURL
:config.toml
文件里的baseURL
的配置 -
.IsHome
: 是否是網(wǎng)站首頁 -
.Type
: 內(nèi)容的類型 -
.IsPage
: 是否是"page"類型
-
- 函數(shù)
-
relLangURL
: 將輸入值轉(zhuǎn)換為以正確的語言變量值為前綴的相對路徑的url,多語言網(wǎng)站才會用到 -
urlize
: 將輸入值編碼成url路徑钢猛,同時把空格改成中橫線"-" -
title
: 將輸入值轉(zhuǎn)換為首字母大寫的標(biāo)題 -
.Date.Format
: 日期時間格式化
-
<!-- side.html -->
<div class="column is-one-quarter sidebar">
<div class="card">
<div class="card-content">
<div class="">
<figure class="image is-128x128 is-inline-block">
<img class="" src="{{ .Site.Params.avatar | relURL }}">
</figure>
<div class="title is-6" style="margin-top: 10px;">
{{ .Site.Params.brief_info }}
</div>
</div>
<div class="subtitle is-6" style="margin-top: 5px;">
{{.Site.Params.info}}
</div>
</div>
</div>
<div class="card">
<div class="card-content">
<h1 class="title is-5">Tags</h1>
<div class="tags">
{{ range $name, $taxonomy := .Site.Taxonomies.tags }}
<span class="tag"><a href="{{ "tags" | absURL }}/{{ $name | urlize }}">{{ $name }}</a></span>
{{ end }}
</div>
</div>
</div>
<div class="card">
<div class="card-content">
<h1 class="title is-5">Archives</h1>
{{ range (where .Site.RegularPages "Section" "post").GroupByDate "2006" }}
<a href="{{ "archives" | absURL }}/{{ .Key }}">{{ .Key }}</a> ({{ len .Pages }})<br>
{{ end }}
</div>
</div>
</div>
解釋一下這段代碼中涉及的hugo變量和函數(shù):
- 變量
-
.Site.RegularPages
: 表示所有"Kind"屬性是"page"的內(nèi)容頁面
-
- 函數(shù)
<!-- footer.html -->
<footer>
<div class="container">
<div class="row">
<div class="has-text-centered">
<ul>
{{ with .Site.Params.social.wechat }}
<li class="is-inline">
<a target="_blank" href="{{ . | relURL }}">
<span class="icon is-medium">
<span class="fa-stack">
<i class="fa fa-circle fa-stack-2x"></i>
<i class="fab fa-weixin fa-stack-1x fa-inverse"></i>
</span>
</span>
</a>
</li>
{{ end }}
{{ with .Site.Params.social.github }}
<li class="is-inline">
<a target="_blank" href="{{ . }}">
<span class="icon is-medium">
<span class="fa-stack">
<i class="fa fa-circle fa-stack-2x"></i>
<i class="fab fa-github fa-stack-1x fa-inverse"></i>
</span>
</span>
</a>
</li>
{{ end }}
</ul>
<p class="copyright text-muted">
Copyright © {{ .Site.Title }} {{ now.Year }}
</p>
</div>
</div>
</div>
</footer>
這段代碼中涉及的hugo函數(shù)只有now
公你,表示返回當(dāng)前的本地時間
首頁的內(nèi)容放在index.html
文件里踊淳。
{{ define "main" }}
{{ $paginator := .Paginate (where (where .Site.Pages "Type" "post") "IsPage" true) }}
{{ range $paginator.Pages }}
<div class="columns">
<div class="column is-four-fifths">
<a href="{{ .Permalink }}">
<div class="title is-size-4">
{{ .Title }}
</div>
</a>
</div>
<div class="column is-right is-vertical-center subtitle">
{{ .Date.Format "2006-01-02" }}
</div>
</div>
<hr>
{{ end }}
{{ $.Scratch.Set "paginator" $paginator }}
{{- partial "pagination.html" . -}}
{{ end }}
解釋一下這段代碼中涉及的hugo變量和函數(shù):
- 變量
-
.Scratch
: 類似字典,可以設(shè)置鍵值對陕靠,作用域就是當(dāng)前頁面迂尝,可以在頁面的其他地方通過.Scratch.Get
獲取值。 推薦參考這篇文章更加深入的了解.Scratch
剪芥。
-
- 函數(shù)
-
.Paginate
: 傳入通過where函數(shù)返回的頁面集合垄开,生成Paginator
分頁器,可以用來構(gòu)建分頁組件
-
_default
文件夾里的single.html
里配置markdown文件內(nèi)容如何渲染到html文件中輸出税肪。
<!DOCTYPE html>
<html>
{{- partial "head.html" . -}}
<body>
{{- partial "header.html" . -}}
<div class="section">
<div class="container">
<div class="card" id="content">
<div class="card-content">
<div class="cxy-post-content">
{{ if not (eq (.Param "showtoc") false) }}
<header>
<h2>TOC</h2>
</header>
{{ .TableOfContents}}
{{ end }}
{{ .Content }}
</div>
<hr>
<ul>
{{ if .PrevInSection }}
<li class="is-inline">
<a href="{{ .PrevInSection.URL }}" class="pagination-previous">←
{{ .PrevInSection.Title}}</a>
</li>
{{ end }}
{{ if .NextInSection }}
<li class="is-inline is-pulled-right">
<a href="{{ .NextInSection.URL }}" class="pagination-previous">
{{ .NextInSection.Title}} →</a>
</li>
{{ end }}
</ul>
</div>
</div>
</div>
</div>
{{- partial "footer.html" . -}}
</body>
</html>
解釋一下這段代碼中涉及的hugo變量和函數(shù):
- 變量
-
.TableOfContents
: hugo可以自動從markdown文件中解析出目錄渲染到頁面里 -
.Content
: hugo從markdown中解析出的文章內(nèi)容 -
.PrevInSection
: 前一篇文章 -
.NextInSection
: 下一篇文章
-
- 函數(shù)
-
.Param
: 從內(nèi)容文件的元信息(front matter
)里取出參數(shù)值為屬性名的屬性值溉躲,如果找不到,再從項(xiàng)目配置文件(config.toml
)里找益兄。
?
-
總結(jié)
以上是我在創(chuàng)建自己的hugo博客和主題時總結(jié)的部分概念和編寫的源碼锻梳。還有很多概念或者代碼邏輯比較復(fù)雜,沒有在本文提及净捅,打算以后再仔細(xì)分析疑枯。希望上面的內(nèi)容能幫到需要的朋友。
我的hugo主題參考了:
最后的效果大概是這樣的国章。