玩转Markdown —— 数据的拆散存储与组件的原生渲染
前言
最近笔者把之前写的文章(markdown)数据,全副同步到数据库里,来交给多端去实时渲染。在同步的过程中,却呈现了一些问题。
笔者这里举个例子,让大家有所感触:
<!-- 这是一份markdown文件 -->---author: icebreakermusic: title: '喜爱寂寞' artist: '苏打绿' src: '${{env.CDN_URL}}/music/喜爱寂寞-苏打绿.m4a' pic: 'http://p1.music.126.net/NGBr80seZ96ILO2h8R390A==/18576248952955358.jpg?param=130y130'---# icebreaker喜爱的音乐<icebreaker-love-music :music="music"></icebreaker-love-music>
上列文本解析后,会在 浏览器 这个环境,应用 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,而去转化为原生标签怎么做呢?解决方案也有很多。
先说一下我实现的计划:
即 <icebreaker-love-music :music="music"></icebreaker-love-music> 这一段字符串一成不变的存入数据库中,
而后在其余平台的场景,都去编写或者移植一个Markdown解析器,接着呢
# 如伪代码所示onParse: mdast if: match(node.name , 'icebreaker-love-music') then: replace and return <native component code>(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