markdown是目前AI输出的主流格式,在前端应用中接入AI,需要考虑将文本格式的md转为可视化更强的html。而md-to-html可以将markdown类型渲染为html展示。
微小wemark
wemark解析markdown的逻辑结构
wemark解析markdown的逻辑结构可拆解为「解析层→转换层→渲染层」的三级处理流程,核心是将 Markdown 文本转化为小程序可识别的视图结构。
一、整体架构:三级处理流程
Markdown 文本 → [解析层] → AST(抽象语法树) → [转换层] → 小程序兼容结构 → [渲染层] → 视图展示 |
三层步骤的具体逻辑:
- 解析层:将 MD 文本解析为结构化的 AST
- 转换层:将 AST 转换为小程序特有的数据结构(适配
template
或rich-text
组件) - 渲染层:通过小程序自定义组件渲染最终视图
二、核心模块与逻辑拆解
- 解析层:基于
remarkable
的语法解析wemark
依赖成熟的 Markdown 解析库remarkable
(位于wemark/remarkable.js
)完成核心语法解析,这是工程化选型的合理选择(避免重复开发复杂的语法解析器)。
核心逻辑:
remarkable
会扫描输入的 MD 文本,通过正则匹配识别不同语法块(如标题#
、列表-
、代码块 ```、链接[]()
等)。- 将识别出的内容转换为 AST(抽象语法树),每个节点包含
type
(如heading
、list
、paragraph
)、content
(内容)、params
(参数,如标题级别、列表类型)等信息。
示例 AST 结构(简化):
{
type: 'heading',
depth: 1, // 对应 # 标题
content: 'Hello World',
children: [{ type: 'text', content: 'Hello World' }]
}扩展能力:
wemark
在remarkable
基础上扩展了小程序特有的语法处理(如图片路径转换、链接跳转适配),通过自定义renderer
覆盖默认解析规则。
- 转换层:AST 到小程序视图结构的映射
这是wemark
针对小程序环境的核心适配层,位于wemark/wemark.js
的parse
方法中,负责将 AST 转换为可被小程序模板消费的数据结构。
核心逻辑:
- 递归遍历 AST 节点,根据节点
type
生成对应的「小程序组件描述对象」,包含:type
:对应模板名称(如h1
、p
、list
);attrs
:节点属性(如href
、src
、class
);children
:子节点数组(嵌套结构,如列表项包含文本)。
示例转换结果(简化):
[
{
type: 'h1',
attrs: { class: 'wemark-h1' },
children: [{ type: 'text', content: 'Hello World' }]
}
]- 递归遍历 AST 节点,根据节点
关键适配处理:
- 图片路径处理:将相对路径转换为小程序可识别的本地路径,或对网络图片添加
mode
属性适配显示。 - 链接处理:将
<a>
标签转换为小程序navigator
组件的跳转配置(通过link
参数控制是否启用)。 - 代码高亮:若开启
highlight
参数,会调用highlight.js
对代码块进行语法着色,生成带样式的<span>
结构。
- 图片路径处理:将相对路径转换为小程序可识别的本地路径,或对网络图片添加
- 渲染层:基于小程序模板的视图渲染
最终通过wemark.wxml
中的模板实现视图渲染,核心是利用小程序的<template>
标签对不同类型的节点进行针对性渲染。
核心逻辑:
- 定义一系列模板(如
tpl-h1
、tpl-p
、tpl-list
),与转换层生成的type
一一对应。 - 通过
<template is="{{item.type}}" data="{{...item}}" />
实现动态模板选择,递归渲染嵌套结构(如列表包含列表项,列表项包含文本)。
示例模板(简化):
<!-- 标题模板 -->
<template name="tpl-h1">
<view class="wemark-h1 {{item.attrs.class}}">
<template is="renderChildren" data="{{children: item.children}}" />
</view>
</template>
<!-- 递归渲染子节点 -->
<template name="renderChildren">
<block wx:for="{{children}}" wx:key="index">
<template is="{{item.type}}" data="{{...item}}" />
</block>
</template>- 定义一系列模板(如
两种渲染模式:
type="wemark"
:使用上述自定义模板渲染,支持完整功能(链接跳转、视频播放等)。type="rich-text"
:转换为小程序rich-text
组件的nodes
结构,利用原生组件渲染,性能较好但功能受限(如不支持视频)。
三、功能性能分析
性能局限性:
- 递归渲染嵌套结构(如多层列表)可能在数据量大时引发性能问题(小程序模板递归深度有限制)。
- 代码高亮依赖
highlight.js
,会增加包体积,需在功能与性能间权衡。
性能优化方向:
- 按需解析:仅在
md
数据变化时重新解析(通过组件生命周期observers
监听),避免重复计算。 - 代码高亮懒加载:
highlight.js
体积较大,仅在开启highlight
参数时加载,减少初始包体积。 - 模板复用:通过递归模板
renderChildren
处理嵌套结构,避免代码冗余。
如何引用remarkable
库?
注:remarkable
库核心参数说明
初始化 Remarkable
时可配置的常用参数如下:
html: boolean
:是否允许解析 HTML 标签(默认false
)。breaks: boolean
:是否将换行符转换为<br>
(默认false
)。linkify: boolean
:是否自动识别链接文本并转换为<a>
标签(默认false
)。langPrefix: string
:代码块的 class 前缀(默认language-
),用于代码高亮。
更多配置可参考 remarkable 官方文档。
一、通用方式(原生 JavaScript/Node.js 项目)
安装依赖
通过 npm 或 yarn 安装remarkable
:npm install remarkable --save
# 或
yarn add remarkable在代码中引入并使用
// 引入 remarkable
const Remarkable = require('remarkable');
// 初始化解析器(可配置参数,如允许 HTML、启用插件等)
const md = new Remarkable({
html: true, // 允许解析 HTML 标签
breaks: true, // 支持换行符转换为 <br>
linkify: true // 自动链接文本自动转换为链接
});
// 解析 Markdown 文本
const markdownText = '# 标题\n\n这是一段 **加粗** 的文本';
const htmlOutput = md.render(markdownText);
console.log(htmlOutput);
// 输出: <h1>标题</h1>\n<p>这是一段 <strong>加粗</strong> 的文本</p>
二、在微信小程序项目中引用(以 wemark
依赖为例)
如果是基于 wemark
框架的小程序项目(如示例中 wemark
对 remarkable
的使用),操作如下:
拷贝文件
将remarkable.js
放入项目目录(如wemark
目录下,参考wemark
项目结构)。本地引入
在需要解析 Markdown 的文件中直接引入本地remarkable.js
:// 例如在 parser.js 中(参考 wemark 的实现)
const Remarkable = require('./remarkable'); // 路径根据实际位置调整
// 初始化解析器
const parser = new Remarkable({
html: true // 允许解析 HTML 标签(如视频标签)
});
// 解析 Markdown 为 tokens(供后续处理)
const tokens = parser.parse(markdownText, {});
三、在框架项目中引用(如 Taro/mpvue)
安装依赖
同通用方式,先通过 npm 安装remarkable
。配置框架忽略编译(如 Taro)
若框架编译时对remarkable.js
处理有问题,需在配置中忽略(参考wemark
对 Taro 的配置):// Taro 项目的 config/index.js
module.exports = {
weapp: {
compile: {
exclude: [
'src/wemark/remarkable.js' // 忽略该文件的编译
]
}
}
};在代码中引入使用
同通用方式,通过require
或import
引入后初始化解析器。
AST简介
AST 是 抽象语法树(Abstract Syntax Tree)的缩写,它是源代码语法结构的一种抽象表示,以树状的形式表现编程语言的语法结构。md-to-html等代码转换(babel)、代码编辑器的高亮显示 与 代码规范化(如vscode的eslint规范化检查插件和preffier代码格式化插件)都用到了AST。
注:
个人学习AST看的好文:【AST 技术学习 - CSDN App】
可视化 AST 结构网站:https://astexplorer.net/
下面这段内容也许可以帮你快速理解AST的构成与生成过程:
一、AST是什么
- AST 是 源代码经过词法分析和语法分析后生成的树形结构。
- 它省略了源代码中与语法无关的细节(例如括号、分号、空格等),只保留程序的结构信息。
- 每个节点代表源代码中的一个结构,比如表达式、语句、函数、变量声明等。
二、AST 的生成过程
源代码 → AST 的过程通常分为两个阶段:
词法分析(Lexical Analysis / Tokenization)
- 将源代码分解成一个个有意义的“词法单元”(Token)。
- 例如,代码
let x = 10;
会被分解为:[Keyword: 'let', Identifier: 'x', Operator: '=', Number: '10', Punctuator: ';']
语法分析(Syntax Analysis / Parsing)
- 将 Token 流按照语言的语法规则组织成树状结构(AST)。
- 这个阶段会检查语法是否正确(如括号是否匹配、语句是否完整)。
三、一个简单的例子(JavaScript)
我们以这行 JavaScript 代码为例:
let x = 1 + 2; |
它可能生成的 AST(简化版)如下:
{ |
这里详细地解读一下这个 AST:
Program
:整个程序的根节点。VariableDeclaration
:变量声明,kind: "let"
表示是let
声明。VariableDeclarator
:具体的声明,id
是变量名x
,init
是初始化值。BinaryExpression
:二元表达式,表示1 + 2
。Literal
:字面量,表示常量值。
四、AST 的特点
特点 | 说明 |
---|---|
抽象性 | 忽略了源码中的具体符号(如分号、括号),只保留结构。 |
层次性 | 树形结构,父子节点体现语法的嵌套关系。 |
可遍历性 | 可以通过深度优先或广度优先遍历所有节点。 |
可修改性 | 修改 AST 节点可以实现代码转换(如 Babel 转译 ES6 → ES5)。 |