使用 Hugo 一年半了,终于有了升级的动力。趁着最近事情不多,着手搞定了这个事情,记录下来分享给需要的同学。

背景

在这一年半里,我一直使用着的是老版本:v0.20.7 ,运行非常稳定,写完文章 Git Push 后,GitLab Runner 自动更新预览地址,浏览没问题就可以一键发布了。

但是下面两个问题让我有了升级的想法。

构建速度随着内容增多变慢

去年十月,在网站架构简化之后,我的完整发布编译时间从 1分钟 进入了 40s 的阶段,但是随着内容的膨胀、编译时间越来越慢了,可以看到不少发布时间变长。

类似这种编译时间超过45s不在少数

看着 Hugo 升级 Golang 版本,重构优化速度变的越来越快,不免心动。

需要维护两套不同版本的站点

一月初将博客主题重构之后,分享给了公司技术团队博客使用,考虑可维护性,新站点使用的是最新版本 0.53 ,因为技术团队站点功能更简单,所以升级过程中不兼容的部分其实并不多。

但是这个站点,因为自定义了“年月日”格式的归档,以及使用的是老版本的模板查找逻辑,生成页面链接也不完全兼容,所以直接升级是不行的。

可以预期的是,随着使用时间越来越长,这两个站点的差异会越来越大,为了可维护性,必须将这两个站点使用的 Hugo 版本统一

梳理主要问题

  1. 官方支持 RSS 文件直接输出,是否还需要自定义站点 RSS 文件?
  2. 官方直接提供压缩能力,是否足够替换 Pipeline 中定制的压缩服务?
  3. 页面模板查找逻辑、模板语法、站点配置文件变更,现有模板无法直接使用。
  4. 分类标签系统扁平化,不再支持树形层级嵌套,链接兼容如何处理?

下面我来逐个击破。

Hugo RSS 解决方案

官方支持了 RSS 格式的输出,只要在 layouts 根目录创建一个文件即可,index.rss.xml,模板可以自定义,参考官方文档 。

另外官方生成文档,默认会输出正确的 XML Version,所以可以检查并删除己配置的文档模板中下面的内容。

但是这样会有两个额外的问题,第一个是额外生成了许多不需要生成的“订阅源”,像是下面这样。

公司的技术团队博客可以保留这个功能,但是我个人一来更新频率没有那么高,二来我希望订阅源唯一可控,所以这些多余的内容我是要干掉的。

第二个问题是官方 RSS 输出内容不支持自定义路径,你的订阅地址就只能是下面这样:

使用老版本的 RSS 方案,创建一个 /feed ,然后放置自定义的 RSS 模板,你会发现生成内容,仅支持该目录之下的文章… ORZ

如果你有类似的需求,这里更好的方案是“禁用官方RSS生成能力”、“自定义RSS模板”,可以做到按照你的需求在你期望的路径生成你期望数量的 RSS 内容。

首先是禁用官方RSS生成能力,在站点 config.toml 配置文件中添加下面的内容:

如果你有定义 output 格式,并包含 RSS 定义,也需要删除该内容。

接着分别创建 layouts/feed/index.htmlcontent/feed/index.md 两个文件。

模板文件内容可以参考:

页面数据文件,示例文件:

再次执行 hugo 进行站点生成,会发现生成的页面内容已经大幅减少:

最后别忘记通过构建脚本,将生成文件进行重命名。

更好的Hugo页面压缩能力

在使用 Hugo 版本和之前的压缩模式进行对比,发现 Hugo 压缩确实效率高不少,添加压缩参数 --minify 执行 Hugo ,生成时间几乎没有变化,还能省下 GitLab Pipeline 调用 Job 过程中的时间损耗,真的是太赞了。

但是压缩结果完全使用了 HTML 宽松模式,所有的 HTML Tag 属性都失去了引号,像是下面这样。

个人倾向使用相对严格的模式进行页面结构编写,即使是程序生成的代码也是如此,因为一旦要进行调试,相对严格的标准,可以消除很多歧义,减少不必要的调试时间。

由于老版本不支持 --minify 参数,所以我使用 Node.js 简单写了一个脚本,用于替换页面内的空白内容,程序处理内容比较多,贴一下主要替换逻辑供参考。

如果你和我一样,定制了代码高亮,有更复杂的 HTML 结构,那么还需要额外处理一下代码,避免出现 ReDoS 问题。

模板配置相关处理

首先贴出我的站点主题的 layouts 目录结构(部分),它也代表了网站的整个抽象逻辑。

