惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Cloudbric
Cloudbric
WordPress大学
WordPress大学
博客园 - 叶小钗
B
Blog RSS Feed
T
Tailwind CSS Blog
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
美团技术团队
Scott Helme
Scott Helme
D
Darknet – Hacking Tools, Hacker News & Cyber Security
Hugging Face - Blog
Hugging Face - Blog
T
Threat Research - Cisco Blogs
B
Blog
V
V2EX
Simon Willison's Weblog
Simon Willison's Weblog
I
Intezer
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
T
Threatpost
Cisco Talos Blog
Cisco Talos Blog
阮一峰的网络日志
阮一峰的网络日志
C
Cybersecurity and Infrastructure Security Agency CISA
PCI Perspectives
PCI Perspectives
雷峰网
雷峰网
The Register - Security
The Register - Security
博客园 - 【当耐特】
Google DeepMind News
Google DeepMind News
N
News and Events Feed by Topic
H
Hackread – Cybersecurity News, Data Breaches, AI and More
U
Unit 42
Security Latest
Security Latest
NISL@THU
NISL@THU
腾讯CDC
S
SegmentFault 最新的问题
小众软件
小众软件
The GitHub Blog
The GitHub Blog
月光博客
月光博客
A
Arctic Wolf
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
N
Netflix TechBlog - Medium
IT之家
IT之家
D
DataBreaches.Net
C
CXSECURITY Database RSS Feed - CXSecurity.com
N
News | PayPal Newsroom
L
LINUX DO - 最新话题
博客园 - 司徒正美
大猫的无限游戏
大猫的无限游戏
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
J
Java Code Geeks
TaoSecurity Blog
TaoSecurity Blog
P
Privacy International News Feed

記緒漂流

Cloudflare One 集成 OAuth 访问控制 | 記緒漂流 Cloudflare One 集成 OAuth 访问控制 打造专属记账助手 | 記緒漂流 打造专属记账助手 Numb | 記緒漂流 Numb 三月自白 | 記緒漂流 三月自白 相反的你和我・同频的心与心 | 記緒漂流 相反的你和我・同频的心与心 使用 Tailscale 内网穿透 | 記緒漂流 使用 Tailscale 内网穿透 在《HackHub》中成为世界首富 | 記緒漂流 在《HackHub》中成为世界首富 社交距离 | 記緒漂流 社交距离 UNIQLO × Akamai 联名 T 恤 | 記緒漂流 UNIQLO × Akamai 联名 T 恤 Windows 按键重映射 | 記緒漂流 Windows 按键重映射 少女・魔法・救赎 | 記緒漂流 少女・魔法・救赎 Live 初体验 | 記緒漂流 Live 初体验 《剧场版总集篇 GIRLS BAND CRY【后篇】嘿,未来。》观后有感 | 記緒漂流 《剧场版总集篇 GIRLS BAND CRY【后篇】嘿,未来。》观后有感 从 UnoCSS 迁至 Tailwind CSS | 記緒漂流 从 UnoCSS 迁至 Tailwind CSS AI² 与站点 LOGO | 記緒漂流 AI² 与站点 LOGO YAML 多行语法 | 記緒漂流 YAML 多行语法 所以我放弃了 npm | 記緒漂流 所以我放弃了 npm 上下求索,以飨四方 理想、偏见与「博友圈」 | 記緒漂流 理想、偏见与「博友圈」 甲流就医纪实 | 記緒漂流 甲流就医纪实
上下求索,以飨四方 | 記緒漂流
TuyuriTio · 2025-11-09 · via 記緒漂流

「想在互联网上拥有自己的一隅。」

这是我一以贯之的念想,便这么做了。对于一个笨拙的人而言,学习新事物难免有些困难,好在热情还是足够的,总是能坚持下来。兜兜转转,几经迭代,迎来了如今这个站点。

开发设计之初的目标只有一个:不嫌弃就行,那么一切都需要从简。

毫不违心地说,现在的样式也好,细节也罢,很难说让自己满意。只是恰好不沾染任何华丽的外观,不会出现原则上的错误,因此勉勉强强看得过去

