顽疾

自从引入 Obsidian 管理 Hexo 博客之后,一个重要的问题需要解决:如何完美将 Obsidian 特性迁移到 Hexo 中,实现「所见即所得」。这里的「见」指的就是本地 Obsidian 渲染样式,「得」指的就是 Hexo 网页渲染样式。

Obsidian 引入了一些自己独有的 Markdown 语法与工作方式,我也为此不断探索最适合的渲染方案与使用习惯。在这个过程中,我成功总结出一套自己的经验:

双链语法是 Obsidian 最重要的特性。一开始,我也找到了相似的插件 hexo-link-obsidian,但是这个插件的使用过为复杂和苛刻(详看 这篇文章):

  1. 首先,你必须安装 hexo-abbrlink 插件来为文章生成永久链接
  2. 其次,你必须在 Obsidian 安装 link-info-server(作者也是 hexo-link-obsidian 插件的作者),这个软件为 hexo-link-obsidian 提供服务。
  3. 最后,才在 hexo 安装 hexo-link-obsidian
  4. 由于 hexo-link-obsidian 使用 link-info-server 服务,使用插件前必须保证 Obsidian 打开,否则插件失效。为此,我又为 Hexo 写了个预处理脚本,检测 hexo-abbrlink 的安装、监测 Obsidian 启动以及服务端口。

这几个乱七八糟的插件强耦合,缝缝补补也能算凑合着用。但各种小问题也接踵而来。

一开始发现对原本笔记就有的网络图片渲染得不是很好,我就自己改了一下源代码,把插件 index.js 匹配图片的正则表达式改成一个永远不会有的值。

1
let pattern = /thestringyouwouldnerverwritekk24oo9669/g

结果当我把我这个解决方案写进文章时,把改的地方直接复制到 Markdown 文章里来,结果 pattern 就匹配上了…(其实是自己🦈🐝了)

另一个小插曲就是 hexo-link-obsidian 插件对 webp 图片格式没有支持,我就直接向它的仓库提交一个拉取请求,结果就通过了。我也算投机取巧成为一个小小贡献者…

这套工作方式安全用了半年。直到有一天,它又抽风了:在连接 link-info-server 插件出现了超时错误,问题难以排查。这个事件成了导火索,我不能再忍了🤬,再忙也得把它弄出来。

一个晚上的奇迹

我原本的思路是:

  1. 另外写一个脚本,每次 Hexo 构建时,都去遍历一遍文章,记录文章标题与文章 front-matter 中 abbrlink 的键值关系,就像我在 另一个项目 做的那样。
  2. 字符串匹配,替换双链为 <a> 标签。

但这会导致最终我还是整了套「工作流」,且强依赖 hexo-abbrlink 插件,还是很麻烦。因为我的思路还是局限于经验,不知道 Hexo 会为插件提供什么。

当我在找类似 Hexo 插件学习时(就像我之前写 Markdown 插件那样),找到了 这个项目hexo-filter-link-posthexo-filter-link-post 将文件里的通过相对路径引用的 markdown 文件转为对应的文章的链接。源代码中使用到了两个机制:

  1. Hexo 插件本来就可以遍历每篇文章,且可以定义一个全局的类似 Map 的东西。
  2. Hexo 本身就有获取当前文章的永久链接的能力,不依赖其它任何插件。
  3. Hexo 提供渲染器,我可以选择在文章渲染的不同阶段中实施我想要的处理。

hexo-filter-link-post 给了我灵感,但还有改进的地方。因为它选择在文章被渲染成 html 后,对其进行正则表达式替换。这显然不够好。通过查阅官方文档和尝试,应该在文章渲染之前就开始进行替换,这更符合一般逻辑。

最终,我只用了一个晚上,一个更符合预期,更方便的插件就诞生啦!!!

Readme Card

先读例子再编码

在写新插件的过程中,我就是抱着贯彻「先看看别人怎么写,在自己改改」的理念进行的,这与我之前认为的必须把所有理论基础过一遍才能下手的观念有些不同(详看 第一次开发 npm 插件)。在学校学习过程中,我们早就熟悉了先学理论再实践的方法论,比如:学习➡️考试➡️应用。但如果在文档资料稀少或时间精力紧缺的情况下,通过模仿学习别人的例子是一个更好的选择。

