诸位老友,下午好。这里是想睡觉的 Chlorine。
最近把园子整个翻修了一次,本来是应该早些把更新日志释出的,但一直拖到了现在……不过最后还是写出来了。
色谱法
故事还得从上一期说起。在把园子迁移到自己的服务器上时,为了复刻 Netlify 舒适的 CI/CD 体验,小氯曾计划采用 Woodpecker CI。不过由于园子既需要 Hugo 又需要 Bun,构建环境不太好写,于是就于是了。当时为了削足适履,小氯曾经考虑过把其中一个扬掉——当然不可能是扬掉 Hugo。不过由于小氯后来复刻了下 Local Capistrano 模式,构建改到了本地,所以这个事情其实也没那么紧迫了。
然而小氯还是做了。理由?闲着没事(元素娘骄傲脸)。
把 Bun 从园子里叉出去,简称叉烧包。
如果硬要给 Shiki 找缺点的话,大概是比 Chroma(Hugo 内置的 Golang 高亮库)慢了太多(你能指望 TypeScript 有多快呢)。此外就是 NPM 生态的那些被碎碎念了无数次的问题了,这里小氯不多提及,不然本文可能又要变成讨 NPM 檄文了。
总而言之……小氯还是把 Shiki 踢出了园子的依赖列表。具体步骤也没什么好说的,把对应的构建步骤删了,然后清理一下原本的 CSS 就行。
Chroma 内置了若干种高亮方案,但它们都不支持亮暗自适应。所以小氯用 hugo gen chromastyles 搞了一套样板出来,然后把 Nord 的配色塞了进去,大致如此。
不过,Chroma 的高亮效果和 Shiki 相比,确实有差距……(但你获得了自由啊.avif)
流沙之地
按理来说,我们的去 NPM 化可以到此为止了。园子虽然还有 UnoCSS 这个依赖项,但它的唯一作用是构建 uno.css 样式文件,而此工作可以交予 Hermeneutics 侧(主题)完成。虽说提交构建产物容易引来绝罚令,但为了园子本身的整洁,也管不了许多了。再者,我们不是有一类代码叫作 Generated Code 嘛,还是可以狡辩解释一下的。
然而做完这些之后,小氯还是看着 Hermeneutics 的 package.json 不住沉思。
好吧,请容我多说几句,关于整个所谓的「现代前端生态」。
小氯无意对现代前端的发展方向指手画脚,因为我并非此间专家——我说不明白 Next.js 有什么优点,Vite 因何长盛不衰。即使是我耳鬓厮磨许久的 UnoCSS,代码也浅薄到堪称惨不忍睹。至于所谓「JavaScript Fatigue」和「依赖地狱」一类为人诟病许久的问题,也只是我偶尔 dust 一下我的开发目录,看到聚沙成塔的 node_modules 才会偶尔感叹一句。
但直观感受——好吧,直观感受是无须门槛的——不论是何种新生或已(堪堪)立稳脚跟的事物,于我而言,它们太光鲜了,如晶体般流光溢彩的官方网站,Blazing Fast 和 Next Generation,鲜衣怒马,掷果盈车。不是说这样不好。但,想想 Linux Kernel 吧。它的介绍只有一句:Linux kernel source tree。
天何言哉?四时行焉,百物生焉,天何言哉?
更重要的是:它们都太快了,或者说,太「迅捷」了,迅捷得令人眩晕。数月前可能还万人瞩目的新框架,数月后就进了故纸堆。即使是那些名震一方的项目:Bun 投了 Anthropic 的麾下,Astro 拜入 Cloudflare 的帐中。你永远说不准第二天,你赖以安身立命的项目会变成何般模样。
这像是流沙上的世界。
如诸位老友所见,小氯是元素娘。如果原子性质不再同一且长期稳定,那小氯根本就不会存在,或者是明天的小氯和今天的小氯毫无共同点。这种稳定性的缺乏,让我害怕。
反观 Hugo 呢?
Golang 本身就有上佳的稳定性——所谓的 Go 1 兼容性承诺即是明例。而 Hugo 自己呢?截至小氯敲下这行字,它已经十二岁了——与它年谊甚好的前端技术们,Grunt 和 Gulp 早已退隐江湖了,React 倒是依然鲜花着锦烈火烹油。「世界上最快的网站构建框架」依然基本名副其实,@bep 依然悬梁刺股般地维护着这个 Super Monolith,它依然吞入你的模板和 Markdown,吐出整齐的、W3C Compatible 的 HTML。你见不到 <app> 和 <astro-island>,只有 <body>、<main>、<article>。即使没有 JS,在模板本身悉心维护的情况下,你依然能得到一份可以在世界上任何地方打开并阅读的 HTML。由此,我们其实也可以对 Hugo 的独立性做一个技术层面的乐观预测——Astro 这样的项目能够被收购,显而易见的是它们有被收购的价值。你可以尽情写边缘计算的代码,而它们将化作 Cloudflare Workers 和 Vercel Serverless Function 价值千金的利润来源。但 Hugo 呢?先不谈你能不能买它的问题,你买下它不会给你的云服务带来任何收益。难道指望靠「企业支持版本」获利吗?抱歉,这种幽默感不合时宜。
至于 HTML、CSS 和 Vanilla JavaScript(准确来说,是它的一个极小的核心子集)本身?只要 chlo.is 这个域名还有意义,它们——所谓的「Web」的地基——将不朽,直至海枯石烂。
这种感觉,和冬天里身边的毛毯一样,令人心安。
所以,我们接下来的工作已昭然若揭。
把字刻在石头上。
Sassy
说了这样多,似乎小氯要把园子的样式重写成原生 CSS 了。事实上小氯最初也是这样打算的。
最简单的方法自然是直接把 UnoCSS 生成的产物复制过去——这也是我之前对 UnoCSS 还算有好感的原因之一,它是可以编译为标准的、通用的 CSS 的。不过,这种方法实在容易喜提绝罚令。而且,这也不符合小氯换掉 UnoCSS 的另一个目的。
如诸位老友所见,UnoCSS 直接把原子类写在 HTML 中。这种拼好 <div> 确实敏捷且便利(其实也有助于最终产生的 CSS 代码量的控制),但请看下面这个标签:
<div w="12" h="12" border="~ border/20" text="sm" font="medium" shadow="lg" flex class="
fixed right-4 bottom-20 md:right-8 rounded-full
backdrop-blur z-50
items-center justify-center tabular-nums
transition-all duration-300 opacity-0 translate-y-24
data-visible:opacity-100 data-visible:translate-y-0">
能在五秒钟内准确指出其样态和用途的老友,请受小氯一拜。
然后再试试这个版本:
<div class="reading-progress-ball">
我的话说完了。
回来。现代 CSS 其实已经相当强悍了,有嵌套、有变量、有计算、有层,园子的样式也并没有多么复杂,看起来用 CSS 会是个哲学上极其正确的选择。
然而小氯最后用了 SCSS。
理由的话……这个……首先,SCSS 是 CSS 的超集,兼容性嘛,小氯喜欢这个。其次,混合宏、占位符和 SCSS Loop 真的很好用(也有助于控制代码体积)。至于持久性的话……虽说 SCSS 也是所谓「前端技术」的一部分,但它其实没那么「时髦」——它和 jQuery 差不多大。虽然它也遇到了很多问题,但和 jQuery 一样,好说歹说是活了下来——而且依然是事实上的「基石组件」。按林迪效应(奇怪,我刚才怎么没提到这个词?),虽说现在去学它(事实上它不怎么需要学)有点 45 年入国军,但它一时半会儿也死不了。
不过说起来,小氯也蛮期待 SCSS 真的毫无必要的那天的。就像 Asahi Linux 一样——它最大的胜利,就是融入上游。
回来。Hugo 原生支持 SCSS,但它内置的那个 C++ SCSS 编译器已经半死不活了。SCSS 的官方推荐实现是 Dart Sass——没错,这里居然能看到 Dart(冷知识,Dart 最初真的是面向 Web 的语言,不是拿来写 Flutter 的)。虽说要多引入一个依赖(绝罚令警告),不过反正万事有 Nix 做主,我闭上眼睛就是天黑。
Bella Ciao
本节标题没有任何含义,只是写到这里时小氯正在听这首歌。
要讲明白小氯是怎么对 Hermeneutics 的 UnoCSS 和 CSS 执行清除计划的,还真有些难度。从 Git 提交记录看,小氯一段时间前就在断断续续地做 de-uno-ize 的工作,然后在春节这几天把它推进完成。scss 分支 squash 了 30 个提交,但由于小氯对需要被 squash 的提交写的提交信息都不太走心,所以要想仔仔细细地编年还是蛮繁琐的。那不如我们挑一些典型案例讲讲吧。
世界级难题
There are only two hard things in Computer Science: cache invalidation and naming things.
UnoCSS 把所有样式都打散成了可组合的 WYSIWYG 原子类,成功规避掉了起名难题,但缺陷就是像上面那样,语义化极差。SCSS 让我们夺回了类的命名权,那相应的,我们也要承担自由的代价了。
首先……首先我们做点简单的事情吧!命名空间隔离!
其实也就是把之前没有这样做的部分变量加了一个 --herm 前缀……比如说,我们有 --herm-text-based 这种变量——这当然是为了兼容过去的 UnoCSS 参照才写的。不过老实说,我的间距之类的大部分都是硬编码的。
关于每个类到底叫什么,小氯并没有太多纠结。如果是全页面容器就叫 container,是一个部分就叫 section,大致如此。真正让小氯头疼的是另外一个部分,也就是什么时候用类。
在 Gradient 03 里,小氯有提到过 Classless CSS。用标签规定样式——或者是尽可能用标签规定样式看起来确实诱人,不过实际用起来问题还是挺多的,比方说标签本身的优先级过高之类。为了方便用,小氯给自己订了套简单的准则:
- 如果一个组件不会在其他地方复用,或者对其上层组件构成依附关系(即其自身不是一个有意义的、自包含的模块),就不赋予类名。
- 尽量不给
div、span、p这样的无语义标签规定样式。
原则看起来总是很美好的,但现实就像园子的文章配色一样,压根没有黑白,全是灰色。所以这个原则大概也只能起到规范以外的一切作用。
涩涩!
严格来说,这一节是三个问题的结合。
首先是 SCSS 变量的问题。SCSS 有自己的 $var 语法,但小氯保留了 CSS 的 --var()。倒不是因为懒得迁移(明明就是),主要是 CSS 的变量已经相当好用了。虽说编译时可能会比运行时稍快那么一点点,但它的代码量还稍微增加了一点点呢。再者,在每个文件上面引入一个 _vars.scss,实在是过于繁琐。
至于颜色格式,小氯也不想再和 HEX、HSL 耳鬓厮磨了。我们直接上最新的 OKLCH。简单来说,L 是明度,C 是饱和度,而 H 是色相。它的益处也很明显:可以轻松地让颜色的明度、饱和度变化贴近人的感知,以及新潮。
小氯的 primary 主色大概是一种略亮的鼠尾草绿。稍微调了一下色值,得到 oklch(53.5% 0.0906 137.5)。至于为什么选这些奇怪的数字,就看老友们的理解咯。
不过后面小氯发现,其实还是需要一个调色板的。所以直接以 H 为步长,硬搓出了六种新的颜色:
--herm-red: oklch(53.5% 0.0906 0);
--herm-purple: oklch(53.5% 0.0906 275);
--herm-amber: oklch(61.8% 0.0906 52.5);
--herm-cyan: oklch(53.5% 0.0906 190);
--herm-pink: oklch(53.5% 0.0906 327.5);
--herm-blue: oklch(53.5% 0.0906 242.5);
虽然颜色名相当名不副实,但起码能用了(骄傲)。
吉光片羽
UnoCSS 最实用的功能之一是 CSS Icons,你可以直接写 class="i-iconset-iconname" 来显示图标。为了避免再写 {{ icon }} 短代码,小氯自然要复刻一下这个功能。具体操作也不难,在 SCSS 里写个循环就行,代码见此处。
之前小氯的图标选得很杂,除了主要的 IBM Carbon,MDI、Simple Icons、Phospor 都用。为了避免在 Acknowledgments 一节列出一大堆名字,同时为了表达对某可以合法使用 JSON 作恶的公司的不满,小氯打算把图标归一化一下。简单搜索之后选了 Lucide。Lucide 应该是法语,来源的话应当和拉丁语的 lux(光)有关。很巧,它的前身叫 Feather。
相比 Carbon,Lucide 的风格明显更圆润,看多了倒也算顺眼。不过 Lucide 几乎没有品牌 Logo,所以拿了 Simple Icons 找补——这位更是重量级,CC0 1.0。另外,由于这两个图册都没找到 Fediverse 的 Logo,所以就去 Remix Icon 捞了一个补上。
你这不还是用了三个图标库吗?!
说起来,Fediverse 有两个 Logo(当然都是非官方的,因为没有官方),一个是五节点全连通图,另一个是三个星号(Asterism)。好像现在第二个更受欢迎些,不知道为何。
排印学
UnoCSS 的 prose 类依赖于 @unocss/preset-typography 包。这个包的样式……小氯没办法找一个合适的词形容。你说它不美观吧,它又确实审美不错;但你说它美观吧……好像又哪里不对……
不过这不是我们要考虑的了,因为 UnoCSS 的时代已经结束力!(喜)
对于其他地方我可能还会陷入是否需要单独命名、这算不算一个完整模块的哲学危机,但对于文章主体内容,我们自然没这个顾虑。开城门,迎闯王,闯王来了不纳粮!
总而言之感觉把排版变好看不算难,行高和间距调大、字号等比缩放、对比度调低就完事了。说起来,小氯感觉 macOS 自带的等宽字体很好看。
无意义的兼容
如诸位老友所见,小氯是个喜欢新东西的元素娘,总想在园子里应用某些最新的 Web 技术。但很遗憾(或者说很幸运),新东西并不是在世界范围内同步适配的。例如,Firefox 到现在还不支持 view-transition API,所以园子现在在 Firefox 系上都是硬切的,没有过渡动画。
不过,这也并非什么大问题,只要我们做好 Fallback 就好,渐进增强嘛。但,这里还有另一个问题:Fallback 也应当是有底线的。不然,想象一下某元素娘为了兼容一个全球用户占有率 0.001%、目标用户占有率(姑且假定园子有目标用户)0% 的 Chrome 旧版本(可能是因为一些长辈从来没升级过浏览器)放弃了一个带来巨大优化的新特性或者是写了一百行的蹩脚的托底方案。这种戏剧感实在是不合时宜。
所以在盲目地写了许久后,小氯终于下定决心定一条兼容性底线。简单搜索后定为:
- Chromium 系:120+(这个数字大抵是我随便定的)
- Firefox 系(Gecko):128+(上一版 ESR)
- Safari:v17.4+
这也实现小氯「兼容到 Tor 的前一个大版本」的旧有底线——说起来,这个底线本身也有待商榷,因为 Tor 的用户大部分也是会紧跟新版本的。而对于 Firefox 一脉的另类们,比如 Firefox ESR(这严格说是官方支持)和 Waterfox,这样大抵也可以让它们过得愉快些。
不过,这里有一个小小的异类:Pale Moon。小氯习惯叫它「苍月」。
苍月的 Goanna 是从很旧的 Gecko 分叉的。在小氯的印象中,它的 Web 兼容性大概相当于 Firefox 60。所以很不幸,园子要在苍月的领域里阵亡了——不过情况其实也没那么惨,小氯试着用仓库打开了一下,虽然说宽度变成了 100%、颜色坏掉了(因为苍月显然不兼容 oklch)、头像显示不出来(因为是 AVIF)……但,它依然能打开、能读。如果小氯未来做一点 Variables Fallback,情况应当会好很多的。
嗯……您问移动端啊?不知道喵,听不见喵,不想管喵。
Optionated
之前有提到过(好像也是 Gradient 03)来着,为了避免小氯的众多实验影响通用版本的稳定性,小氯在 main 之外单独开了一个 custom 分支。但经过几个月,小氯发现,自己没什么能力维护主分支了。或者说得直接点,小氯在 custom 玩得太开心,迭代得太激进,导致 main 和 custom 的差别已经太大,不管是 merge 还是 cherry-pick 都开始费力了,当然也不再想着维持了。
按理来说,重写了这样多的代码,兼容性打得碎碎的。如果说之前的是 v0 的测试版本,那这起码可以说是 v1 了。于是在并非深思熟虑后,小氯直接废掉了 main 分支,把 custom 改成了 main。我的意志,就是 Hermeneutics 发展的全部——为什么没有这个功能?因为我不想写。如果您希望有什么功能,请行使您的自由,自己 fork 自己添加。
好吧,把 Hermeneutics 写成一个「通用」的主题的愿望终究是失败了(叹气)。
至于之前那个 UnoCSS 的版本(也就是之前的 main 分支),我丢到 legacy 分支了。如果哪位老友还希望用,就请随意吧。
Goodbye, Artalk
SCSS 重写完,小氯就要对另一个部分动刀了——占了园子一篇文章 TDS(Transferred Data Size)三分之二的评论系统。
大概在许久以前(似乎还是 Gradient 03),小氯就想着将 Artalk 废弃掉,但因为种种原因停止了。至于这次为何又下定决心了,大概是受了 Eltrac 的影响。另外,最近小氯收到了一些……令小氯感到困惑且有些犹豫如何回应的评论。倒不是垃圾评论或者恶意攻击,只是感到莫名其妙而已。解决问题,哪有解决产生问题的功能来得顺手呢。当然,这次主要还是因为 Artalk 加载的资源实在是太多了,足足有 200 多 kB,相当于好几个园子的页面了,这样多少有些喧宾夺主。
当然,让大家都给小氯写邮件来互动实在是不合适。替代品的话,小氯还是用了 Gradient 03 中就想到的 Fediverse。至于自动化问题怎么解决?没有自动化,小氯手动发帖,手动回填元数据。
CI/CD: Chlorine Integration & Chlorine Delivery
至于匿名评论的问题,小氯打算开一个 Mail Form,这样就可以做到「私人且匿名」的评论了——因为邮箱可以随意填写,只是这样会收不到回复而已。
至于为什么没有「公开且匿名」的评论……这个问题我们可以多说两句。
最近小氯的老友们似乎有遇到许多评论上的烦恼。轻则是逻辑性和正确性一言难尽的发言,重则是人身攻击一般(或者就是人身攻击)的无礼冒犯,甚至直接是注入攻击。而这些行为,无一例外都是基于「公开且匿名」的评论完成的——这里的匿名,指的是在身份标识上不具备任何可追溯性,哪怕是向假名的追溯也是。例如,一个毫无标识性的昵称,加上一个完全是随机生成甚至胡编乱造的邮箱。
当然,这里有一个问题——「有人利用这种方式作恶」(姑且让我们用作恶这个词吧)并不直接构成废弃这种技术的理由,就像我们不能因为有人用 Tor 网络进行非法交易或者攻击就禁止使用 Tor 网络一样,因为这样会伤害同样使用它的隐私爱好者、记者、活动家们。但这里的前提是:这种技术的确被需要。
在小氯眼里,「公开且可追溯」(这里没有用实名这个词,因为大部分时候我们用的是假名)和「私人且匿名」已经足够覆盖所有的需求了——不管您是希望闲聊、互动、建议、批评还是只想劈头盖脸地骂这只元素娘一顿。如果您有意愿公开您对小氯或者园子的看法,那也应当愿意为自己的言论承担名誉上的责任。如果您希望公开发言,却又不愿意透露「我是谁」,那在小氯眼里,大概率您发表的高论对小氯只有负价值。
当然(这是我写的第几个当然了?),我们不能做有罪推定,所以我们再打个补丁——或许确实有老友的想法是「我想公开发言,但我不想联结到自己的主要网络身份」。小氯对此表示理解,因为这是个人自由。这种情况,麻烦您用 Mail Form,我会把您的观点公布出来。
Hello, IndieWeb
接着上面的评论系统说。
依然是在 Gradient 03 中,我们简单探讨过不同类型的「互动」方式。公开、异步、结构化的互动方式,除了公开信/公开邮件列表就是 Webmention。正巧,按照 IndieWeb 规范,IndieWeb 三件套我们只缺个 Webmention 了,那不妨趁机把这件事摆平下。
最简单的方法自然是直接用 Webmention.io 的托管服务,但小氯显然不满足于此。不巧的是,现在的自托管 Webmention 服务端相当有限。小氯最开始看中的是 Golang 写的 Webmentiond,不过它既没有 Nixpkgs 又没有 ARM 架构的 OCI。而且,一个 Webmention Database 还要有账户实在是令小氯无法理解。一番搜索后找到了一个名为 webmention-receiver 的 Rust 软件,虽然上次更新是在四年前,不过好歹是能用。
高高兴兴地去 Webmention.rocks 测试一下……等下,为什么返回 201 没有 Location Header?
简单翻一下源码,发现是作者漏了写了。倒也不难修,加一句就行。按理说可以把这个 patch 合并回上游,但鉴于某元素娘没有 GitHub 账户,这个项目看着也有点……完成态,那我们 Fork 过来自己维护好了。
Fork 第一步,看一下上游许可证。对于能自托管的项目,小氯倾向于用 AGPL v3。那么上游用的是……
坏了,没有许可证。
如果代码没有许可证,那默认就是保留所有权利,那小氯就不能合法地 fork 这个项目。虽然某元素娘对现行版权法持保留态度,但为了避免被抓去玩手铐 play,暂时还是要谨慎一点的。而且,如果把项目托管在 Codeberg 上,其只接受严格自由软件许可的项目,如果混进去了许可不明的代码,是违反 TOS 的。
所以小氯先用了一个 patch 方法:
rustPlatform.buildRustPackage rec {
pname = "webmention-receiver-patched";
# ...
cargoPatches = [
./v0_1_1.patch
];
}
然后拍了封邮件给原作者,请求一下许可声明。很遗憾,目前没有回复。所以小氯暂时只能把 patch 分发一下,虽然说小氯也不知道怎么分发。
未来大概只能是自己重写一个,来彻底解决版权问题了。正巧原本的项目用的是 Hyper(直接用 Hyper 写 Web 服务……怎么说呢,确实富有魄力),修一修改写成 Axum 也算是件好事。
我写 Rust?真的假的?
总而言之,是把 Webmention 加上了,虽然说没有展示(Webmention 标准确实也没要求要展示),也没找到发送的方式,但至少我们可以说,园子已经是 IndieWeb 的一部分了 >ω<
后记
差不多就写到这里吧。
实话说,这篇文章写得有些艰难。倒不是因为文章的内容多复杂,只是不知为何,小氯最近总是感到无来由的疲惫。或许把这篇文章写完,就应该去休眠一会儿,或者只读整个互联网了。
就这样好了。祝老友们万事胜意……万事胜意还是有些艰难的,或者至少是,一切事情都足够好吧。
B Side
本期的音乐过于杂乱或不明确。