似有天意,这回站点公开之后,鲜有不满和疑惑的声音,多是夸赞,称是「有特点」。这何尝不是一种歪打正着。更时有建议开源发布出去,或许互联网上还有同样偏好的开发者乐于使用。

大概是为了收获更多认同,也想得到他人的维护支持,我想,值得一试。

提交与版本规范

不幸中的万幸,我是有些精神洁癖的完美主义者。

早在站点迁移之初,就考虑到了开源的可能性,为了让 Git Tree 看上去清爽,自初始化起就使用了约定式提交,且通过英语撰写提交信息。只是,即使格式是固定的,内容也仅能凭着主观臆断煞有介事地填充上去,至今没有定下非常满意的方案。

基本开发工作完成之后,便需要确定版本,选择 SemanticVersioning(语义化版本) 是最自然也最实际的,各个工具链的支持也相当完备,无需多想,按照规范正常推进就是。

最初,我用 npm version 命令手动更新版本,自动更新 package.json 版本号并添加 git tag,一切都合适,只是缺少了 GitHub 上的 Release 信息,显得纯粹的标签十分空洞。

于是基于 googleapis/release-please-action@v4 写了一个 GitHub Actions 工作流,识别 git log 自动创建升级版本,弊端是会疯狂创建 Pull Request

值得一提的是 SemVer 的第 4 条:

主版本号为零(0.y.z)的软件处于开发初始阶段,一切都可能随时被改变。这样的公共 API 不应该被视为稳定版。

侥幸于没有着急发布 1.0.0,这成为了我发布 BREAKING(破坏性) CHANGE(更新) 的免死金牌。

理论上说,主题已经投入了生产环境中,应该发布稳定版本了,但依旧有许多想法随时可能变更……

例如前不久为了增强配置文件的约束,从 JSON Schema 的方式换成了 TypeScript 的类型检查。

未来还计划更改数据库的字段使其更具泛用性。

ZeroVer 下,所有的 BREAKING(破坏性) CHANGE(更新) 都淡化为了一次次小小的次版本号迭代。更多的是困惑与惭愧,怨自己能力不足,亦不太愿意为这么个小项目花额外的精力编写迁移脚本…

即便不去变更主版本,我认为也应该在提交中体现,例如按照约定式提交的规范使用 ! 字符和 BREAKING CHANGE 脚注。一开始也确实这么做了,但 release-please 直接将版本号迭代到了 更新到了 1.0.0,给我吓得不轻,连忙手动修改了那条 PR。之后就没再这么做,只能简单地在提交的正文的弱弱地加入一句「This is a breaking change.

好在就结果来看,Git Tree 保持得比较干净,长成了自己期待的模样,自觉赏心悦目。

干净利落写法
干净利落写法

图中所示 VSCode 插件为 Git Graph

配置与约束

我是一个遇到复用陷阱1就自觉跳进去的人。

似乎生来就对重复冗余的事情有着极强的怨念,尤其在代码中,写死的值和两段及以上逻辑相同的代码会另我抓狂。因此即使是自用的项目,我也要留出些个可配置项来让自己的代码看上去是高度可配置,除非太麻烦

但事实上,除了基本的站点信息和作者信息,我很少再提供其它配置,变得有些 Opinionated(固执己见),认为自己已在提供最合适的逻辑和样式方案。

此前的所谓配置,也不过是以某种自己认可的方式定义一个变量。于是,即便这次有了一个冠冕堂皇的理由可以尽情抽象自己的代码,但这意味着我需要把变量都集中到一个文件中,还是令人头大。

第一时间想到也是最简单的方式是定义一个 JSON,极致的便利与灵活。缺点亦然,过于灵活,导致缺乏字段与类型提示,放到逻辑中也难有较好的校验方式,明明都是一眼就能明白的变量类型,却只能无奈定义为 any

JSON Schema 在一定程度上可以解决问题,不如说丰富的约束条件使其即使不够严格,也能让使用者在配置时理解开发者的意图。不过到了编译期,其效果难说与变为 any 有何异同。

