markdown 自定义的思考

本贴最后更新于 1167 天前,其中的信息可能已经事过景迁

写在前面的

已经记不清什么时候开始接触的 markdown 了,好像是从我的一个学长做了我们学校的大数据协会论坛开始的。如果我没记错的话用的还是 b3log 的开源项目,应该是使用的 Symphony。后来听学长说太复杂了,维护无从下手然后删库跑路了 😂

也正是他画的这个饼,让我从零开始设计一个社区给不同学科、不同专业的同学来使用,之前实现过的版本并不是很满意(应该在我的链滴帖子里还能找到),然后又开始了重构之路。也正是不断的与 markdown 不同的编辑器接触,也让我对 marked.js、markdown-it、for-editor、lute、vditor 等等的编辑器、编译器、渲染器有了很多的接触,对于 markdown 及其拓展语法有了各种奇奇怪怪的想法

for-editor-herb

for-editor 是一个 UI 设计非常不错的编辑器,它的底层解析引擎为 marked.js。marked.js 对于 markdown 的解析渲染在前端非常有名,并且在很多的编辑器上面也得到了应用。for-editor 很轻、很小、很美,但是也避免不了出现了不少的 bug,也不支持很多的 features。当时只算是个开源项目的萌新,给作者提 PR 没有得到回复,然后给作者发邮件还是没有回复 23333 我明显感觉到作者好像无意再继续维护下去了,在 issues 里面也帮助了一些老哥解决了一些问题。

直到有个老哥建议我不如去自己新开一个仓库,然后自己来打包发布 2333 其实吧,当时做前端顶多算个页面仔,也就是自己写写页面这个样子,压根也没接触过 npm 的库咋发布,然后就试错硬着头皮来呗。修复了很多的 bug,一度怀疑人生 😑 尤其是那个编辑器-简直是噩梦,有兴趣的同学可以了解一下前端如何实现一个富文本编辑器 🤣 我当时一度怀疑我是不是在试图写一个 word!!在编辑器开发里其实说问题吧,多也不多,也就是怎么撑开 textarea、怎么控制焦点、怎么获取选区这点问题 😈 当然这只是开个玩笑,实操起来真的是怀疑人生。

for-editor 计算行号就明显有个问题,它的行号是基于 \n 来计算的,这根本是不准确的。在打开和关闭渲染的时候由于 textarea 的宽度会发生改变。这就直接会导致,其实字它会换行,其实这跟行号就完全不匹配了。后来我的改进方案是获取容器的实际高度,然后根据字高来算实际的行数。一般默认的字体大小 font-size: 16px; ,然后明面上解决了这个问题,但是还是没能解决开关渲染导致的不准确,因为根本不会触发 height 发生改变。。。这个很大程度上跟设计机制有关系(随便看一看 HTML 就知道问题了),不过整体体验影响还可以,虽然一度让我想重构整个编辑器。愚以为 vscode 的体验真的是神仙,vscode 其实知道的都知道是基于 electron 开发的,叹为观止。。。

上面就是举个例子,在 for-editor 里面也就实现了下面的一些功能,好像有老哥还把这个用于项目了 😨

  • 更多的工具栏按钮
  • 支持数学公式的渲染
  • 支持响应式布局
  • 支持大纲跳转锚点
  • 支持大纲直接生成插入
  • 内置简繁体中文、英文和日文的支持
  • 支持自定义支持编辑器国际化
  • 支持 GitHub Diff 语法渲染(这个 vditor 好像并没有完全支持)
  • 支持自定义注册代码高亮的语言类型
  • 支持 emoji 短码渲染绘文字 emoji
  • 支持 ==markdown== 语法行内高亮