这好比要在地上一堆零件或玩具中,自己尝试拆解并搭建一个小机器人,能用就行;又或是在未知的自然界中探索和考察,通过模仿和分析去制作出人使用的工具、剖析人与自然的关系。这个过程就如同在迷雾中探索,迷茫而又艰难,但每一步也能探出能落脚的地方。

在前期简单调研过程中,我又发现了一个开发者明确说出「先看别人怎么写,再自己写」的例子,印证这个方法论在程序界中不是一条野生的理论,而是有用且实际的理论。这个例子的背景大概就是一个打算编写 Hexo 插件的开发者(cedric-sun)发了条 Issue 吐槽官方文档内容过少、教程过简,Hexo 官方开发人员(curbengh)解释道最好的文档就是源代码,或者可以参考已经成形的插件

Improve guide on extension development #1202

cedric-sun:

First, we have to create a Hexo instance.

Well, seriously? You don’t have to create a hexo instance if you are developing plugins, because the hexo runtime invoked from the CLI will load your script instead. That’s merely the very first meaningful sentences of you API document, and it’s wrong. At least please add some restriction and provide some context, and avoid making too general statements, otherwise it could be very confusing to new plugins developer.

Some pages of the document is just brusque, like Processor.

Last thing. It’s good we have all those API document around, but that’s not a “guide” on extension development. Are we supposed to write our extension right in the node_modules directory under the hexo root? Or should we put it in some separate directory and refer to it in the package.json of hexo? In the latter case should we add hexo in dependencies? Or devDependencies? Or not at all? At least I was expecting some recommendation or pros-and-cons from the official team, not from some personal blogs.

I don’t think that quality of developmental documentation is consistent with a 28k stars repo. Do you have any plan to improve it?

curbengh: Transferring to https://github.com/hexojs/site as that’s the source of documentation.

curbengh: I do agree the documentation on extension development needs improvement. I initially learned plugin dev mostly by looking through the source code of existing ones and only use the doc for additional clarification. I believe most plugin devs also learned it this way. I’m not saying this approach is ideal though, it would significantly help newcomer by having more thorough examples and step-by-step guides…

正则表达式工程师

在编写这种「语法」类型的插件,正则表达式真的绕不开。好的正则表达式能准确定位我们的需求,并有着极高的检索效率。我时常使用正则表达式编程,但更多情况下我更倾向于能不用就不用,这是因为:

  1. 正则表达式过于强大,以至于我觉得它更像终极的核武器,利用得好可以发电,利用不好直接爆炸。
  2. 各种语言的正则表达式函数用法不尽相同,难记,我也只停留在每做一步就百度一步的阶段。

我最常使用的正则编程利器:正则表达式在线测试 | 菜鸟工具 (jyshare.com)

对于它的使用(无论是在编程上还是笔记排版上),我也有一些自己的总结:

  1. 时常使用工具验证自己的想法,常看表达式翻译后的自动机图形
  2. 如果仅仅是用于便利日常生活,而非写入程序(比如一些笔记使用的自动化排版脚本),编写的正则表达式覆盖痛点的主要部分即可,而不是绝大部分,不要「过拟」。
  3. 应用范围要可控,不要一键全局替换。这种情况堪比 rm -rf /*。时间允许的话可以速览即将被替换的内容。
  4. 在兼顾效率的情况下尽可能添加约束,比如前向预测、反向预测等。
  5. 将痛点分隔成不同单元,尽可能使匹配的规则更简单,并且便于配置规则的开闭。
  6. 时常备份,时常留下修改日志,及时察觉错误并改正。
  7. 为不同场景定制正则。除非是官方提供的(经过了良好测试),不要将自己写的某条正则规则不经思考引用到新的场景中。比如我的笔记中使用的正则规则,不要立即同步到博客那边,等到博客那边真的需要时再引用,以避免潜在匹配失误。
  • 由于它的重要性和复杂性,未来我将写一篇文章好好总结 Python、Java、JavaScript 使用常用正则函数的方法。