tplate

new tplate

By Hubert Chen at 2025/09/20

Updated on 2025/11/20

起因

我的 Pixelbook Go 的 Linux 虚拟机在某一天再次(是的,很多次)炸掉了,于是我开始将更多的开发工作挪到台式机上。但是呢,我的台式机配置依然还是 8 GB 内存。

是的,我那台 8 GB 内存的台式机已经没法跑起 Urara 模板了,在构建时会因为内存不够而失败 但这并不是我这么久没有更新的原因

时间间隔太久,我也忘了是什麽时候了,我觉得 @kwaa 写的 lume_theme_shiraha 很合我的胃口,MD3 自适应配色加上样式,非常的喜欢

后面因为对 Pixelbook Go 的逐步厌恶,我反倒去买了台只有 8 GB 内存的笔记本(ThinkPad X1 Carbon Gen 4),它给我的印象还不错,至少比前面那台能干的活多... 本来我想在这里放一个 Pixelbook Go 的链接,因为我真的喜欢它的设计,但是 Google 似乎把它从商店里下架了

但是呢... 我发现在跑 lume_theme_shiraha 时,deno 依然是得吃掉 4 GB 的内存,这是 JavaScript 的原罪吗...

现在

我于是想写一个尽量不用到 JavaScript 的博客生成模板(至少在构建时不能用),同时它还需要支持我的一些奇怪想法,于是就写,如果你能看到这个文章,那我就是在写,或许已经写好一大半了

我打算先支持一下完整的 Markdown 文章解析,再考虑其他的附加东西,最后替换掉正在使用的 Urara 博客,当然还有一大堆事情要做

⚠ 注意:此项目不遵循任何网页设计规范,您可能会遇到以下各种情况

  • 主容器不遵循 768px 最大宽度
  • 可能并不适合人类观看的配色
  • 乱用字重和行高

前期

我选了 go 语言来写这个东西,毕竟我就会两门语言,要是用 C 写的话那就不知道要写多久了(说实话也很久没写了)

其次就是我在使用 Gitea 时发现,它用到了一种叫 .tmpl 的文件,只要去仓库里把对应目录下的 .tmpl 拖到对应的目录,再修改其中的内容,就可以直接更改网页的布局,不需要构建甚至重启 还是要的,但可以做,感觉非常的神奇

但我上手了几次后,发现它的逻辑确实是有一点难懂的,如果去看 Gitea 的模板的话,它也不是按照引用 template 来嵌套组合网页的,而是从头到尾拼出来的,也就是 header + 某些组件 + 实际内容 + footer 这种类似字符串拼接的做法,其中 headerfooter 的标签并不是完全闭合的,当案例去看很误导人怎么用

而且 .tmpl 没有静态检查拓展,语法之类的就问 GPT 去吧

文章解析器

一开始我只使用了 lute 作为 Markdown 解析器,然后我发现它似乎没有什么好的方法可以在将 Markdown 解析成 HTML 的过程中将类或者 ID 选择器加到 HTML 标签中,于是开始邪恶的将 YAML 作为文章源文件(对的,现在还留着,现在这篇文章就是用 YAML 写的 放弃了)

但我尝试写了一会后,发现这样跟手搓 HTML 似乎没什么区别,过了一阵子又改回来了。但样式的问题解决了,CSS 的类型选择器 也不是不能用,而且不用往 HTML 标签里单独塞东西感觉还不错

期间搬了自己几篇文章试试效果,就是不知道为什么不能解析 Markdown 的 Front Matter 部分。没有什么想法,于是跑回去写 bot,由于配置文件我也用的是 YAML,结果越看越眼熟...

这个 Front Matter 部分怎么长得跟 YAML 有点像,而且 Front Matter 前后的 --- 符号似乎也是 YAML 的单文件分隔符,询问神奇的 ChatGPT 后,得到了肯定的答复,于是简单写个解析器区分 YAML 和 Markdown 部分单独解析,文章信息就好了

代码高亮

本来是不打算做代码高亮的,原因很简单,我写不出来

但是呢,lute 支持代码高亮,虽然中途发现样式似乎有奇怪的问题,但后面改了一下,效果还不错,但配色跟我的主题看上去不是很好,现在还没有改的打算

Spoiler / 遮罩

由于 lute 好像不支持 Markdown 的 Spoiler 语法(即 || 遮罩内容 ||),而我之前又有在部分文章中使用到了这个语法。找了一圈 HTML 中也没有对应的标签实现

当然你能在这个文章中看到这个语法,就代表我用了一些奇怪的实现方式 因为我把 <mark> 标签夺舍成 Spoiler 语法了,实际上就是 ==遮罩内容==

这可能会导致在一些 RSS 订阅的阅读器中降低一些阅读体验,因为没找到什么好方法,而且在手机上,必须得长按选中才能看到实际的内容

文章目录和章节

lute 好像没有方法在渲染 Markdown 后拿到一些章节数据,不过还是提供了章节标题锚点的功能,见章节标题右边那个小图标

改了一下,靠 JavaScript 实现了类似 lume_theme_shiraha 的标题链接锚点效果,本来也想写到标题前面,但是因为我设定的 body 填充范围小了些,放到外面在移动端上不是很方便,就顺着放到右边了

一开始是靠作为 lute 标题链接锚点的伪元素实现的,但这样其实还是会留着 svg 元素,标题多了浪费流量,后面问了问 GPT 才给出一个用 JavaScript 代码自动查找元素并添加子元素的方法,但这也导致了如果禁用 JavaScript 那就没法显示出这个按钮