其实我之前还准备支持 mermaid 的,但是之前我一直没有解决渲染的问题,没找到渲染的时机,后来放弃支持了。到后来我其实比较抗拒维护这个项目了,因为我能明显感觉到渲染越来越慢。在这个项目的 issue 里,也有老哥给我安利过 vditor,但是他貌似不会在 React 里面用 emmm。这个项目我几乎调整了 80% 的代码结构,有一些问题不是很好修复,只能寄希望于重构 😭 但是当时又在同步写着 pyvm,没有想法继续重构下去。后来业务代码越来越多就没有继续了,被我 Achieved 了,有 4 个 fork,应该还有老哥还在维护,项目可以在 for-editor-herb 体验。

在这个项目的开发中,我为了实现一些解析语法去阅读了 marked.js 的源码。marked.js 整个项目的灵魂在于正则表达式,里面的正则表达式写的非常非常厉害。在部分 features 实现的时候也参考了,marked.js 一直到高级使用部分都非常非常熟悉。但是,性能和维护永远是最大的问题。

lute

先写 lute 的原因是 vditor 我一直在不断地关注着,并且一直发现问题和提 feature 疯狂“作妖”😁 希望 @Vanessa V 姐不会觉得我超级烦 😥

接触 lute 的原因是有一些 feature 依靠 vditor 没办法实现。举个例子,在 React Native 中如何实现 markdown 渲染?难道是套一个 WebView 加载 H5?虽然在某些场景不得不去这么实现,比如依赖 KaTeX 做数学公式渲染、abcjs 做五线谱渲染,要不就自己再写一个库?emmm WebView 会有很多的限制,而且其实在移动端做 markdown 即时渲染的体验不会很好。即使是 GitHub 官方的 APP 也没有实现,然后我的眼光就放到了 lute 的身上 23333

lute 是基于编译原理使用 go 开发的,刚好一不小心之前自学过 golang,然后开始研究 lute。看的很浅显,并没有关注生成 AST 的部分,自己关注的主要是在渲染的处理上。lute 关于内部实现方面没有文档,只能自己去看源码。lute 的代码结构设计很优秀,很容易上手去阅读定位具体的问题和实现的位置。

分析需求和具体实现:基于 lute 直接实现组件渲染其实是不实际的,即使后来 vditor 支持了自定义渲染器,但是其实仍然是 string 类型的。而 React Native 的组件是 JSX,所以我需要一棵树,自己根据这棵树来遍历递归渲染。很遗憾,lute 其实并没有提供这个方法,lute 并不是一个纯粹的解析器,而是同时是一个渲染器,直接生成了 DOM!如果你尝试在 js 里面调用 lute 暴露的某些在 godoc 有的方法,会直接报错。

其实,lute 的节点细分比 vditor 多很多,好像是有一百四十多个。并且随着特性的不断增加,这个数量还在增加。最后还是实现了“这棵树”的输出,自 lute v1.7.1 之后支持,是 lute.RenderJSON() 这个方法。这棵树实现了 vditor 绝大多数支持的语法输出,但其实 vditor 有很多很多的渲染都是在前端完成的。其实依靠这个方法,是可以自行实现与 vditor 兼容的一个纯粹的渲染器,可以自行看 lute 的文档 如何使用

当我开始基于 lute 写 npm 包的时候,发现了一个很严重的问题。lute 的包太大了,打包失败。lute 是通过 gopherjs 打包成 js 的,没办法拆包。上文提到 lute 其实并不是一个纯粹的解析器,包含了渲染的部分,不知道 @88250 D 哥有没有计划把解析的部分单独分离出去,新成立一个极高效率的 markdown 解析器的项目 emmm

如果你想要使用 lute 自定义渲染器,或者写个库什么的,这并不影响你在 web 端的使用。可以参考 rollup 外部依赖的这部分,通过 CDN 外部引入问题不大的,只是把 lute 打包会出现问题而已。

vditor

刚刚查了一下,在 vditor 总共提过 9 个 issues,开始提的问题是极蠢的,对 8 起,浪费 V 姐的时间了。不过好像我提的好几个 issue 后来的版本实现了 2333。比如这个 使用 hint at 方法报错 ,我希望通过拓展 @ 这个方法,实现 at 的并不只是一个用户,甚至是可以引用一篇文章等。

