池塘

你今日飲咗未﹖

我是怎么在静态博客里插图的?

在静态博客里插图一直是个问题,常用的解决方案是第三方/自建图床,但是这些解决方案有以下缺点:

  • 不知道这个第三方能用爱发电到什么时候,万一突然倒闭了数据很难找回来
  • 某些白嫖图床(比方说大眼)有可能突然把外链砍了,到时候整个博客一片“此图片禁止外部引用”
  • 自建可能会被某些杂种刷流量刷爆 CDN 或者 OSS 账单,就算设了配额也麻烦
  • 也有可能某天域名忘记续费了,外链全部挂掉,等到回头记得续费的时候已经变成天价了

因为这些破事,我就一直不敢往文章里插图,不过最近闲着没事,便开始着手解决这个问题。

我使用 Obsidian 管理笔记和博文,基本上就是 Obsidian 写一份,然后复制到 Hugo 里发布,不过这么做有个问题:Hugo 推荐你把图片资源和博文(index.md)放在同一个文件夹里,而 Obsidian 则是把所有“附件”都放在存储库的某一个目录下。要复制和修改起来就非常麻烦,因为你需要:

  • 把 Markdown 里引用的图片的路径调整为相对路径,而不是“附件”文件夹下的某个文件。举例来说就是 ![Foo](attachments/foo.jpg) -> ![Foo](foo.jpg)
  • 挨个复制文件本身

为了解决这个问题,我把 Hugo 的博文目录(content/posts)软链接到了 Obsidian 存储库里,然后再用 Templater 插件,自动在 Hugo 的博文目录里新建文件夹,然后把我目前打开的文档、及其引用的资源快速复制到新建的文件夹里。具体解决方案我会写在下一篇文章里。

这个方案的优点是:图片和博文是跟着走的,我托管到 Github Pages 永远不用担心挂掉。如果有一天政策改变了,大不了带着所有东西换个托管平台部署,迁移起来非常灵活。

首页的图呢?

做完之后乍一看没有问题,可结果我却发现文章中的图片在首页预览中加载不出来!按 F12 看浏览器请求资源的路径才明白怎么回事:默认情况下,Hugo 的图片渲染器在渲染到 HTML 时会使用相对路径。 举例来说,你有一个这样的目录结构:

- contents
  - posts
    - foo
      - index.md
      - bar.jpg

然后你在 contents/posts/foo/index.md 里引用图片 ![Bar](bar.jpg),这个图片渲染到 HTML 之后是这样的:

<img src="bar.jpg">

在具体的博文页面当然没有问题,因为此时浏览器会访问 /posts/foo/bar.jpg 这个路径获取图片资源,但如果是其他页面,例如首页,访问路径就变成了 /bar.jpg,可根目录根本没有我们需要的资源,这就出问题了。

本来我可以选择和 Obsidian 一样,把附件全部存到一个目录了事,不过前面花了大劲写的 Templater 模板可就废了。怎么办呢?我开始爬楼翻 Wiki。

不错,Hugo 刚好有一个 Image Render Hook(图片渲染钩子)可以覆写 Hugo 渲染图片的逻辑。在主题目录下创建 layouts/_default/_markup/render-image.html 文件,编写以下内容:

{{- $img := .Page.Resources.GetMatch (.Destination | safeURL) -}}
{{- if $img -}}
  <img src="{{ $img.RelPermalink }}" alt="{{ .Text }}" {{ with .Title }} title="{{ . }}"{{ end }}>
{{- else -}}
  <img src="{{ .Destination | safeURL }}" alt="{{ .Text }}" {{ with .Title }} title="{{ . }}"{{ end }}>
{{- end -}}

这段钩子的意思非常简单:在渲染图片资源时,填写 src 属性为资源的绝对路径,就避免了刚刚出现的问题。

图片压缩

既然钩子都写了那自然不能错过另一件非常重要的事:图片压缩。

目前我们的图片都是以原图形式插到页面里的,Github 获取资源的速度本来就一般,加上中国大陆对其的干扰,加载速度简直感人。本来可以用 CDN 加速图片,不过我懒,再说 CDN 有点治标不治本。

回到我们刚刚写的钩子,在 {{- if $img -}} 之后,{{- else -}} 之前,换成以下内容:

  {{- $resized := $img.Process "webp q75" -}}
  <img src="{{ $resized.RelPermalink }}" alt="{{ .Text }}" {{ with .Title }} title="{{ . }}"{{ end }}>

这里的意思也很简单:处理图片资源,转换为 75% 质量的 WebP 格式,然后插入转换过后的绝对路径。

不过我们还有一个问题要解决,现在页面里引用的图片的确都是压缩后的了,但是 Hugo 在构建站点的时候仍然会分发原图。

这是因为这些原图是作为页面包的一部分构建的,Hugo 默认会帮我们同时分发这些资源。不过由于我们这里重写了图片渲染的逻辑,不再引用原始资源,所以要在页面中把这个行为关掉。

在博文 foo/index.mdfront-matter 中,填写以下内容:

build:
  publishResources: false

比较坑爹的是每一篇都要写上这个。

RSS 牵扯出的 Bug

现在原图没有了,我正准备发布的时候,看了一下 RSS 订阅的状态。乍一看没问题,可仔细一看,图片中子目录的路由重复了一次:

重复的子目录

这里先解释一下,我的博客部署在 GH Pages 上,但是除了博客之外我还部署了别的静态页面,所以我在 hugo.toml 里填写的路径是 baseURL = 'https://diredocks.github.io/blog',这就导致出问题了。

最开始我还以为是我的配置问题,在社区论坛上发帖之后,维护者表示这是一个 BUG,估计和 Hugo 内部处理路由的一些机制有关。

暂时的解决方法是,使用 HTML 渲染同款的 RSS Render Hook:创建 layouts/_default/_markup/render-image.rss.xml,然后填写以下内容:

{{- $img := .Page.Resources.GetMatch (.Destination | safeURL) -}}
{{- if $img -}}
  {{- $resized := $img.Process "webp q75" -}}
  <img src="{{ $resized.Permalink }}" alt="{{ .Text }}" {{ with .Title }} title="{{ . }}"{{ end }}>
{{- else -}}
  <img src="{{ .Destination | safeURL }}" alt="{{ .Text }}" {{ with .Title }} title="{{ . }}"{{ end }}>
{{- end -}}

和前面的思路一样,不过从绝对路径替换成永久链接,暂时绕过了路由问题。

最后

除了以上提到的这些,我还参考 Hugo 图片懒加载和自适应 CSS 图片占位 给加上了懒加载。图片注解(就是图片右下角的小标签)也是抄上面这位博主的,它的博客源码也基于 Hugo,翻一翻基本上就能理解。