玩转Markdown —— 数据的分离存储与组件的原生渲染
前言
最近笔者把之前写的文章(markdown
)数据,全部同步到数据库里,来交给多端去实时渲染。在同步的过程中,却出现了一些问题。
笔者这里举个例子,让大家有所感受:
---
author: icebreaker
music:
title: '喜欢寂寞'
artist: '苏打绿'
src: '${{env.CDN_URL}}/music/喜欢寂寞-苏打绿.m4a'
pic: 'http://p1.music.126.net/NGBr80seZ96ILO2h8R390A==/18576248952955358.jpg?param=130y130'
---
# icebreaker喜欢的音乐
上列文本解析后,会在 浏览器 这个环境,使用 icebreaker-love-music
这个组件构建一个音频播放器,并把在 yaml
里面声明的数据,作为 props
传递给它,从而达成了在 markdown
中使用 vue
,react
,web component
组件的效果。
而且通过这个思路,还演化出 MDX
这个格式,大大的增强了 JSX
与 Markdown
混合书写时的开发体验,增强了它的表现能力。
怎么做到的呢?
我们知道,原生 Markdown
功能很少,不会做任何花哨的事情,这导致它无法满足大量的场景。于是乎,大量的开发人员充分发挥了 主观能动性 ,定制了许多的Markdown
编译器。
以著名的 Typora
为例,它就集成了 flowchart.js
, mermaid
这类的图表库。我们可以在md
里快速的生成一些简单的图表,但是遇到复杂的 case
时,可操控性还是远远弱于代码的。(这种情况,通常会在编辑器外部,先把图表做好,再把图片导出,插入md
里)
甚至还出现了 nodeppt 这样,使用 markdown
来制作 ppt
的包。笔者曾经使用过一段时间,认为使用的场景,还是以部门内部的分享为主。受限于许多难记的语法和md
自身的表现力,在遇到高自定义化的场景时,制作成本会远远超出powerpoint
。
markdown 数据的分离存储
那么进入正题了,如何对 markdown
内不同的数据进行归类呢?
我们知道,不进行预处理的话,直接存进数据库里,无非就是一堆字符串。这堆字符串里藏着的数据,去实时处理,就是对计算机算力的浪费。
许多的 markdown
解析器,也都能够支持像 yaml
,json
,toml
,csv
等数据格式,此时预先把它们存进数据库就很有必要了。
怎么解析呢? 通常的做法就2字,标记 ,在编写时,把它们用特殊的flag
标识起来,比较通用的做法有:
---\n{{code}}\n---
=> yaml
---toml\n{{code}}\n---
=> toml
---json\n{{code}}\n---
=> json
这种做法本质上,和代码染色类似:
```js(染色语言)\n{{code}}\n```
于是在标记出来之后,我们就可以非常容易的,对这堆字符串,进行 截取解析
再 分发给不同的解析引擎处理
了。现有的实现也很多,比如 gray-matter
。
但是这只解决了数据分离的问题,还有一个组件渲染的问题没有解决。
组件的原生渲染
在谈这个之前,先看看 md
是如何转成 html
的:
以 markded
,markdown-it
,unified(remark)
为例
它们无非是 把 md
先解析成 tokens/mdast
, 例如:
{
type: 'root',
children: [
{
type: 'heading',
depth: 2,
children: [
{type: 'text', value: 'Hello, '},
{
type: 'emphasis',
children: [{type: 'text', value: 'World'}]
},
{type: 'text', value: '!'}
]
}
]
}
然后再交给 html
的 renderer
去处理的,上述的例子可以很容易的看出它的结果。
那么非转化成 html
,而去转化为原生标签怎么做呢?解决方案也有很多。
先说一下我实现的方案:
即
这一段字符串原封不动的存入数据库中,
然后在其他平台的场景,都去编写或者移植一个Markdown
解析器,接着呢
# 如伪代码所示
onParse: mdast
if: match(node.name , 'icebreaker-love-music')
then: replace and return (node.attrs)
这种做法本质就是条件渲染,相当于一个 if
分支。
这个解决方案需要在不同的平台上,把 icebreaker-love-music
这个组件都实现一遍,并作为插件挂载在 Markdown
解析器中。
它的缺点也是很明显的:
- 即使各自平台的生态下,已经存在了优秀的解决方案,但无法保证各自的实现以及插件的效果。
- 工作量大,原生需要不同语言,实现相同的组件效果。
- 死板,当发现获取的数据中有不明组件,就需要
fallback
处理,这种会造成和后台那些管理系统的富文本/Markdown编辑器
,产生高度的耦合,甚至会影响到版本的发布。
另一种的畅想
另外一种则是我的畅想了,我们能否把组件本身,进行编译,变成一种 IL
(Intermediate Language)的存在,交给各个端,进行原生渲染呢?
比如我们知道,web component
浏览器端原生支持
vue
组件可以被 @vue/web-component-wrapper
转化为 web-component
react
则有 react-web-component
那么 web-component
有可能,能依托一个像 QuickJS
这样的 Javascript Engine
,在原生环境进行实时的编译渲染吗?
以上这些就是笔者的一些愚见,如有想法,欢迎大家讨论和指点。
附录(ast的生成与转化)
syntax-tree
mdast-util-from-markdown
mdast-util-to-hast