本文使用「署名 4.0 国际 (CC BY 4.0)」许可协议,欢迎转载、或重新修改使用,但需要注明来源。 [署名 4.0 国际 (CC BY 4.0)](https://creativecommons.org/licenses/by/4.0/deed.zh) 本文作者: 苏洋 创建时间: 2020年11月08日 统计字数: 5690字 阅读时间: 12分钟阅读 本文链接: https://soulteary.com/2020/11/08/upgrade-hugo-across-versions-2.html ----- # Hugo 跨版本升级(二) 上一次折腾 Hugo 是[去年年初的](https://soulteary.com/2019/02/01/upgrade-hugo-across-versions.html)事情了,时隔接近两年,再次记录下 Hugo 跨版本升级的一些实践细节。 ## 写在前面 为什么接近两年时间里,没有继续折腾 Hugo 呢? 1. 我对网站模版功能没有变动诉求。这个模版虽然写于六年前,但是不论性能还是基础功能、以及在资源有限的老设备上运行,都没有什么问题。在三年前移植到 Hugo 模版后,除了为增强 Hugo 日志归档功能,添加了一个功能外,就没有变动了。 2. Hugo 跨版本升级,总会出现一些 Break Changes 的问题。在 0.5x 版本和 0.6x 版本中,官网引入了一些导致 Crash 的问题,让我没有升级的兴趣。 那为什么有了这篇文章呢?主要也有两个原因: 1. 最近业务上有静态网站诉求,使用 Hugo 0.78 构建了一个站点,目测后续还有更多的需求,所以需要深度使用踩踩坑。相比较零到一的站点,我的网站有上千篇内容,以及各种场景站点的模块,作为试验田非常合适。 2. 为了统一软件技术栈,减少维护心智负担,只要新版本的软件在数据和功能处理方面没有问题,我一般会将其升级到新的稳定版本。 ## 主要调整内容 因为之前使用 Hugo 模版功能,将一些场景的功能模块,以及这几个版本中 Hugo 变动了的接口都实现和封装了一遍,比如:页面布局模块、RSS、归档页面、多级文档目录、网站地图... 所以这里升级只需要调整一些简单的数据字段引用,或者调整下配置文件,最多是调整下 Nginx 配置,除了调试编写模版外,过程还是比较轻松的。 ### 修正路由变化的独立页面 在升级到 0.78 之后,Hugo 配置文件中的 `uglyurls = true` 配置项会使得独立的页面路由发生一些变化,从原来的 `/pageName` 变为 `/pageName.html` 。 如果你想让页面 URL 保持和原来一致,可以考虑参考 [内容组织管理](https://gohugo.io/content-management/organization/#url-1) 文档中的方案,在 Markdown 文档中添加下面的参数来声明页面的地址。 ```Toml url = /page-url/ ``` ### 部分模版使用 Page 变量获取数据为空 在升级之后,有一部分模版页面使用 `where` 函数获取站点数据会出现异常。 ```html {{ $Posts := where .Pages "Section" "post" }} {{ range $Posts }}

{{.Title}}

{{end}} ``` 这时可以尝试使用带有 `$.Site` 命名空间,或者更换方法为 `RegularPages` 来获取数据。 ```html {{ $Posts := where $.Site.Pages "Section" "post" }} {{ range $Posts }}

{{.Title}}

{{end}} {{ $Posts := where .RegularPages "Section" "post" }} {{ range $Posts }}

{{.Title}}

{{end}} ``` ### 标签分类地址兼容 [上篇文章中](https://soulteary.com/2019/02/01/upgrade-hugo-across-versions.html)有提过对一些带有特殊符号的标签,做了 URL 兼容,避免它生成多级目录。 官方对于标签/分类的 URL 生成其实一直很摇摆,挨着时间顺序看以下讨论,可以发现官方原本的方向是朝着标准的网站实现去做的: - [https://github.com/gohugoio/hugo/issues/3577](https://github.com/gohugoio/hugo/issues/3577) - [https://github.com/gohugoio/hugo/issues/4090](https://github.com/gohugoio/hugo/issues/4090) - [https://github.com/gohugoio/hugo/pull/5519/files](https://github.com/gohugoio/hugo/pull/5519/files) - [https://github.com/gohugoio/hugo/pull/5282](https://github.com/gohugoio/hugo/pull/5282) 但是在有一天,一个老外反馈他使用 Hugo 做地理位置多级联动的站点,官方便对上面的实现方案产生了动摇,最终将 Hugo 的标签生成模式改成了下面这样: - [https://github.com/gohugoio/hugo/issues/5571](https://github.com/gohugoio/hugo/issues/5571) 并且单独开了一个帖子,讨论未来如何增强调整标签/分类的地址: - [https://github.com/gohugoio/hugo/issues/5520](https://github.com/gohugoio/hugo/issues/5520) 为了不"伤经动骨",单独编译一个定制版出来。这里可以使用 Nginx 处理链接兼容问题,让原来的 `/tags/linux-mac.mac` 这类只有两级目录的标签/分类转向到类似 `/tags/linux/mac.html`的地址。 ```c location = /tags/linux-mac.html { return 301 https://$host/tags/linux/mac.html; } location ~* ^/tags/linux-mac/(page/.*)$ { return 301 https://$host/tags/linux/mac/$1; } ``` ### 使用自定义模版输出 RSS [上篇文章中](https://soulteary.com/2019/02/01/upgrade-hugo-across-versions.html)我有提过我使用自定义的模版替换了官方实现的 RSS 生成,所以在官方修改 RSS 输出实现后,也就幸免,省的折腾了。 ### 调整客户端输出页面为服务端 之前生成日志归档,主要依赖前端脚本和 Hugo 模版,以及脚本生成的列表数据进行页面生成。 比如先使用工具生成类似下面年份或者月份的内容: ```md --- title: "2020年文章存档" type: archives draft: false isCJKLanguage: true outputs: [ "HTML"] --- ## [2020年11月](/archives/2020/11/) - `01` [使用 Nginx 构建前端日志统计服务(打点采集)服务](/2020/11/01/use-nginx-to-build-a-front-end-log-statistics-service-service.html) ## [2020年10月](/archives/2020/10/) - `31` [阿里云 IP 地理位置库(淘宝IP库)实践(后篇)](/2020/10/31/dockerize-aliyun-geoip-part-2.html) - `30` [阿里云 IP 地理位置库(淘宝IP库)实践(前篇)](/2020/10/30/dockerize-aliyun-geoip-part-1.html) - `04` [容器化 FRP 使用方案](/2020/10/04/frp-in-docker.html) ... ``` Hugo 模版调整为方便 JavaScript 使用的模式就行,将上面的内容,直接输出在页面中: ```html

{{ .scope.Title }}

``` 最后使用类似下面的模式拼合 HTML 片段,然后呈现在页面上: ```js ... function makeYearTemplate(years, isCurrentYear, hasFiltered) { var tpl = []; if (isCurrentYear) { tpl.push('
'); } else { tpl.push('
'); } for (var i = 0, j = years.length; i < j; i++) { if (!isCurrentYear) tpl.push(''); } if (isCurrentYear) { tpl.push('
'); } else { tpl.push('
'); } return tpl.join(''); } ... function main() { var container = document.querySelector('.all-archive-container'); var dataContainer = document.getElementById('archives-data'); var filterYear = container.getAttribute('data-year-filter'); filterYear = filterYear ? parseInt(filterYear) : 0; var filterMonth = container.getAttribute('data-month-filter'); filterMonth = filterMonth ? parseInt(filterMonth) : 0; if (container) { showMainPage(container, filterYear); if (!dataContainer) return; if (filterYear) { var data = getSource(dataContainer); return archiveList(data, filterYear); } } } ``` 这样做的好处是 Hugo 什么活都不用干,逻辑极其简单,其实就几个不到二十行的 HTML 模版,完全依赖外部脚本和浏览器中的客户端 JavaScript 生成,浏览器访问的时候,页面传输量也小。 但是劣势也很明显,内容无法被搜索引擎检索,页面第一次打开后会抖动一次,尤其是和其他直接服务端渲染的页面对比时,另外这个页面必须依赖客户端 JavaScript 执行正常。 而这套来自六年前的模版主题有一个隐藏的特点是“JavaScript Less Support”,即使客户端不支持运行 JavaScript,其实也不影响页面内容的浏览,所以这次调整的时候的想法之一就是把内容归档交付给 Hugo 来渲染,让这个“特点”完整起来。 按照上面的思路来做,需要编写一个 Hugo 模版,像是下面这样: ```html {{ $allowYearList := ($.Site.RegularPages.GroupByDate "2006" "desc") }} {{ $archiveData := $.Site.Data.archive.years }} {{ $archiveListData := $.Site.Data.archive.years.list }}
{{ $currentYear := first 1 $allowYearList }} {{ range $currentYear }} {{ $yearName := .Key }} {{ range $archiveListData }} {{ $dataId := string .id }} {{ if eq $dataId $yearName }} {{end}} {{end}} {{ end }} {{ $YearCount := len $allowYearList }} {{ $YearExcludeCount := 1}} {{ $YearGroupLimit := 4}} {{ $groupCount := math.Ceil (div (sub (float $YearCount) $YearExcludeCount) $YearGroupLimit ) }} {{ range $gid := seq $groupCount }}
{{ range $yid, $sequence := seq $YearCount }} {{ if gt $yid 0 }} {{ $groupId := add (div (sub $yid $YearExcludeCount) $YearGroupLimit ) 1 }} {{if eq $groupId $gid}} {{ $yearName := $yid }} {{ $yearData := index $archiveListData $yid }} {{ end }} {{ end }} {{ end }}
{{ end }}
``` 按照这样编写的模版,可以自动筛选适合展示的,以年份或者月份归档的文章,生成对应的页面,不光是简化了客户端 JavaScript 的实现,原本用来分析文章时间生成归档文件的逻辑也可以省略掉了。 ## 最后 原本以为从 Hugo 0.20 升级到 0.50 ,性能提升已经接近瓶颈,没想到在 0.70 的版本里,Hugo 构建速度能够更进一步,更加期待接下来 Go 运行时和编译器的更重磅的性能优化了。 接下来我会试验使用 Hugo 创建几十万页面,并进行内容的快速更新迭代,或许结果会颠覆之前对于静态网站生成器的看法也说不定。 --EOF