如何利用 Hugo 构建一个方便于 markdown 发表博客和记笔记的统一工程,这篇文章给出了我实践中的一种思路:利用 Git 的 submodule 组合了笔记仓库、GitHub Pages仓库、Hugo 主题仓库、Hugo 博客仓库等四个 Git 仓库,构成一个利用开源工具实现的的分布式博客和笔记方案。

早在 16 年的时候就尝试过使用 Hexo 利用 GitHub pages 搭建过个人博客,当时也只是跑通了个流程,随后也就没管过。

一是当时记笔记基本上使用 OneNote,再将其转为 markdown 很费事费神,到后面面对跨设备写笔记的场景越来越多和对笔记软件服务商的不信任,进而转为采用 git + 文件系统 + VSCode + markdown 的方式记笔记。

二是总觉得 nodejs 下的 Hexo 过于繁杂,不太适合我,直到遇到使用 Golang 写的静态博客生成器 Hugo,仅需要一个可执行文件即可完成博客编写环境的搭建,简洁高效,正是我想要的。

安装

如果不需要二次定制的话,得益于 go 的特性,只需在 releases 里下载相应平台的可执行文件,复制或者链接到 PATH 的搜索路径下即可。

如果使用源码安装可以使用下面的方式,推荐使用发版本安装。

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

新建站点

安装完 hugo 之后即可立即开始:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
$ hugo new site blog
$ tree blog
blog
├── archetypes
│   └── default.md
├── config.toml
├── content
├── data
├── layouts
├── static
└── themes

其中 config.toml 是整个网站的配置文件,其他的文件夹的作用:

  • archetypes:包括内容类型,在创建新内容时自动生成内容的配置。
  • content:包括网站内容,全部使用 markdown 格式。
  • layouts:包括了网站的模版,决定内容如何呈现,目录下模板优先级高于 /themes/<THEME>/layouts/,可以用来小规模的定制主题。
  • static:包括了 css, js, fonts, media 等,决定网站的外观。

content 内支持任何级别的文件夹和文件的嵌套,最后的文件系统里 markdown 文件路径和最后站点 url 的对应关系一般如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
.
└── content
    └── about
    |   └── _index.md  // <- https://example.com/about/
    ├── post
    |   ├── firstpost.md   // <- https://example.com/post/firstpost/
    |   ├── happy
    |   |   └── ness.md  // <- https://example.com/post/happy/ness/
    |   └── secondpost.md  // <- https://example.com/post/secondpost/
    └── quote
        ├── first.md       // <- https://example.com/quote/first/
        └── second.md      // <- https://example.com/quote/second/

有两个文件名名字是有特殊含义的: _index.mdindex.md 可以用于组织页面,更多请参考 Hugo 官方文档。

使用主题

刚新建的 hugo 站点直接使用 hugo server 是跑不起来的,hugo 没有自带主题,所以需要在 hugo themes 找一个主题然后解压到 themes 文件夹下。

我的推荐方式是 fork 一个主题到你的 GitHub 上,然后使用 git submodule 的方式添加到 themes 里。比如这里我用的一个简洁风格主题 even(这个项目源于 hexo-theme-even),然后:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
$ git init
$ git submodule add https://github.com/creaink/hugo-theme-even themes/even
# 添加原本仓库,以便合并
$ git remote add even https://github.com/olOwOlo/hugo-theme-even
$ git remote -v
even    https://github.com/olOwOlo/hugo-theme-even (fetch)
even    https://github.com/olOwOlo/hugo-theme-even (push)
origin  https://github.com/creaink/hugo-theme-even (fetch)
origin  https://github.com/creaink/hugo-theme-even (push)

$ git pull even master

定制

第一次使用 even 需要将 themes/even/exampleSite/config.toml 模板配置文件复制到根目录,然后根据此文件来配置你的设置。有几个有必要修改的地方:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
# 这个是需要改的
baseURL = "https://creaink.github.io/"