不过有一说一,我回顾了一下我的 issue 还是觉得有一些是真的 low😥vditor 的源码看了一部分,有的部分还是很有意思的,我也知道了 vditor 为什么有的方法那么实现。vditor 的 features 更新速度总是比文档快很多,如果想尝鲜之类的话,建议去阅读源码(手动滑稽)

不过,这几天发现 vditor 有一个问题我还是感觉挺严重的。当 vditor 和 SSR 一起使用的时候,可能会导致页面崩溃,甚至是浏览器崩溃。当你在 sv 模式下输入 iframe 这个 HTML 标签,可能会导致一些问题。具体讨论可以看这个 issue #918 ,需求是在这个 issue #906 提出的。

需要提醒把 vditor 当作编辑器的项目,markdown 原生支持 HTML 标签,所以除了开启 vditor 的安全过滤规避 XSS 攻击(默认打开的)之外,还需要过滤 iframe 这个标签!如果是论坛等交互式网站的话,可能无法控制用户通过 iframe 植入广告甚至是非法网站!

iframe 渲染这个需求,其实实现起来并不简单,需要尤其注意过滤 iframe 的 domain 和减少 iframe 导致的 GET 请求,有的引用网站不排除有访问的风控。在上面 issue 的讨论里我提供了两种思路,目的就是为了 iframe 的请求和过滤非法域名,我已经自行实现了第一种思路,依赖下面介绍的库实现起来其实非常简单。其实就是延时渲染或者防抖,这些其实在前端优化里面都有实际的应用,每个人都有不同的实现方式,不赘述了。

codeblock-iframe 语法

这是我在上面的思考中提出的一种语法,也许之前可能有人提也能不一定的。方法特别简单,即把 iframe 当作代码块处理,遵循 TOML 的语法,这样对代码侵入很小。

基于这个思想,我写了两个库分别支持了 webpack 等打包工具的调用和 web 端直接通过 script 进行引用,并且为 docsify 写了插件支持这种语法。

转化过程可以在这个 demo 里体验 demo

docsify 的使用在这个 demo 里体验 demo

仓库地址如下

Todos

在我的脑子里,还有很多很多的 feature 提供给 markdown 进行支持,比如最近对 markdown 支持化学结构式很感兴趣。看了现存的极少的实现方案,感觉不怎么样,可能的话只能自己去研究了,比如 zrender

因为自己是物理系的,对公式输入很敏感所以早都基于 KaTeX 尝试实现了 Tex

更多的想法的话,可能会为飞书写应用或者单独开发一个 PC 端为协同办公提供服务。虽然飞书的云文档很好用,但是还是避免不了它还是不能支撑很多的协同文档问题。所以,为团队的项目开发的话,可能会基于 vditor 实现一个协同文档的服务,有想法是借鉴 slack 实现。目标跟思源笔记的方向完全不一样,主要协同文档、文档安全、权限控制等,实现起来的架构设计和算法还是挺难的。

因为 idea 太多了,导致自己写代码的速度跟不上哈哈哈哈哈

我的其他开源项目

就写我认为好用的吧,其他的小插件都在我的仓库里很好找的

  • nbc 全称 nuco-backend-cli 是我针对限制团队 commit 风格和开发有的常用需求写的命令行工具,基于 golang 开发,支持 MacOS、Linux 和 Windows 三端,已经投入平时的开发之中使用了,并且提供了详细的文档使用文档
  • nuco-docsify 这是为了团队项目生成文档模板的工具,支持很多 feature。这样我们就不必要每一次都配置文档了,nbc 也提供了 nbc docs 直接生成文档,并且可以通过 nbc serve 直接启动静态预览!

还有一些文档翻译什么的,如果你觉得好用的话给个 Star 嗷~哈哈哈哈

相关帖子

欢迎来到这里!

我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。

注册 关于
请输入回帖内容 ...
HerbertHe
主营前端, 热衷于造轮子 ~ 现为西部计划服务新疆专项志愿者~ Nuco.Tech Studio 联合创始人 宣城