Hugo 迁移记录

2019-01-18 03:40编辑本页

前提

前几天想实现一个在博客的 front-matter 中加入一个标签来区分生活类文章和技术类文章,在首页的表现为链接的颜色不同的功能。

问题

在 Hexo 官网及 Google 中寻找解决方案的过程中发现了几个问题:

  1. Hexo 使用的默认模板 paularmstrong/swig 已经不再维护了1
  2. 在 Hexo 官网看文档时依然简陋,

之前在 hexo + gitlab 服务隐藏静态文件 中提到的:

之前有过使用 hexo 的经验,又加上 node 的生态在个人博客这里又异常繁荣(大量node相关的第三方插件可用),还是决定迁移过来了。

其实也是造成问题一的原因之一。关于问题一在 hexo 的 github issue 中也有讨论:Why not totally replace Swig with Nunjucks? #1593,其中 mozilla/nunjucks2 作为 Swig 模板的升级,可以作为 Swig 不再维护后的替代品。但 hexo 项目这边对默认模板的替换就没那么迅速了。3

问题 2 则是由来已久,对主题开发者不友好的程度有点高。这里可以看其中一位主题开发者的感受 https://blessing.studio/get-hexo-posts-by-category-or-tag/ :

今天在将博客主题移植至 Hexo 时,想要获取某个分类(Category)或者标签(Tag)下的所有文章(准确来说是想获得文章总数),在使用中文关键词搜索时,没有获得任何有用的信息(或许是我搜索姿势不对)。换用英文关键词「hexo category all posts」后搜索到了所需的信息,遂决定写一篇文章记录一下,希望能帮到后来人。~~~~

这里不得不吐槽一下,Hexo 的文档真是太烂了,太烂了。写个主题,有时候想要实现一个功能还要疯狂看 Hexo 源码,说不出话。

解决方案

我的解决方案如下:

我的解决过程正好是按照我列举的顺序来的

方案 1

由于 hexo 对 front-matter 自定义的部分需要涉及 hexo 引入脚本,而我并不想在 hexo 这个框架本身花费过多时间。于是放弃。
在过程中,我使用了另一种方法:用一个不常用的 front-matter 字段用来标识链接颜色(我使用的是 layout 这个变量)。实际效果也实现了类似的效果,可是代码看上去很容易让人困惑。4

方案 2

nunjucks 只有几个 hexo 相关插件,hexo-renderer-nunjuckshexo-nunjucks,打开来看就知道这两个项目的最后更新时间都锁定在三年前。于是放弃。

方案 3

这几个模板引擎直接由 Hexo 官方的支持,然而在实际使用的时候,还是觉得那个过了时的 swig 会稍好一点(主要是符合我对模板的印象)。于是放弃。

方案 4

先列一下备选项:5

  • JavaScript: Next.js & Gatsby (for React), Nuxt & VuePress (for Vue), Hexo, Eleventy, GitBook, Metalsmith, Harp, Spike.
  • Python: Pelican, MkDocs, Cactus.
  • Ruby: Jekyll, Middleman, Nanoc, Octopress.
  • Go: Hugo, InkPaper.
  • .NET: Wyam, pretzel.

我最终在这些中选择了 Hugo,主要是因为最近正好在学习 Go。而 Hugo 使用的 “text/template” 也算是一个 Golang 的一个标准扩展模块了,应该不会像 Hexo 那样多模板之间来回跳转^(问题1)^。 Hugo 的官方文档也是肉眼可见的多^(问题2)^。

实现

确定了解决方案,再理一下选择当前解决方案后要做的事:

  1. 文章迁移
  2. 模板迁移6
  3. 功能实现
  4. 部署方案

1. 文章迁移

其中,由于同是 markdown 写的文章,迁移大概就是一条 cp 命令。这里就不详述了。

2. 模板迁移