# HTML head title
title

# footer 里的 author
[author]
  name = '<a href="https://creaink.github.com" class="theme-link">Creaink</a>'

[params]
  since = "2017"        # 站点建立时间
  logoTitle = "Even"    # 顶部 banner 标题
  keywords = ["Hugo", "theme", "even"] # HTML head meta keyword
  description = "Hugo theme even example site." # HTML head meta description

# 一些社交链接
[params.social]

之后更改下位于 themes/even/static/ 站点的图标,可以找一找适合做 favicon 的图然后在 favicon generator 生成各种尺寸的替换目录下(也可放于顶级 layout 目录下)的东西。

最后我还做了些其他的定制和修改,如在配置文件里:

 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
# 文章内连接用新标签打开
[blackfriday]
  hrefTargetBlank = true

# 谷歌分析,分析站点访问
googleAnalytics

# 来必力评论
livereUID

# 字数统计和预计时间
moreMeta = true

# 不蒜子统计阅读次数,siteUV sitePV pagePV 可自行设置
  [params.busuanzi]
    enable = true

# 增加友链和关于界面,之后新增 /content/links.md 和 /content/about.md 两篇文章即可
[[menu.main]]
  name = "Links"
  weight = 50
  identifier = "links"
  url = "/links/"
[[menu.main]]
  name = "About"
  weight = 60
  identifier = "about"
  url = "/about/"

最后再小小地修改了下样式(需要有 nodejs 和 yarn 环境,参考 even 项目主页)。

集成笔记

使用 hugo new post/start.md 命令就可根据 archetypes/default.md 模板创建一个 content/post/start.md 文件,然后 even 主题的 index 显示的就是 post section 内容的文章,然后使用 hugo server -D (-D 是允许草稿)随后就可以在主页看见了。

除了将 theme 作为 git submodule 之外,如果你习惯于用 markdown 写东西,并且利用文件系统来管理的话,可以将你的笔记做一个单独的 git repo 然后再作为 submodule 集成添加到 content/post 目录下。然后再写 markdown 的笔记时候,文件开头加上一个:

1
2
3
---
draft: true
---

之后照常即可,这样 hugo build 或者 server 时候不强制指定 draft 的话,这些笔记是不会被主动发现的。稍后要发表了,可以将 draft 改为 false 并且完善下其他属性内容(可以 hugo new 一个然后粘贴修改下),而 hugo 强大的性能可以保证在笔记文件众多的时候也可以高效完成这事。如果笔记众多需要统一添加 draft 开头的话可以使用 find . -iname "*.md" -exec sed -i '1i---\ndraft: true\n---' {} \; 命令来自动添加。

这样做的好处是可以随时保持笔记和博客文章的同步,避免出现笔记的记录和展现之间分叉。同时在使用 markdown 做笔记的同时能够随时分享。具体添加 submodule 参考 使用主题 里的步骤,这里就不再赘述了。

部署到 GitHub Pages

hugo 生成的静态文件会存放到 public 文件夹下,按照上面的套路,可以将其也作为 submodule 添加。步骤和前面的一致,也可参考 hosting on github

最后整个静态博客可能会使用到四个代码仓库,结构如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
[blog](hugo 生成的站点总仓库)
├── archetypes
│   └── default.md
├── config.toml
├── content
│   └── [post](markdown 笔记仓库)
├── data
├── [public](GitHub pages 仓库)
├── layouts
├── static
└── themes
    └── [even](fork 自 even 的主题仓库)

可以根据使用场景对不同的仓库设置为 public 或者 private,如这里由于某些隐私的问题我就将总站点仓库笔记仓库设置为私有(时值微软开放 GitHub private 仓库,为微软大法打 call)。

存在的问题

在结合 markdown 笔记和站点的时候遇到些问题,需要互相妥协。