也因为这个,所以文章目录不是很好写,或许可以写一些正则表达式,逐行匹配 heading 标签来尝试生成目录,这个缓一缓也没什么问题了...

我还想设计成每个段落都像代码编辑器里那样可以折叠显示,这样相对来说可能方便些?不过感觉很难做

样式

由于 前面说到 lume_theme_shiraha 很合我的胃口,就打算照着样子做一个(不过就不是最新版那个样子了),还有部分设计借鉴了 Urara,非常感谢 kwaa

当然前者是有 MD3 按图片取色的,这个我尝试从网上找一个 MD3 取色的 go 语言库,完全没找到于是放弃,就固定两个配色就好了...

个人偏好

期间还掺杂了个人偏好的样式,例如我觉得全屏看网页时 768px 的主容器宽度未必显得两侧太空了,于是在窗口大小大于 1536px 时,主容器宽度会拓展到 1024px。也不至于出现 21:9 的屏幕读一行字需要头从左往右转的情况

由于没有使用 css 库之类的,样式全都是自己写的,还可以把样式全部写进 HTML 的 <head> 标签里,这样只需要按 Ctrl + S 就能按照原样保存的网页

不过文章里有引用网络资源的话就还不行,或许能想个办法在构建静态网页时把引用的网络资源都一起扔进 HTML 里。但这样可能会因为重复加载相同的样式和资源而消耗多余的流量甚至拖慢加载速度,这个就之后再说了

按需样式加载

好像前端那些样式库之类的东西,都会根据使用到的样式来按需构建样式表文件,而我思考了一下,好像做起来很难,但每个页面都加载用到的全部样式又比较浪费流量,思考了一段时间后我选择了另一个方法

把全部的样式文件,看哪些组件会用到哪些样式,将其分别存放到对应文件夹中,在构建时存储到变量中,当调用这个组件时,自动在组件前附加样式

本来还想把样式都丢在 <head> 标签里,但是在 .tmpl 模板中进行操作时无法更改外部的值,好在测试都正常,就暂时用吧

文章列表

想到之前使用 Urara 时会将题图放到文章列表中,于是自己试了试也做一个,发现效果很难看,甚至想过要不还是不显示图片了

后面又乱折腾了一阵,最后是作为文章列表项目的背景图像显示的,并叠加了一个背景色到透明的渐变来模拟遮罩效果,在深色模式下观感可能还可以,但浅色模式下可能就不太行了

而且图片的显示区域是由设定文章描述高度来撑开的,想不到什么办法了...

通过 PageSpeed Insights 测试了一下,这个题图实现方式还有个应该应用 fetchpriority="high" 的建议,但我这个是 <div> 标签的 background 样式,添加属性也没效果,就暂时先这样了

文章分类和标签

在之前使用 Urara 的时候是有从其他仓库里拿过一个 sections 插件(见 Interstellar750/hexo_s 的 README 部分),可以以某个分类来显示其下的文章,这个代码在我的 Urara 库中还有保留,但是 @jiwaszki 似乎转向使用 hugo 来搭建博客,原来的代码就不得而知了...

好在这个写起来也不算太难,其实仔细想想跟 tag 也没差多少,就是分类只能指定一个,而标签能指定多个,最终都是靠标签或分类来索引符合条件的文章而已

由于它们很像,索引过程也都是用的文章列表模板,我把它们都放到同一个页面去了,然后再遍历生成 HTML,分类的的 URI 就是 host/category/<分类名>,标签就是 host/category/_<标签名> (前面多一个下划线)

在分类展示页里,分类以 grid 的形式显示在前面,显示分类名、最后发布的文章时间和其下的文章数量。后面部分就留给标签,如果有某个标签包含多个文章,也会显示计数

不过相比 Urara 就不能按分类 + 标签逐个筛选了,只能一个个看

网页交互

RSS 订阅 / Sitemap 数据

一开始我觉得 HTML 也是一种 XML 嘛,就直接当做 .tmpl 文件,进行一个模版的写,但是由于开头的 <?xml version='1.0' encoding='utf-8'?> 不是一个正常的 HTML 标签,就会导致被转义掉,就算用 template.HTML() 套住也不行,它在执行模板时就被转义了,没法被正常识别成 XML 文件

中途想了各种奇怪方法来解决这个问题,最后在写文章的时候突发奇想:

既然就开头那一行有问题,那我先把它去掉,执行完模板后再把开头作为字符串把它拼起来不就好了

于是现在就只需要在使用时填一下 "atom" 参数就好了,舒服

途中还看了看 AtomRSS 的区别,我一开始还以为 RSS 是 Atom 的升级,没想到是反着来的

后面顺带把 sitemap 也做了

链接预览

在写了一半发出去让群友们不得不品鉴时,发现好像没有链接预览,试了试几个博客链接,最后发现是 <meta> 标签的问题

途中看了各种网站,最后的结论大概是得加 <meta property="og:description" content="描述"> 这种标签,但是总感觉不太对劲,我在 MDN 甚至都没看到 meta 标签有 property 这个属性...

还尝试过为文章生成链接预览图片,但尝试了一会后发现设计出来的图相当难看... 暂时搁置了

评论系统

好在我使用 Urara 时并没有添加太多的评论系统,只用到了 GiscusWebmention,这让我的迁移过程变得比较轻松

Giscus 本来就提供 JavaScript 代码的使用方式,而 Webmention 显示嘛,用 kwaa 写的 seia 就好了

提交 Webmention 链接的话,去了解了一下 API 端点,请 ChatGPT 写了一份代码,就好了