目录

「Typecho2Hugo」你伤害了我,却一笑而过

记录从基于 Typecho 框架的动态博客迁移到基于 Hugo 框架的静态博客的过程。

缘起

之前的博客搭建在私有服务器上,如果服务器迁移会面临迁移博客的问题。动态博客涉及数据库,虽然小博客使用简单的 SQLite 已经足够,但还是较为麻烦。另一个就是文章的更新问题,之前使用 PyTypecho 提供的 API 来进行文章更新1,但这需要维护一套业务逻辑代码来进行本地和远程端文章的匹配并按需更新,流程复杂,且长期来看并不稳定。此外,相应文章的评论维护(比如垃圾评论)也是较为麻烦,不利于轻装上阵,聚焦于内容产出。

https://store.yirami.xyz/review/typecho_to_hugo/classics_spam_comments_preview.png

基于此,考虑使用 Git+Hugo+Giscus(待定)+Cloudflare 的方案。使用 Git 可以跟踪原始文稿的改动并轻松的多端备份,使用简单的 Python 脚本可以完成草稿格式的转换(主要是 front matter),使用 Giscus 等评论系统集成评论功能,再使用 Cloudflare 自动化托管网站并进行加速。

准备

目前我主要有两个博客。一个用于学习整理古文献,主要是需要支持渲染注音。一个用于整理学术工作向的笔记,主要是需要支持渲染大量公式。

原方案

采用的方案是:

  • Typecho: v1.2.1

  • Initial: v2.5.3

  • MDRuby: v0.2

  • MathJax: 主题设置中在底部添加如下代码

    <script>
    window.MathJax = {
    tex: {
        inlineMath: [ ['$','$'], ["\\(","\\)"] ],   // 行内公式选择符
        displayMath: [ ['$$','$$'], ["\\[","\\]"] ],   // 段内公式选择符
        autoload: {
        color: [],
        colorv2: ['color']
        },
        packages: {'[+]': ['noerrors']}
    },
    options: {
        skipHtmlTags: ['script', 'noscript', 'style', 'textarea', 'pre','code','a'],   // 避开某些标签
        ignoreHtmlClass: "comment-content",   // 避开含该Class的标签
        processHtmlClass: 'tex2jax_process'
    },
    loader: {
        load: ['[tex]/noerrors']
    }
    };
    </script>
    <script src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js" id="MathJax-script"></script>
妄境
  • 主题效果 https://store.yirami.xyz/review/typecho_to_hugo/classics_preview.png
  • 注音效果 https://store.yirami.xyz/review/typecho_to_hugo/classics_zhuyin_preview.png
温故
  • LaTex 渲染效果 https://store.yirami.xyz/review/typecho_to_hugo/review_latex_preview.png

新方案

基于 Hugo 的方案是:

感谢 LoveIt,已经集成了 KaTex 并且兼容前述 MDRuby 注音的 Markdown 语法,所以只要搞定公式渲染即可。

因为之前折腾过 KaTex,发现有很多公式符号无法渲染,最后选择了 MathJax,这次也就打算直接尝试集成 MathJax

搞起

呸,MathJax

由于对 JS 技术所知不多,约等于没有,所以 Google 一通搜索,坚信肯定有大佬干过这事儿。果不其然,被我逮到俩需求不能说相近,只能说一模一样的,后一篇甚至连主题都一样,大事可成矣。

  1. 首先在主题 layouts/partials/plugin/ 目录下创建 mathjax_support.html 文件,内容如下:
    <script>
    MathJax = {
        tex: {
        inlineMath: [['$', '$'], ['\\(', '\\)']],
        displayMath: [['$$','$$'], ['\\[', '\\]']],
        processEscapes: true,
        processEnvironments: true
        },
        options: {
        skipHtmlTags: ['script', 'noscript', 'style', 'textarea', 'pre']
        }
    };
    
    window.addEventListener('load', (event) => {
        document.querySelectorAll("mjx-container").forEach(function(x){
            x.parentElement.classList += 'has-jax'})
        });
    
    </script>
    <script src="https://polyfill.io/v3/polyfill.min.js?features=es6"></script>
    <script type="text/javascript" id="MathJax-script" async
    src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js"></script>
  2. 然后在主题 layouts/partials/head/ 目录下创建 mathjax.html 文件,内容如下:
    {{ if .Params.mathjax }} {{ partial "plugin/mathjax_support.html" . }} {{ end }}
  3. 接着修改主题 layouts/_default/baseof.html 文件,并在 <head>...</head> 标签内按如下示意插入内容:
    <head>
    ...
    {{- partial "head/mathjax.html" . -}}
    ...        
    </head>
  4. 最后在需要启动 MathJax 的文章的 front matter 中做如下设置2
    mathjax: true

但是,但是,但是。。。

上述一通操作后,对于拥有大量公式的文章总有某些公式渲染异常,或者刷新页面后有些本来可以渲染的公式又无法渲染了。已知 MathJax 如此强大与优秀,我也不是没有试着挣扎一下就立刻投降。经过一通 Google + GPT

  1. 部分公式加载异常可能与默认使用的 goldmark 解析引擎有关,为确保正确解析,可以使用 Hugoshortcodes 特性。主题 LoveIt 在原生 shortcodes 上做了一些扩展,其中 raw 扩展可以用来辅助在文章中插入原始 HTML 内容,避免转义。我们只需要在 LaTex 块两侧包上 raw 标记即可。不过我不想因此“污染”原始稿件,也想给将来可能的技术升级(featurebug)留悬念。这个好办,祭出 Python,写个正则转换即可。

  2. 另一个刷新后公式不渲染的问题则麻烦了些,好像是与页面缓存有关,又好像是应该把 MathJax 脚本的加载逻辑放置在 <body>...</body> 标签底部,但经过奋力尝试都没能解决。。。