在模板的部分,基本就是像素级的 COPY:将 swig 模板实现的功能按行级别的使用 text/template 实现。当然语法风格是按照 Hugo 官方文档中的来写。
在过程中还是遇到了几个小难点,这里将解决方案也一并贴出来:

  1. 在实现 /tags/ 页面的时候,需要先将文章按照标签分组,再依次将各组标签中的文章遍历出来。在 text/template 中,变量的作用域非常奇怪,代码如下:

1
2
3
4
5
{{ $v := "init" }}
{{ if true }}
    {{ $v := "changed" }}
{{ end }}
v: {{ $v }} {{/* => init */}}

这一段代码以直觉判断,$v应该是输出 "changed"。然而实际结果有点意外。我对此情况的理解:在 text/template 的实现中,每个代码块拥有独立的作用域,这个作用域在遇到嵌套时也不会发生继承。
这样实现起来的代码应该是最干净并简单的。遇到这种情况,官方的建议是使用 .Scratch 来创建一个页面级作用域可读可写的变量,然而这对于主题模板来说有点重了。

在 Google 的帮助下,找到这样一段 gist: https://gist.github.com/Xeoncross/203d8b1459463a153a3c734c98b342a9

1
2
3
4
5
6
7
         <ul class="tags">
            {{ range $name, $taxonomy := .Site.Taxonomies.tags }}
              <li><a style="text-transform: capitalize" href="#{{ $name | urlize}}">{{ $name }}</a>
                <!-- <span>({{ len $taxonomy }})</span> -->
              </li>
            {{ end }}
          </ul>
  1. 在有目录的文章中,发现自动渲染出的目录会有空的 · 出现。

在某些文章中,我的小标题是以 <h3> 来标识的,而自动生成目录的模板却没弄自动去掉未使用的 <h1><h2>

1
2
·    ·    · 三级标题 1
          · 三级标题 2

这在 Hugo 的 Github Issue 中也有体现:Heading levels in Markdown table of contents #1778,而这里又牵涉到另外一个问题,就是 Hugo 使用的 markdown 渲染模板 russross/blackfriday 在 Hugo 中还是 v1 的版本,而 v1 版本解析 markdown 后的输出结果是一段 HTML,因为这个,在生成 .TableOfContents 的时候有这样一段很丑陋低效的代码:https://github.com/gohugoio/hugo/blob/master/helpers/content.go#L416

 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
33
34
35
36
37
func ExtractTOC(content []byte) (newcontent []byte, toc []byte) {
    if !bytes.Contains(content, []byte("<nav>")) {
        return content, nil
    }
    origContent := make([]byte, len(content))
    copy(origContent, content)
    first := []byte(`<nav>
<ul>`)

    last := []byte(`</ul>
</nav>`)

    replacement := []byte(`<nav id="TableOfContents">
<ul>`)

    startOfTOC := bytes.Index(content, first)

    peekEnd := len(content)
    if peekEnd > 70+startOfTOC {
        peekEnd = 70 + startOfTOC
    }

    if startOfTOC < 0 {
        return stripEmptyNav(content), toc
    }
    // Need to peek ahead to see if this nav element is actually the right one.
    correctNav := bytes.Index(content[startOfTOC:peekEnd], []byte(`<li><a href="#`))
    if correctNav < 0 { // no match found
        return content, toc
    }
    lengthOfTOC := bytes.Index(content[startOfTOC:], last) + len(last)
    endOfTOC := startOfTOC + lengthOfTOC

    newcontent = append(content[:startOfTOC], content[endOfTOC:]...)
    toc = append(replacement, origContent[startOfTOC+len(first):endOfTOC]...)
    return
}

在这里是用处理字符串的方法来解析 .Content 中的内容,再将其要生成的内容拼凑成几块,再加到原内容中。这个问题在 blackfriday.v2 中得以解决,即输出一个 AST 再交由其他程序处理,这样也能保证后续版本的兼容性。但在 Hugo 中,作者也是多次推迟该特性的里程碑 Upgrade to Blackfriday v2 #39497