前文提及关于配置文件类型的变动,正是为了加强约束,虽然无法像 JSON Schema 那样精确到对字符串应用正则表达式,但是合理搭配 TypeScript 类型体操,至少还是能达到缓解运行时报错的效果。

国际化

如果说样式从简是视觉上的目标,那么 i18n 就是逻辑上的主要发力点。

搭建动态路由只是基础中的基础,其配套建设才是折腾的核心。先是自行实现了翻译函数,开发插件的适配也需要额外完成,翻译文案与命名空间的设计也全靠自己摸索。

当前语言与目标语言的切换并不复杂,只是脑子没转过弯,一心想着单个文件只能对应一个翻译函数,但为每种语言都定义之后,问题就能迎刃而解。此外,翻译文案又怎么不是一种配置文件呢?在各自的文案中加入配置字段的翻译,先前所有被迫重复编写的代码都迎来了复用的曙光。

在 i18n 的用户配置方面,Astro 一直有个恼人的点,defineConfig 中的 i18n 是一个可选字段,这意味着即使面临无法编译的风险,用户也随时可以删除它,而我在代码中却不得不用 i18n! 的断言方式减少大量不必要的判断,这显然是冲突的。

那么,把选项移动到自己的配置文件中并作为必填选项就好了!这么一想自己真是被原教旨主义思想害得不轻,非得用官方的接口定义配置…或许有一个原因是,其通过泛型设计,使得 defaultLocale 的值必须来自 locales,这在当时还是非常令我震惊。但…实际并不复杂,抄过来就好了:

interface SiteConfigOptions {
  /** Internationalization Configuration */
  i18n: {
    /** Supported Locales */
    locales: Locales;

    /** Default Locale (must be one of the locales) */
    defaultLocale: Locales[number];
  };
  ...
}

...

function siteConfig<const Locales extends string[]>(config: SiteConfigOptions<Locales>) {
  return config;
}

使用 const 关键字保留 locales 的字面量信息,使其类型 Locales 被推断为形如 ["en", "zh-cn"] 的元组而不被泛化为 string[]。那么,Locales[number] 就表示这个元组中所有元素的联合类型,实现 defaultLocale 必须是 locales 数组中的一个值:

那么接下来,我不再需要调用 Astro 提供的 astro:config/client 转而使用自己的接口,但这主要还是为了自己接下来的一个小巧思做铺垫。


这套主题起初的目标就是作为彻底的 i18n 解决方案走向开源,但回头细想,又有多少开发者,多少站长真正用得上这个功能呢,这是否本就是设计上的冗余?

违背部分的初衷,提供单语言模式也可用的配置成为刚需。

权衡之后,我认为比起提供另外的配置项,利用现有配置一样可以做到。所谓单语言,也就是只保留一种语言:i18n: { locales: ["en"], defaultLocale: "en" } 不就是这个意思么?对于逻辑中的处理,只需要暴露一个常量,判断 locales 的元素个数是否为 1 即可。

得益于可选动态路由(/[...locale])的建设,我在访问路径上不需要做任何改变,只要把对内容解析从各个语言文件夹改为其父级目录变大功告成。

文档与 CI/CD

README 是一个项目的门面,应当在最短的时间内向他人阐述这是什么、为什么要用、如何安装以及如何使用。我在编写时以 Standard Readme 为基准,同时参考了一些同类型的灵感项目,简单涵盖特性、安装、命令、部署等方面,主要是作为详细文档的入口,为有需要的使用者提供使用思路。其它文档的编写同样花了不少时间,对接口层面的配置内容做了详细说明,同时也提供了一些主题特性的预览。

光有文档和截图不一定能看出个所以然,还要提供在线演示。同样是利用 GitHub Actions,主分支自动部署到 Vercel,带有评论系统的分支自动部署到 Cloudflare Workers

对于使用者的文档,个人认为还是比较详尽,但对于开发者则只提供了最最基础的流程,或许盲目信任只会导致工作量与复杂度的骤然提升。

故事得从一条 PR#17 谈起。

在仓库公开之前,我就已经提前放置了一些文档,那大多是提供给普通使用者的教程,留给开发者的只有一份浅显的 CONTRIBUTING.md