罢了,不如离了。。。

嗨,KaTex

还得是 KaTex 这种被主题包养的路线好,弯路一定少不了,呸呸呸,弯路一定少!

  1. 首先第一步,使用 VSCode 的内置 Markdown 渲染器(它也是基于 KaTex 引擎)扫一下以前写的公式(当时是用 Typora 写的),是不是都被兼容。嗯,小改十几处,无伤大雅。

  2. 再来第二步,按照 LoveIt 说明,在主题配置中开启数学支持3

    # 配置中启用功能
    ...
        [params.page.math]
        enable = true
    ...
  3. 最后一步,在文章的 front matter 中做如下设置4

    # 文章中启用功能
    mathjax: true

好啦,启动服务来预览下公式效果如何,,,

hugo serve --disableFastRender

等等,怎么这么多公式渲染不出来???

莫慌,细看一下,嗯,老问题,看来包一下 raw 的代码还不能丢。有方案,优势在我。

# 此处省略前述实现
def add_hugo_raw_tags(content:str)->str:
    ...

再运行,嗯,这次大差不差,公式基本都在。多刷两页,嗯???怎么还有漏网之鱼???

深入分析,小心求证,是当前使用的 [email protected] 集成的 Katex 才是 v0.16.0。而公式中涉及到的 \dddot 符号在 [email protected]支持。 一通折腾,最终发现仓库主分支上未发布的提交中已经集成了最新的 [email protected],好耶,拉它。

终于,一切正常,果然这条路蛮值!

Cloudflare Pages

我来 Cloudflare 只办三件事:

  1. 建仓
  2. ID
  3. 拿令牌

至于为什么使用 Cloudflare Pages 而不是 Github Pages?那当然是因为它不需要手稿仓库为 Public 呀!你就说,Actions + Token 能不能把网站办起来!!!

建立 Pages
  1. 打开并登录 Cloudflare,在左侧边栏找到 Workers & Pages
  2. 进入后点击 Create 并切换到 Pages 选项卡
  3. 选择下方的 Upload assets 即可开始创建新页面
  4. 选择一个合适的项目名称,比如网站域名我觉得就是个好主意。

至此,“空壳站点“已就绪。

获取账户 ID
  1. 回到 CloudflareWorkers & Pages 页面
  2. 在右侧找到 Account ID

那串字母数字就是账户 ID 了,So easy …

获取令牌
  1. Cloudflare 页面右上角找到 Profile 并进入
  2. 在左侧边栏找到 API Tokens 并进入
  3. 选择 Create Token -> Create Custom Token (Get Start)
  4. 选择一个合适的名称以便将来管理(基本就是不玩了删它的时候你能把它认出来),我还是觉得网站域名是个好主意,或者加个前缀(比如 Hugo)。
  5. 权限设置
    • 方案一
      • Permissions: Account | Account Settings | Read,即账户的读取权限
      • Permissions: Account | Cloudflare Pages | Edit,即前面仓库的写入权限
      • Account Resources: 限定为你的账户 https://store.yirami.xyz/review/typecho_to_hugo/cloudflare_pages_token_permission.png
    • 方案二(更小的权限粒度,适合于多域名账号)
      • Permissions: Account | Cloudflare Pages | Edit,同样需要仓库的写入权限
      • Permissions: Zone | Zone Settings | Read
      • Account Resources: 限定为你的账户
      • Zone Resources:限定为对应域名
  6. 最后 Continue to summary -> Create Token

至此,令牌已到手(页面先别关,令牌只显示一次)。

Github Settings

获取到前述 IDToken 后,即可设置 Github 中的手稿仓库,以便该仓库的 Actions 可以访问 Cloudflare Pages

  1. 打开手稿仓库的 Settings
  2. 左侧边栏找到 Secrets and variables -> Actions
  3. 单击 New repository secret,并创建如下 Name | Secret
    • Name: CLOUDFLARE_ACCOUNT_ID
    • Secret: 上一步拿到的账户 ID
  4. 再次单击 New repository secret,并创建如下 Name | Secret
    • Name: CLOUDFLARE_API_TOKEN
    • Secret: 上一步拿到的 Token

至此,仓库的配置完成。

Github Actions

最后一步,就是推送手稿并触发 Actions 进行部署。

需要在仓库的 .github/workflows 文件夹下创建配置文件,一般为 your_purpose.yml

对于不同的需求其具体实现可能大不相同。就我而言,我需要使用 Actions 完成如下功能:

  1. 拉取最新手稿
  2. 更新所有子模块(主要是依赖的主题)
  3. 安装 Python 及其依赖,并运行脚本
    • 脚本会获取 Hugo 程序主体
    • 从原始手稿生成 Hugo 专用稿件
  4. 运行 Hugo 程序构建静态博客
  5. 发布5构建结果到 Cloudflare Pages

  1. 我更倾向于使用 vscode 编辑并预览完后再上传,而不是直接在网页端撰写 ↩︎

  2. YAML 格式为例,如果为 JSONTOML 等格式需自行修改 ↩︎

  3. TOML 格式为例,如果为 JSONYAML 等格式需自行修改 ↩︎

  4. YAML 格式为例,如果为 JSONTOML 等格式需自行修改 ↩︎

  5. 这一步需要注意,发布分支需匹配 Cloudflare PagesSetting 页的 Production branch 设定,否则只能通过带前缀的预览地址访问最新部署。 ↩︎