但是在 Issue 讨论中,各路大神也提出了自己的解决方案,可以点进 Heading levels in Markdown table of contents #1778 中去查看详情。我采用了这其中的模板解决方案:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
            {{ $toc := .TableOfContents }}
            {{ $toc := (replace $toc "<ul>\n<li>\n<ul>" "<ul>") }}
            {{ $toc := (replace $toc "<ul>\n<li>\n<ul>" "<ul>") }}
            {{ $toc := (replace $toc "<ul>\n<li>\n<ul>" "<ul>") }}
            {{ $toc := (replace $toc "</ul></li>\n</ul>" "</ul>") }}
            {{ $toc := (replace $toc "</ul></li>\n</ul>" "</ul>") }}
            {{ $toc := (replace $toc "</ul></li>\n</ul>" "</ul>") }}
            <!-- count the number of remaining li tags -->
            <!-- and only display ToC if more than 1, otherwise why bother -->
            {{ if gt (len (split $toc "<li>")) 2 }}
              {{ safeHTML $toc }}
            {{ end }}
          {{ end }}

这个方案在日后的特性版本合并后可以简单的移除模板中的相应部分。算是比较简便的一种。

3. 功能实现

模板迁移完成之后,就开始把最初想实现的功能实现出来。在 Hexo 中,front-matter 可以填写用户自定义的字段(文档见:https://gohugo.io/variables/page/#page-level-params)。
我这里选择使用 linkcolor 字段。

1
2
3
4
---
title: test
linkcolor: #7076c7
---

再在主页遍历标题的地方加入该变量的判断:

1
2
3
4
5
6
{{ if .Params.linkcolor }}
{{ $color := .Params.linkcolor }}
<a href="{{ .Permalink }}" class="post-list-item" style="color:{{ $color }};">
{{ else }}
                    <a href="{{ .Permalink }}" class="post-list-item">
{{ end }}

我觉得每次写一个抽象的颜色 #7076c7 看起来不好看,又在 data/color.toml 目录中加入以下内容(文档见:https://gohugo.io/templates/data-templates/

1
2
[link]
blue = "#7076c7"

修改主页模板

1
2
3
4
5
6
{{ if .Params.linkcolor }}
{{ $color := index .Site.Data.color.link .Params.linkcolor }}
<a href="{{ .Permalink }}" class="post-list-item" style="color:{{ $color }};">
{{ else }}
                    <a href="{{ .Permalink }}" class="post-list-item">
{{ end }}

这样在 front-matter 中只需要写 linkcolor: blue 就可以达到相同的效果了。之后想有其他的颜色相关扩展功能,也可以方便的实现。

4. 部署方案

这里官方文档讲的很详细,我是使用 Netlify 平台发布博客的,文档在 https://gohugo.io/hosting-and-deployment/hosting-on-netlify/
其他常用平台在文档中也有讲到。

这里主要是讲对老博客的提交记录的迁移,我这里使用了将 Hexo 博客的文件及目录放入一个单独目录 hexo_archive 中,将 Hugo 平台的代码放在项目根目录中,这样,之前的提交记录和文件得以保留,又留下了一个对于 Hugo 相对干净的目录。

See Also


  1. github 页面 https://github.com/paularmstrong/swig 已经显示:“This repository has been archived by the owner. It is now read-only.”
  2. 看到 mozilla 这个前缀,感觉 Nunjucks 这个项目应该是会稳定维护一段时间
  3. 截止 2019-01-18日,在 hexo 项目中有看到相关的 PR Replace default swig engine with nunjucks #2903
  4. 代码详见:https://github.com/wukra/izhengfan.github.io/commit/2e3d6782b1bf5c43d149fabe961b7fb09c84a2c5
  5. 来自 https://snipcart.com/blog/choose-best-static-site-generator
  6. NexT & izhengfan 结合主题 文章中提到,我把这个主题从 Jekyll 迁移到 Hexo。我很喜欢现在这个主题
  7. 截止 2019-01-18日,这个特性本打算在 2017年10月的 v0.31 版本加入,却一推再推,到现在是放在了 v0.55 发布计划中

标签: hugo go

点击加载Disqus评论
Creative Commons © 2003 — 2019 xiaocang | Theme based on fzheng.me & NexT | Hosted by Netlify