作为自己的第一个开源项目,许多准则都拿不定主意,需要在实践中慢慢积累,但什么对自己对社区有利什么有弊,我想自己还是有些最基本的认知。

面对这条 PR,在第一时间的喜悦与惊喜过后,便多是困惑。

新特性、新配置甚至代码格式化跻身同一条请求,令我稍有些束手无策。看来,前期偷的懒在此刻要开始还了…

向提起 PR 的开发者表明想法请求修改重开后,我开始修订文档,描述了 PR 前的前置工作以及其本身的相关要求。这自然还并不完善,但目前作为一些兜底条款大概还是够用了。

至于格式化规范的需求,原本打算对现有方案精挑细选后逐步推进,现在看来不得不将日程提前。

在前端领域,最最主流的必然是 ESLint + Prettier 的方案,好巧不巧,这俩正好都不在我的好球区。于是将目光转向了正在活跃开发中的 Biome,这也可能是 Rust 爱好者的支持方式吧。

与其说是新兴,不如说有些不成熟过头了,上个月才提供了对 Astro 和 Svelte 这类 HTMLish 框架的实验性支持…经测试使用下来,BUG 也是离奇得多,还是默默关闭了这个特性,暂时用着稳定版本的残疾支持。

但依旧给自己留了一条后路,我没有立刻删除 Prettier 的相关配置,倒是做了优化,将其作为回退选项,以备不时之需。

确定 Formatter / Linter 工具后,就试着将其作为所有项目参与者的标准化工具,接入了 Husky 在本地作为 Git Hook,并 GitHub Actions 中也通过 Biome 处理 CI 流程。

快捷脚本

内容可能不多,但值得单独拎出一个章节说说。

使用命令创建新文章是博客主题常有的功能,脚本的实现并不算难,但是脱离了核心框架后略显麻烦。

比起直接甩个用户一个写死的模板,我想尽可能提供更好的体验,通过交互的方式进行初步的文章配置。那么需要用到命令行交互库,一共尝试了三款:@inquirer/promptsenquirer 功能尚可,但样式有所欠缺,最后选择了 @clack/prompts,配置方便,样式也比较现代化。

创建 Svelte 项目时,官方用的也是这个库2偶像同款~

脚本的内容就是选择内容类型、语言,填写标题、标签等等。读取命令行输入,再创建文件,最后打开。一开始只有英文提示,就连 i18n 也要在脚本里单独配置一次。而前面提到的配置文件重构,就是为了优化这个问题。

astro.config.ts 并非不能读取,只是需要加载整个配置,东西非常多,导致脚本启动需要费非—常—大的劲:

等脚本加载好了,文件也都创建好了…

那么,我把 i18n 的配置也挪到主题配置中不就好了么,一来脚本可以直接加载,二来也能优化主要文件,一举两得,可惜是个伤敌一千自损八百的做法

这时,既然与主项目同步的语言选项都到手了,那主项目的翻译文件也可以考虑一下了…于是乎,我给脚本也做了 i18n,顺带扩展了翻译函数的命名空间支持。

虽然不一定有人用得上,但自己的成就感已经满溢而出。

结语

首先,必须感谢科技的发展,如今的 AI 虽然仍有非常多的缺陷,但作为助手已然绰绰有余。特别是针对纯文本的工作,翻译根本不在话下,基本不太需要检查。如果用上 Deep Research,那么甚至写一个直接可用的 Astro 集成都是没有问题的。

然后说一下自己的愿景吧,一直待在 ZeroVer 的舒适区肯定是不行的,但现在正式发布仍为时尚早。

Biome 没有稳定,甚至会给自己的开发添堵;如果要增强 i18n,那必须挑战适配阿拉伯语;最重头的依旧是字体,我需要等待 Astro 的实验性字体 API,也需要规划怎么合理加入 Sans-serif 字体来提升阅读体验。


最后,我想自己可能是尝到了些甜头,有些飘飘然,掉了一些 Star 就开始失落。

但这到底是一个满足自己癖好的业余项目,积累些经验已是谢天谢地,在追求社会认可之前还有很长的路要走。

Footnotes

  1. Premature Abstraction in Software Design

  2. sveltejs/cli