图片问题,在写 markdown 笔记的时候我常常用相对路径来存一些简单的图片到本地,但是到 hugo 上,笔记工程是在 post 目录下,这样绝对路径肯定是不对的了,相对路径由文章 hello 于是 /post/hello/ 的样式导致相对路径也不对。

不过可以使用 uglyurls = true 配置使得其路径变为 /post/hello.html ,这样图片的相对路径就对了。但是,本地的图片不会有 draft 的配置,总是会在生成静态文件时候一股脑生成到 public 文件夹下,不过除了使用图床存图片好像也没有其他解决方法了,这样的话上文提到的菜单栏的 URL 就要更改下了,如 url = "/about.html",同时 tag 和 catalog 的页面的每一个 term 也会出现问题(even主题的锅),参考 uglyurl,比较蛋疼的是 hugo 作者不在模板里提供 uglyurl 这个参数的获取,只能够自己使用 Params.uglyURLs 冗余一遍然后修改模板。

同时大小写敏感导致找不到图片的问题可以使用 disablePathToLower = true 配置来解决。

markdown 引用,传统的 [ref](rel/to/file.md) 在静态站点上并用不了,能想出的解决方法就是当为发布的文章时候,把传统的注释掉然后换上站点上的绝对路径🤣。

自动更新修改日期,参考 configure-dates,只要是 enableGitInfo = true 那么 lastmod 是可以自动设置为该文件的 commit 时间,但是有一个问题就是 hugo 现在还不指定哪一个 repo 的 git,不知道以后有没有解决方法。

hugo 特性

常用命令

  • hugo new site SITE_NAME 生成静态博客项目
  • hugo server 启动本地服务器,加上 -D 可以渲染 draft
  • hugo new post/new-content.md 在 post 下新建一篇文章
  • hugo 生成站点静态文件
  • hugo list drafts/expired/future 列出特点的文件

Front Matter

markdown 文件开头的 --- 内的内容称为 Front Matter,用于文章生成的一些控制,Hugo 支持TOML、YAML、JSON格式的Front Matter,下面节选些常用的配置:

 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
---
title: "文章标题"
description: "文章的描述信息"
# 建议对标签的理解为:文章所涉及到的技术、内容点,越多越好
tags: [ "标签1", "标签2"]
# 建议对目录的理解为:文章所属的系列,越少越好
categories: [ "分类1", "分类2" ]
# 关键词用于 HTML head 的 keyword
keywords: [ "Hugo", "blog" ]
date: 2019-01-01
lastmod: 2019-1-1
# CJKLanguage: Chinese, Japanese, Korean
isCJKLanguage: true

# 如果draft为true,除非使用 --buildDrafts 参数,否则不会发布文章
draft: false

# 设置文章的过期时间,已过期的文章不会发布,除非使用 --buildExpired 参数
expiryDate: 2020-01-01

# 设置文章的发布时间,未来的文章不会发布,除非使用 --buildFuture 参数
publishDate: 2020-01-01

# 文章排序权重
weight: 40

# 还有些有些第三方自定义的
comment: true
hiddenFromHomePage: false
contentCopyright: true
reward: false
---

有些配置是可以指定默认值,不必每一篇都这样设置,可以参考 configure-front-matter

这里夹带点自己对博客的 categories 和 keywords 的理解:

  • categories: 目录,文章所属的系列,关键词越少越好,也可理解为专栏。
  • tags: 标签,文章所涉及到的技术、内容、知识点,关键词越多越好。

内容摘要

Hugo 会自动提取文章的前 70 个字符(hasCJKLanguage )作为摘要,可以在文章内使用 <!--more--> 注释进行强行分割。

Shortcodes

Shortcodes 帮助你在编写 markdown 时快捷的插入 HTML 代码,也可以编写自己的 Shortcodes,放置于根目录或者主题目录的 layouts/shortcodes 下。

Even 主题便捷

Even 主题提供了很多方便地方,如 markdown 的一些扩展,具体可以参考 even/exampleSite/content 内的文章。

参考