将 Markdown 转为 AST:实现思路与实战解析

将 Markdown 转为 AST:实现思路与实战解析

在前端开发中,有时我们不仅仅需要渲染 Markdown,还希望对其结构进行精细化操作,比如自定义组件渲染、添加交互或者导出成不同格式。这时,把 Markdown 转为 AST(抽象语法树)就非常必要。本文将分享一个完整的实现思路,并提供 TypeScript 实现示例。


功能目标

我们的目标是:

  1. 将 Markdown 文本解析成一棵 AST 树;
  2. 支持文本、代码块、HTML 标签、换行等节点类型;
  3. 支持自定义标签(如 <map> 标签)和属性解析;
  4. AST 树便于后续渲染或进一步处理。

效果示意:

ini 复制代码
const markdown = `
# 标题
这是 **加粗** 文本
<map name="myMap">地图</map>
`;

const ast = mdToAST(markdown);
console.log(ast);

输出:

css 复制代码
[  { "type": "heading", "attrs": { "level": 1 }, "children": [{ "type": "text", "text": "标题" }] },
  { "type": "paragraph", "children": [
      { "type": "text", "text": "这是 " },
      { "type": "strong", "children": [{ "type": "text", "text": "加粗" }] },
      { "type": "text", "text": " 文本" }
  ]},
  { "type": "map", "attrs": { "name": "myMap" }, "children": [
      { "type": "text", "text": "地图" }
  ]}
]

实现思路

实现主要分为两个步骤:

  1. 将 Markdown 转为 Token
  2. 将 Token 转为 AST

1️⃣ 使用 markdown-it 解析 Markdown 为 Token

markdown-it 是一款强大的 Markdown 解析器,它会将 Markdown 文本解析为 token 列表,每个 token 描述了一个语法结构。

ini 复制代码
import MarkdownIt from 'markdown-it';

const md = new MarkdownIt({ html: true });
const tokens = md.parse(markdown, {});

Token 示例:

json 复制代码
[
  { "type": "heading_open", "tag": "h1", "attrs": null },
  { "type": "inline", "content": "标题", "children": [...] },
  { "type": "heading_close", "tag": "h1" }
]

2️⃣ 将 Token 转为 AST

Token 的解析是核心步骤,我们需要处理以下几种情况:

文本与内联代码
go 复制代码
if (token.type === 'text' || token.type === 'code_inline') {
  addNode({ type: 'text', text: token.content });
}
自定义 HTML 标签 <map>

我们使用正则匹配 <map ...> 标签,并解析属性:

typescript 复制代码
const MAP_TAG_OPEN_REGEX = /^<map\s+([^>]*)>/i;

function parseAttrs(attrString: string): Record<string, string> {
  const attrs: Record<string, string> = {};
  const regex = /([\w-:]+)\s*=\s*"([^"]*)"/g;
  let match;
  while ((match = regex.exec(attrString))) {
    attrs[match[1]] = match[2];
  }
  return attrs;
}

解析 <map name="myMap">地图</map>,生成 AST 节点:

json 复制代码
{ "type": "map", "attrs": { "name": "myMap" }, "children": [{ "type": "text", "text": "地图" }] }
块级与内联节点的递归

使用 stack 来维护嵌套结构:

go 复制代码
if (token.type.endsWith('_open')) {
  const node: ASTNode = { type, attrs: {}, children: [] };
  addNode(node);
  stack.push({ node });
}

if (token.type.endsWith('_close')) {
  stack.pop();
}
换行处理
ini 复制代码
if (token.type === 'softbreak') addNode({ type: 'softbreak' });
if (token.type === 'hardbreak') addNode({ type: 'hardbreak' });
内联 token 递归
ini 复制代码
if (token.type === 'inline' && token.children) {
  const childrenAST = tokensToAST(token.children);
  childrenAST.forEach(child => addNode(child));
}

核心亮点

  1. 支持自定义标签
    支持 <map> 等自定义 HTML 标签,并能解析属性,便于做可交互组件渲染。
  2. 完整的 AST 结构
    支持嵌套结构,通过栈维护父子关系,AST 可以直接用于渲染或导出其他格式。
  3. 文本与 HTML 混合处理
    文本、内联代码、HTML 块、换行都能正确处理,保证 Markdown 原意保留。
  4. 可扩展性强
    新增自定义标签只需增加正则匹配即可,整体解析逻辑清晰。

总结

本文展示了如何用 TypeScript 将 Markdown 转为 AST,并支持自定义标签和属性解析。核心思路是:

  1. 使用 markdown-it 将 Markdown 转为 token;
  2. 递归解析 token,处理文本、内联代码、HTML、自定义标签、换行等;
  3. 利用栈维护嵌套关系,生成完整的 AST。

这种方式非常适合在前端进行自定义渲染、编辑器实现、导出格式转换等场景。

相关推荐
徐小夕8 小时前
花了3天,我写了一款开源AI公众号编辑器
前端·vue.js·github
PBitW9 小时前
git 中容易遗忘的点 (二) ⚡⚡⚡
前端·git·面试
Hilaku9 小时前
2026 年前端大洗牌:React 服务端到 Vite 的底层重构
前端·javascript·程序员
PBitW9 小时前
git 中容易遗忘的点 (三) 🚀🚀🚀
前端·git·面试
名字还没想好☜9 小时前
React setState 连续调用「丢更新」排查:批量更新与函数式更新
前端·javascript·react.js·react·usestate
come112349 小时前
Vue 2 与 Vue 3 语法区别及使用技巧
前端·javascript·vue.js
腻害兔9 小时前
【若依项目-产品经理视角】RuoYi-Vue-Pro 源码拆解:ERP 企业资源模块,一个轻量级进销存的完整实现?
前端·javascript·vue.js·人工智能·前端框架·产品经理·ai编程
虹科网络安全10 小时前
艾体宝新闻|从 SQL 注入到服务器接管:CVE-2026-57517 暴露 Web 管理面板的供应链与安全编码风险
服务器·前端·sql
糖果店的幽灵10 小时前
langgraph的四种state解析
java·前端·javascript·langgraph
顺颂时绥_110 小时前
前端大图片压缩与安卓/苹果设备矫正实践
前端