250830微小项目阅读笔记-子博客wemark项目


markdown是目前AI输出的主流格式,在前端应用中接入AI,需要考虑将文本格式的md转为可视化更强的html。而md-to-html可以将markdown类型渲染为html展示。

微小wemark

wemark解析markdown的逻辑结构

wemark解析markdown的逻辑结构可拆解为「解析层转换层渲染层」的三级处理流程,核心是将 Markdown 文本转化为小程序可识别的视图结构。

一、整体架构:三级处理流程

Markdown 文本 → [解析层] → AST(抽象语法树) → [转换层] → 小程序兼容结构 → [渲染层] → 视图展示

三层步骤的具体逻辑:

  1. 解析层:将 MD 文本解析为结构化的 AST
  2. 转换层:将 AST 转换为小程序特有的数据结构(适配 templaterich-text 组件)
  3. 渲染层:通过小程序自定义组件渲染最终视图

二、核心模块与逻辑拆解

  1. 解析层:基于 remarkable 的语法解析
    wemark 依赖成熟的 Markdown 解析库 remarkable(位于 wemark/remarkable.js)完成核心语法解析,这是工程化选型的合理选择(避免重复开发复杂的语法解析器)。
  • 核心逻辑

    • remarkable 会扫描输入的 MD 文本,通过正则匹配识别不同语法块(如标题 #、列表 -、代码块 ```、链接 []() 等)。
    • 将识别出的内容转换为 AST(抽象语法树),每个节点包含 type(如 headinglistparagraph)、content(内容)、params(参数,如标题级别、列表类型)等信息。

    示例 AST 结构(简化):

    {
    type: 'heading',
    depth: 1, // 对应 # 标题
    content: 'Hello World',
    children: [{ type: 'text', content: 'Hello World' }]
    }
  • 扩展能力
    wemarkremarkable 基础上扩展了小程序特有的语法处理(如图片路径转换、链接跳转适配),通过自定义 renderer 覆盖默认解析规则。

  1. 转换层:AST 到小程序视图结构的映射
    这是 wemark 针对小程序环境的核心适配层,位于 wemark/wemark.jsparse 方法中,负责将 AST 转换为可被小程序模板消费的数据结构。
  • 核心逻辑

    • 递归遍历 AST 节点,根据节点 type 生成对应的「小程序组件描述对象」,包含:
      • type:对应模板名称(如 h1plist);
      • attrs:节点属性(如 hrefsrcclass);
      • children:子节点数组(嵌套结构,如列表项包含文本)。

    示例转换结果(简化):

    [
    {
    type: 'h1',
    attrs: { class: 'wemark-h1' },
    children: [{ type: 'text', content: 'Hello World' }]
    }
    ]
  • 关键适配处理

    • 图片路径处理:将相对路径转换为小程序可识别的本地路径,或对网络图片添加 mode 属性适配显示。
    • 链接处理:将 <a> 标签转换为小程序 navigator 组件的跳转配置(通过 link 参数控制是否启用)。
    • 代码高亮:若开启 highlight 参数,会调用 highlight.js 对代码块进行语法着色,生成带样式的 <span> 结构。
  1. 渲染层:基于小程序模板的视图渲染
    最终通过 wemark.wxml 中的模板实现视图渲染,核心是利用小程序的 <template> 标签对不同类型的节点进行针对性渲染。
  • 核心逻辑

    • 定义一系列模板(如 tpl-h1tpl-ptpl-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 结构,利用原生组件渲染,性能较好但功能受限(如不支持视频)。

三、功能性能分析

性能局限性:

  1. 递归渲染嵌套结构(如多层列表)可能在数据量大时引发性能问题(小程序模板递归深度有限制)。
  2. 代码高亮依赖 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 项目)

  1. 安装依赖
    通过 npm 或 yarn 安装 remarkable

    npm install remarkable --save
    # 或
    yarn add remarkable
  2. 在代码中引入并使用

    // 引入 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 框架的小程序项目(如示例中 wemarkremarkable 的使用),操作如下:

  1. 拷贝文件
    remarkable.js 放入项目目录(如 wemark 目录下,参考 wemark 项目结构)。

  2. 本地引入
    在需要解析 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)

  1. 安装依赖
    同通用方式,先通过 npm 安装 remarkable

  2. 配置框架忽略编译(如 Taro)
    若框架编译时对 remarkable.js 处理有问题,需在配置中忽略(参考 wemark 对 Taro 的配置):

    // Taro 项目的 config/index.js
    module.exports = {
    weapp: {
    compile: {
    exclude: [
    'src/wemark/remarkable.js' // 忽略该文件的编译
    ]
    }
    }
    };
  3. 在代码中引入使用
    同通用方式,通过 requireimport 引入后初始化解析器。

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 的过程通常分为两个阶段:

  1. 词法分析(Lexical Analysis / Tokenization)

    • 将源代码分解成一个个有意义的“词法单元”(Token)。
    • 例如,代码 let x = 10; 会被分解为:
      [Keyword: 'let', Identifier: 'x', Operator: '=', Number: '10', Punctuator: ';']
  2. 语法分析(Syntax Analysis / Parsing)

    • 将 Token 流按照语言的语法规则组织成树状结构(AST)。
    • 这个阶段会检查语法是否正确(如括号是否匹配、语句是否完整)。

三、一个简单的例子(JavaScript)

我们以这行 JavaScript 代码为例:

let x = 1 + 2;

它可能生成的 AST(简化版)如下:

{
"type": "Program",
"body": [
{
"type": "VariableDeclaration",
"kind": "let",
"declarations": [
{
"type": "VariableDeclarator",
"id": {
"type": "Identifier",
"name": "x"
},
"init": {
"type": "BinaryExpression",
"operator": "+",
"left": {
"type": "Literal",
"value": 1
},
"right": {
"type": "Literal",
"value": 2
}
}
}
]
}
]
}

这里详细地解读一下这个 AST:

  • Program:整个程序的根节点。
  • VariableDeclaration:变量声明,kind: "let" 表示是 let 声明。
  • VariableDeclarator:具体的声明,id 是变量名 xinit 是初始化值。
  • BinaryExpression:二元表达式,表示 1 + 2
  • Literal:字面量,表示常量值。

四、AST 的特点

特点 说明
抽象性 忽略了源码中的具体符号(如分号、括号),只保留结构。
层次性 树形结构,父子节点体现语法的嵌套关系。
可遍历性 可以通过深度优先或广度优先遍历所有节点。
可修改性 修改 AST 节点可以实现代码转换(如 Babel 转译 ES6 → ES5)。

文章作者: Qijia Huang
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Qijia Huang !
  目录