现在的查找逻辑不是十分合理,为了避免构建时的警告信息,我使用 layouts/_default 接管了标签和分类的模板入口,其余的入口页面依旧放在 layouts 子目录的各同名目录下,比如 layouts/post/single.html

所有页面的真实处理逻辑放置在 layouts/partials/pages 中,可以最大限度保障页面主题的兼容性,比如这次,我就只是修改了入口页面的位置,而页面处理逻辑没有大动。

额外说明一点,_default 目录下的文件,只有 tagtopics 相关三个文件存在内容,其余文件保持为空即可。

新版本 Hugo 针对 config.toml 也有升级策略,直接执行 hugo ,配置文件中的问题,它会进行报错提示,并示例你如何更改,比如这样:

分类和标签扁平化以及其他兼容处理

在 Hugo 升级之前,我使用的是这样的分类结构:

老版本的 Hugo 会自动生成两级分类目录,并且两个目录都支持索引,像是下面这样。

而新版本会生成唯一的分类,并且使用自己的策略转义链接地址中的空格和斜杠为连字符

在思考之后,我发现除了做接口需要表明资源从属关系之外,除非写书似乎文章还真的不太需要那么明确的层级隔离,一级目录外加标签就能提供良好的阅读和解决简单检索需求。

所以我进行了简化,去掉了所有的文章分类从属关系。但是我还有一个按照年月日进行日期归档的路径,这里还是希望能够保持从属关系的,该怎么解决呢?

首先归档内容,暂时还是需要自己手动生成并维护的,下面是我使用另外一个脚本在每次文章发布时生成的目录结构(部分)。

这里使用了一个取巧的方案,使用 _index.md 会枚举目录中所有内容的特性,可以轻松生成下面的结构。

接下来只需要修正原本模板中引用地址 /archives/YEAR//archives/YEAR.html 即可

上文提到过 Hugo 新版本对于链接的一些额外转义处理,除了分类会被影响外,标签也被波及到了。

举个例子,我原本有一个标签叫做 : Linux/Mac ,在旧版本的 Hugo 中的输出结果是这样:

但是在新版本变成了这样:

因为我禁用了 RSS ,暂时不提供标签的订阅,文章内直接引用标签目前也比较少,访问地址变了就变了,但是模板中如果直接使用老版本的语法,标签地址生成的还是老样子(生成链接策略和渲染逻辑不一致),结果就是含有空格和斜杠的标签页面是无法正常浏览的!

举个例子,老版本语法:

解决方式比较 trick,需要手动在模板中进行转义,并补全 .html 后缀:

至此,升级过程中的主要问题就都讲完了,我们接下来聊聊性能提升和其他的话题。

性能和一些其他的事情

这次重构之后,完整发布时间缩短了 10s ,构建时间减少了 3s ,后续计划将预览和生产的流水线进行分割,应该还能进一步提升效率。

完整发布时间35s左右

在做这次升级重构之前,我首先考虑了最低成本升级到的完全兼容的可用版本。

v0.20.7 @bep bep released this on 3 May 2017 · 1666 commits to master since this release

在试验之后,我发现唯一和我当前版本兼容的是 v0.21,也是一个古董版本,没有实质的变化。

v0.47.1 Hugo Static Site Generator v0.47.1 darwin/amd64 BuildDate: 2018-08-20T08:16:52Z

稍微做一些重构,能够接触到的最新版本则是上面的 0.47.1 ,也还是低版本的 Golang,并且和主流版本差异还是不小。

我使用 hugo benchmark 比较了 0.20.x0.40.x 发现性能不升反降,期初我也很犹豫是否值得进行升级。

v0.20.x

v0.40.x

于是我认真浏览了这一年半以来的版本发布记录,觉得还是要与时俱进,如果不升级,将一直锁定在低版本的 Golang 运行时,所有的问题也都只能自己定制解决,完全不能使用社区新功能、也不把折腾内容贡献社区。

在使用两个简单站点分别使用 v0.20 和 v0.50 进行测试的时候,我发现提升还是很明显的,于是便着手进行了升级,附加调试完整花费了6个小时。

很可惜在 v0.50.3 版本之后,官方废弃了 hugo benchmark 这个命令,所以我们不能够和以往一样输出性能报告,不过直接使用站点生成时间来进行对比,也是一样的(站点实际构建时间)。

可以看到效果还是不错的,另外我也终于可以使用 brew install hugo 安装的 Hugo 更方便的进行本地预览了。

最后

下篇内容,我们继续聊聊 Wiki 系统的搭建。

—EOF