前情提要
想写一本书,尝试了 LeanPub,支持 markdown,太好了!但是没有所见即所得功能,预览起来的步骤非常复杂而且慢。又试了一下 Overleaf,非常好,可惜 LaTex 学习曲线陡峭,仅支持基本的 markdown,如果我在 markdown 里嵌入 mermaidjs,就不能正常预览了。语雀可以预览 mermaidjs,编辑功能也非常棒,但是整个目录组织调整起来比较麻烦。又试了 Confluence、notion 等等,都感觉不满意。
最终还是决定使用静态站点生成工具来做,试了 nextjs、gatsbyjs 等,都略重。发现 vitepress 非常轻量,大喜!它不仅支持我想要的几乎所有功能,而且非常灵活可以自定义,并且支持在线编辑和实时预览!
不过,一番操作下来,就发现有一丢丢小小的瑕疵,那就是不支持层级组织文件结构,有点失望。但是,这已经是过去时了,现在已经支持,这都得感谢 ChatGPT!
最终效果:完美的 markdown 写作体验
https://identity.jefftian.dev/
我很喜欢 mermaidjs,希望可以随时在写作中插入 mermaidjs 图形以辅助表达,虽然上面提到语雀也可以,但是不能尝鲜使用 mermaidjs 还未正式发布的 beta 特性!而使用 vitepress,只需要在项目中引用它的 beta 包即可:
json dependencies: { @mermaid-js/mermaid-mindmap: ^9.3.0, mermaid: ^9.3.0, }
另外,对于写书,我希望有这样的层级结构,每一部分、每一章节,都有各自的目录,最终通过一个主入口文件包含它们。即主文件依次包含进第一层文件,而每一个第一层文件依次包含进各个章文件,然后每个章文件包含进每一节文件,如此嵌套下去。
比如入口文件是这样的:
markdown [[toc]]
而第 1 部分的内容是这样的:
markdown ...
第一章里又有其他的节,如此嵌套包含。
最终只需要通过主入口文件地址访问,就能渲染所有内容:https://identity.jefftian.dev/main
实现过程
我这样组织好 markdown 文件后,打开 main 后发现只能看到第一级被包含的文件,更深层的文件,根本没有在 main 中被渲染出来。于是,先去 vitepress 许了个愿。因为之前在别的开源项目里,有许愿成功过,即想要一个功能,在 Issues 里描述了一下,然后就有人帮忙实现了: https://github.com/NervJS/taro-ui/issues/1582
许愿
于是这次也希望有人帮忙实现这个功能,就去 vuejs/vitepress 里提了这个需求: https://github.com/vuejs/vitepress/issues/2544
分析
但是这次,等了几天没有人响应,又急着要用这个功能,于是准备自己动手,就开始分析了一下,找到了相关的源代码:
很快找到了文件包含的相关代码,明显看出只处理一层包含,不支持嵌套。我的直觉告诉我,修改起来应该很简单,递归处理就行了。
先写测试
但是得抑制住直接修改实现代码的冲动,先写个测试,来重现不能嵌套的问题。这个我比较擅长,因为我有 TDD 的习惯,现在还轮不到找 ChatGPT 帮忙。
测试准备
我先添加了一个 markdown 文件,并在文件内容里加上了包含另一个文件的指令:
然后,在一级文件中添加包含这个新加的文件的指令:
实现测试用例
比较简单,定位到新增加内容的标题的锚点(vitepress 会自动给标题添加锚点,规则是大致将字母全改成小写,并使用短划线替换空格),再基于这个锚点往后寻找最近的一级标题,确认它的 id 是被嵌套包含进来的那个 foo-1 (因为使用了同一个 foo.md文件,其同一个标题在页面上出现了两次,第二个标题自动生成的 id,为了和第一个区别开,vitepress 自动给它添加了一个编号后缀,就和多次下载同一个文件时,文件名会增加后缀一样)即可。
在实现测试用例时,也顺手改了一下文档,以终为始嘛。
运行测试,显然失败了,因为被深层嵌套包含的文件,最终没能渲染出现,自然也找不到对应的锚点。
修改实现代码
在这里,我意识到自己的基本功太差,反复修改了几版,就是改不好。我明明还专门反复阅读了《<font style=color:rgb(133, 133, 133);>计算机程序的构造与解释》(简称 SICP)一书,还做了不少练习,并放到了网上:https://sicp.jiwai.win/zh_cn/。这些练习里我印象最深的就是 lisp 语言不支持循环等控制语句,很多时候,都需要刻意使用递归来解决问题,但我不知道为什么,当遇到实际问题时,就又不会了。总之,我的直觉告诉我,只要把当前实现中的 replacer 函数,修改成递归运行的,就能通过测试用例,但花了很久也没能成功改好。
ChatGPT 来帮忙
恼羞成怒的我,打开 ChatGPT,将现有代码扔给它,问怎么让它支持嵌套包含这个功能?
果不其然,ChatGPT 给到的解决方案就是用递归!但它不仅给思路,还直接给了一段代码!
我半信半疑,将它拷贝粘贴到我克隆的 vuejs/vitepress 项目里,重新运行测试,居然通过了!!!
显然,论写代码,ChatGPT 比我强多了!我想基于它给的答案再优化一下,无奈水平有限,最终只是将它的 processedContent 变量内联,直接 return 了。这一个优化项显而易见,我也就只能做这种显而易见的事情了……
提交 Pull request
既然通过了测试,我就直接提交了代码,并给 vuejs/vitepress 发了 PR:
PR 被采纳
没想到,PR 很快得到了 vitepress 维护者的采纳!就这样轻松混了个 vuejs 组织下的贡献者身份。
彩蛋
当然,维护者毕竟是高手,对代码又做了进一步的优化,让我们一起来围观大佬做了哪些改进吧!
结语
这就是我利用 ChatGPT 给知名开源项目做贡献的全部实录,希望也给你勇气:**基本功太差并不要紧,善于利用工具,还是可以改变世界的!**现在,我有了一个完美的 markdown 写作神器(改变了自己)!
当然,我的 PR 虽然被采纳,但目前官方并未发布新的版本。但是我已经在利用这个新功能了,怎么做到的呢?其实是发布了一个自己的版本:https://www.npmjs.com/package/@jeff-tian/vitepress
但是,这只是临时做法,我相信官方很快会发布新的版本,到时候所有人都可以使用嵌套包含的功能了。耶!又改变了世界!!(一丢丢……)
希望本文给你启发,并激励你也多做开源贡献。同时,对于写书、写文档的同学们,我在此墙裂向你们安利 vuejs/vitepress!