MarkdownOnline多人协作编辑器

MarkdownOnline多人协作开发

markdown多人协同在线编辑器

一个功能完整、架构清晰的 Markdown 到 HTML 转换器,采用经典的编译器设计模式,通过词法分析、语法分析和渲染三个阶段,将 Markdown 文本高效转换为 HTML 代码。

📋 目录

🎯 项目简介

markdown2html-parser 是一个轻量级、高性能的 Markdown 解析器,采用 TypeScript 编写,支持将 Markdown 文本转换为标准的 HTML 代码。项目遵循编译器设计原则,通过词法分析(Lexer)、语法分析(Parser)和渲染(Render)三个阶段,实现了完整的 Markdown 解析流程。

设计理念

本项目采用经典的编译器前端设计模式

复制代码
Markdown 文本 → [词法分析] → Token 流 → [语法分析] → AST → [渲染] → HTML

这种设计使得代码结构清晰、易于维护和扩展,同时保证了解析的准确性和性能。

✨ 核心特性

1. 完整的 Markdown 语法支持

  • 标题 :支持 H1-H6 六级标题(#######
  • 文本样式 :加粗(**bold**__bold__)、斜体(*italic*_italic_)、删除线(~~text~~
  • 链接和图片:支持标准 Markdown 链接和图片语法
  • 代码 :行内代码(code)和代码块(` ```code````)
  • 列表 :有序列表(1. item)和无序列表(*+-
  • 引用 :块引用(> quote
  • 分隔线 :水平分隔线(---
  • 复杂组合:支持多种样式组合(如加粗链接、斜体加粗等)

2. 模块化架构

  • 词法分析器(Lexer):将 Markdown 文本解析为 Token 流
  • 语法分析器(Parser):将 Token 流转换为抽象语法树(AST)
  • 渲染器(Render):将 AST 渲染为 HTML 代码

3. 类型安全

  • 完整的 TypeScript 类型定义
  • 严格的类型检查,确保代码质量

4. 多模块系统支持

  • 支持 ESM(ES Module)导入
  • 支持 CommonJS 导入
  • 支持默认导入

🏗️ 架构设计

项目结构

复制代码
markdown2html-parser/
├── src/                          # 源代码目录
│   ├── md2html.ts               # 主入口函数,协调整个解析流程
│   ├── lexer.ts                 # 词法分析器
│   ├── parser.ts                # 语法分析器
│   ├── render.ts                # HTML 渲染器
│   └── common/
│       └── html_regexp.ts       # 正则表达式定义
├── dist/                         # 编译输出目录
├── index.ts                      # 包入口文件,导出 API
├── package.json                  # 项目配置
└── tsconfig.json                 # TypeScript 配置

数据流

复制代码
输入 Markdown 文本
    ↓
[Lexer] 词法分析
    ↓
Token[] 数组
    ↓
[Parser] 语法分析
    ↓
AST (抽象语法树)
    ↓
[Render] 渲染
    ↓
输出 HTML 字符串

🔍 核心代码解析

1. 主入口函数 (src/md2html.ts)

这是整个解析流程的协调者,将三个阶段串联起来:

5:9:src/md2html.ts 复制代码
export function markdownToHtml(input: string): string {
  const tokens = lexer(input); // 词法分析,生成 tokens
  const ast = parser(tokens); // 语法分析,生成 AST
  return render(ast); // 渲染 AST 为 HTML
}

设计亮点

  • 简洁的 API 设计,一行代码完成转换
  • 清晰的职责分离,每个阶段独立处理

2. 词法分析器 (src/lexer.ts)

词法分析器负责将 Markdown 文本解析为 Token 流。核心逻辑包括:

Token 类型定义
24:58:src/lexer.ts 复制代码
type Token = {
  type:
    | "text"
    | "header"
    | "bold"
    | "italic"
    | "strikethrough"
    | "link"
    | "image"
    | "code"
    | "code_block"
    | "newline"
    | "list_item"
    | "horizontal_rule"
    | "blockquote"
    | "complex"
    | "order_list"
    | "unorder_list";
  content?: string; // 适用于 header, bold, italic, strikethrough, code, code_block, list_item, blockquote
  value?: string; // 适用于 text 类型
  level?: number; // 适用于 header
  href?: string; // 适用于 link
  text?: string; // 适用于 link
  src?: string; // 适用于 image
  alt?: string; // 适用于 image
  children?: String[]; // **允许所有 Token 存储子 Token**
  start?: number; //有序列表开始的顺序,默认为1
  listArr?: Array<{
    value: string;
    type: "order_list" | "unorder_list";
    start: number;
    isFirst: number;
  }>;
  isFirst?: number; //判断是否为第一项
};
核心解析函数
60:203:src/lexer.ts 复制代码
function lexer(input: string): Token[] {
  const tokens: Token[] = [];
  console.log("原生代码");
  console.log(input);
  const lines = input.split("\n"); // 按行分割输入文本

  lines.forEach((line, index) => {
    const token = TypeCheck(line);
    // 打印整个 token 数组的 JSON 字符串(已格式化)
    // console.log("打印返回的数组");
    console.log(JSON.stringify(token, null, 2));

    // console.log("输出数组位置");
    // console.log(index);
    // 遍历 token 数组,获取每个 token 的键值对
    const tokenType = token["type"] as string;
    const tokenContent = (token["content"] as string) ?? "";
    const tokenValue = (token["value"] as string) ?? "";
    const tokenLevel = (token["level"] as number) ?? 0;
    const tokenHref = (token["href"] as string) ?? "";
    const tokenText = (token["text"] as string) ?? "";
    const tokenSrc = (token["src"] as string) ?? "";
    const tokenAlt = (token["alt"] as string) ?? "";
    const tokenChildren = (token["children"] as string[]) ?? [];
    const tokenStart = (token["start"] as number) ?? 1;

    if (index !== 0) {
      tokens.push({ type: "newline" });
    }
    // 处理不同类型的 Token
    switch (tokenType) {
      case "text":
        console.log(token["value"]);
        tokens.push({
          type: "text",
          value: tokenValue,
        });
        break;

      case "header":
        tokens.push({
          type: "header",
          content: tokenContent,
          level: tokenLevel,
        });
        break;

      case "bold":
        tokens.push({
          type: "bold",
          content: tokenContent,
        });
        break;

      case "italic":
        tokens.push({
          type: "italic",
          content: tokenContent,
        });
        break;

      case "strikethrough":
        tokens.push({
          type: "strikethrough",
          content: tokenContent,
        });
        break;

      case "link":
        tokens.push({
          type: "link",
          href: tokenHref,
          text: tokenText,
        });
        break;

      case "image":
        tokens.push({
          type: "image",
          src: tokenSrc,
          alt: tokenAlt,
        });
        break;

      case "code":
        tokens.push({
          type: "code",
          content: tokenContent,
        });
        break;

      case "code_block":
        tokens.push({
          type: "code_block",
          content: tokenContent,
        });
        break;

      case "horizontal_rule":
        tokens.push({
          type: "horizontal_rule",
        });
        break;

      case "blockquote":
        tokens.push({
          type: "blockquote",
          content: tokenContent,
        });
        break;

      case "complex":
        tokens.push({
          type: "complex",
          children: tokenChildren,
          content: tokenContent,
        });
        break;
      case "newline":
        tokens.push({
          type: "newline",
        });
        break;
      case "order_list":
        tokens.push({
          type: "order_list",
          value: tokenValue,
          start: tokenStart,
        });

        break;
      case "unorder_list":
        tokens.push({
          type: "unorder_list",
          value: tokenValue,
        });
        break;
      default:
        console.warn(`未知的 Token 类型: ${tokenType}`);
        break;
    }
  });
  return tokens;
}

设计亮点

  • 按行处理,简化逻辑
  • 支持复杂样式组合(通过 complex 类型)
  • 完整的类型检查机制
类型检测函数
205:392:src/lexer.ts 复制代码
function TypeCheck(input: string): Object {
  const token: { [key: string]: any } = {};
  const status: Array<string> = [];

  // 内容保存
  let content: string = "";

  const headerReg = [h1Regex, h2Regex, h3Regex, h4Regex, h5Regex, h6Regex];
  for (let i = 0; i < headerReg.length; i++) {
    const header = input.match(headerReg[i]);
    if (header) {
      let temp = i + 1;
      status.push("h" + temp);
      input = replaceInput(headerReg[i], input);
    }
  }
  // 处理加粗
  const boldRegexes = [boldRegex, boldAltRegex];
  const matchedBoldRegex = boldRegexes.find((regex) => input.match(regex));
  const bold = matchedBoldRegex ? input.match(matchedBoldRegex) : null;

  if (bold) {
    status.push("bold");
    input = replaceInput(matchedBoldRegex, input); // 确保替换用的是匹配到的正则
  }

  // 处理斜体
  const italicRegexes = [italicRegex, italicAltRegex];
  const matchedItalicRegex = italicRegexes.find((regex) => input.match(regex));
  const italic = matchedItalicRegex ? input.match(matchedItalicRegex) : null;

  if (italic) {
    status.push("italic");
    input = replaceInput(matchedItalicRegex, input);
  }

  // 匹配删除线
  const strikethrough = input.match(strikethroughRegex);
  if (strikethrough) {
    status.push("strikethrough");
    input = replaceInput(strikethroughRegex, input);
  }

  // 匹配图片
  const image = input.match(imageRegex);
  if (image) {
    status.push("image");
    input = imgAndLinkReplace(imageRegex, input);
  }

  // 匹配链接
  const link = input.match(linkRegex);
  if (link) {
    status.push("link");
    input = imgAndLinkReplace(linkRegex, input);
  }

  // 行内代码匹配
  const code = input.match(inlineCodeRegex);
  if (code) {
    status.push("code");
    input = replaceInput(inlineCodeRegex, input);
  }

  // 代码块匹配
  const code_block = input.match(codeBlockRegex);
  if (code_block) {
    status.push("code_block");
    input = replaceInput(codeBlockRegex, input);
  }

  // 换行符匹配
  const lineBreak = input.match(lineBreakRegex);
  if (lineBreak) {
    console.log("匹配到换行符");
    status.push("newline");
  }

  // 有序列表匹配
  const orderLine = input.match(orderedListItemRegex);
  let start: number = 1;
  if (orderLine) {
    status.push("order_list");
    // console.log("匹配到了列表项");
    let getStart = input.split(". "); //将数据分开
    input = replaceInput(orderedListItemRegex, input);
    start = +getStart[0];
  }

  const unorderedListRegexes = [
    unorderedListItemRegex,
    unorderedListItemAltRegex,
    unorderedListItemAlt2Regex,
  ];

  const matchedRegex = unorderedListRegexes.find((regex) => input.match(regex)); // 找到第一个成功匹配的正则
  const unorder = matchedRegex ? input.match(matchedRegex) : null;

  if (unorder) {
    status.push("unorder_list");
    input = replaceInput(matchedRegex, input); // 确保用匹配到的正则替换

    console.log("匹配到无序列表");
    console.log(input);
  }

  content += input;
  // 封装token
  if (status.length > 1) {
    for (let s of status) {
      if (s === "image") {
        let ans = typeImgOrUrl(content);

        token.type = "complex";
        token.src = ans.url;
        token.alt = ans.desc;
        token.children = [...status];
      } else if (s === "link") {
        let ans = typeImgOrUrl(content);

        token.type = "complex";
        token.text = ans.desc;
        token.href = ans.url;
        token.children = [...status];
      } else {
        token.type = "complex";
        token.content = content;
        token.children = [...status];
      }
    }
  } else if (status.length === 1) {
    const style = status[0];
    let headerLevel = typeHeader(style);
    if (headerLevel) {
      let str = style.split("");

      token.type = "header";
      token.level = +str[str.length - 1];
      token.content = content;
    } else {
      // 不是标题则是其他简单样式
      let ans = typeImgOrUrl(content);

      switch (style) {
        case "bold":
          token.type = "bold";
          token.content = content;
          break;
        case "italic":
          token.type = "italic";
          token.content = content;
          break;
        case "strikethrough":
          token.type = "strikethrough";
          token.content = content;
          break;
        case "image":
          // console.log("style是图片");
          token.type = "image";
          token.src = ans.url;
          token.alt = ans.desc;
          break;
        case "link":
          // console.log("style是链接" + style);
          token.type = "link";
          token.text = ans.desc;
          token.href = ans.url;
          break;
        case "order_list":
          token.type = "order_list";
          token.value = content;
          token.start = start;
          break;
        case "unorder_list":
          token.type = "unorder_list";
          token.value = content;
          break;
        default:
          break;
      }
    }
  } else {
    token.type = "text";
    token.value = content;

    console.log(token);
  }
  return token;
}

设计亮点

  • 使用状态数组记录匹配到的所有样式类型
  • 支持多种样式的组合(如加粗链接、斜体加粗等)
  • 通过正则表达式匹配,性能高效

3. 语法分析器 (src/parser.ts)

语法分析器将 Token 流转换为抽象语法树(AST)。

AST 节点类型定义
3:24:src/parser.ts 复制代码
type ASTNode =
  | { type: "document"; children: ASTNode[] }
  | { type: "header"; level: number; content: string }
  | { type: "bold"; content: string }
  | { type: "italic"; content: string }
  | { type: "strikethrough"; content: string }
  | { type: "link"; href: string; text: string }
  | { type: "image"; src: string; alt: string }
  | { type: "code"; content: string }
  | { type: "code_block"; content: string }
  | { type: "blockquote"; children: ASTNode[] }
  | { type: "horizontal_rule" }
  | { type: "text"; value: string }
  | { type: "complex"; content: string; children: String[] } // 修改 complex 类型
  | { type: "newline" }
  | {
      type: "order_list";
      content: String;
      start: number;
      nestedItems?: ASTNode[];
    }
  | { type: "unorder_list"; content: String };
核心解析函数
26:161:src/parser.ts 复制代码
function parser(tokens: Token[]): ASTNode {
  let current = 0;

  function parse(): ASTNode {
    const children: ASTNode[] = [];

    while (current < tokens.length) {
      const token = tokens[current];

      switch (token.type) {
        case "text":
          // 处理 text 类型
          children.push({ type: "text", value: token.value! });
          current++;
          break;

        case "header":
          // 处理 header 类型
          children.push({
            type: "header",
            level: token.level!,
            content: token.content!,
          });
          current++;
          break;

        case "bold":
          // 处理 bold 类型
          children.push({ type: "bold", content: token.content! });
          current++;
          break;

        case "italic":
          // 处理 italic 类型
          children.push({ type: "italic", content: token.content! });
          current++;
          break;

        case "strikethrough":
          // 处理 strikethrough 类型
          children.push({ type: "strikethrough", content: token.content! });
          current++;
          break;

        case "link":
          // 处理 link 类型
          children.push({
            type: "link",
            href: token.href!,
            text: token.text!,
          });
          current++;
          break;

        case "image":
          // 处理 image 类型
          children.push({
            type: "image",
            src: token.src!,
            alt: token.alt!,
          });
          current++;
          break;

        case "code":
          // 处理 inline code 类型
          children.push({ type: "code", content: token.content! });
          current++;
          break;

        case "code_block":
          // 处理 code block 类型
          children.push({ type: "code_block", content: token.content! });
          current++;
          break;

        case "blockquote":
          // 处理 blockquote 类型
          children.push({
            type: "blockquote",
            children: [{ type: "text", value: token.content! }],
          });
          current++;
          break;

        case "newline":
          // 处理 newline 类型
          children.push({ type: "newline" });
          current++;
          break;

        case "complex":
          // 处理 complex 类型,多个样式组合
          children.push({
            type: "complex",
            content: token.content!,
            children: token.children!, // 保存组合的样式信息
          });
          current++;
          break;

        case "order_list":
          children.push({
            type: "order_list",
            content: token.value,
            start: token.start,
            nestedItems: token.listArr
              ? token.listArr.map((item) => ({
                  type: "order_list",
                  content: item.value,
                  start: item.start,
                }))
              : [], // 如果 `listArr` 存在,则递归解析子项,否则为空数组
          });
          current++;
          break;

        case "unorder_list":
          children.push({
            type: "unorder_list",
            content: token.value,
          });
          current++;
          break;
        default:
          current++;
          break;
      }
    }

    // 返回包含所有内容的文档节点
    return { type: "document", children };
  }

  return parse();
}

设计亮点

  • 使用递归下降解析器模式
  • 统一的 AST 节点结构
  • 支持嵌套结构(如列表项)

4. 渲染器 (src/render.ts)

渲染器将 AST 转换为 HTML 字符串。

核心渲染函数
6:60:src/render.ts 复制代码
function render(ast: ASTNode): string {
  switch (ast.type) {
    case "document":
      return ast.children.map((child) => render(child)).join("");

    case "header":
      return `<h${ast.level}>${ast.content}</h${ast.level}>`;

    case "bold":
      return `<strong>${ast.content}</strong>`;

    case "italic":
      return `<em>${ast.content}</em>`;

    case "strikethrough":
      return `<del>${ast.content}</del>`;

    case "text":
      return ast.value;

    case "link":
      return `<a href="${ast.href}" target="_blank" rel="nofollow">${ast.text}</a>`;

    case "image":
      return `<img src="${ast.src}" alt="${ast.alt}">`;

    case "blockquote":
      return `<blockquote>${ast.children
        .map((child) => render(child))
        .join("")}</blockquote>`;

    case "horizontal_rule":
      return `<hr>`;

    case "code":
      return `<code>${ast.content}</code>`;

    case "code_block":
      return `<pre><code>${ast.content}</code></pre>`;

    case "newline":
      // 确保处理换行符
      return `<br>`;

    case "order_list":
      return `<ol start = ${ast.start ?? "1"}><li>${ast.content}</li></ol>`;

    case "unorder_list":
      return `<ul><li>${ast.content}</li></ul>`;
    case "complex":
      return renderComplex(ast.content, ast.children);
    default:
      return "";
  }
}
复杂样式渲染
72:101:src/render.ts 复制代码
function renderComplex(content: string, children: String[]): string {
  if (children.length === 0) {
    return content;
  }

  const style = children[0]; // 取出当前需要应用的样式
  const remainingStyles = children.slice(1); // 剩下的样式继续递归
  let ans = content.split(" ");
  switch (style) {
    case "bold":
      return `<strong>${renderComplex(content, remainingStyles)}</strong>`;
    case "italic":
      return `<em>${renderComplex(content, remainingStyles)}</em>`;
    case "strikethrough":
      return `<del>${renderComplex(content, remainingStyles)}</del>`;
    case "code":
      return `<code>${renderComplex(content, remainingStyles)}</code>`;
    case "link":
      return `<a href="${
        ans[1]
      }" target="_blank" rel="nofollow">${renderComplex(
        ans[0],
        remainingStyles
      )}</a>`;
    case "image":
      return `<img src="${ans[1]}" alt="${ans[0]}">`; // 图片不需要嵌套
    default:
      return renderComplex(content, remainingStyles); // 未知类型,继续递归
  }
}

设计亮点

  • 递归渲染,支持嵌套结构
  • 复杂样式通过递归方式处理,保证正确的 HTML 标签嵌套
  • 链接自动添加 target="_blank"rel="nofollow" 属性

5. 正则表达式定义 (src/common/html_regexp.ts)

所有用于匹配 Markdown 语法的正则表达式都集中定义在这个文件中,便于维护和扩展。

1:84:src/common/html_regexp.ts 复制代码
// 匹配一级标题(#)
// 例如: # 一级标题
export const h1Regex = /^#\s(.*)$/gm;

// 匹配二级标题(##)
// 例如: ## 二级标题
export const h2Regex = /^##\s(.*)$/gm;

// 匹配三级标题(###)
// 例如: ### 三级标题
export const h3Regex = /^###\s(.*)$/gm;

// 匹配四级标题(####)
// 例如: #### 四级标题
export const h4Regex = /^####\s(.*)$/gm;

// 匹配五级标题(#####)
// 例如: ##### 五级标题
export const h5Regex = /^#####\s(.*)$/gm;

// 匹配六级标题(######)
// 例如: ###### 六级标题
export const h6Regex = /^######\s(.*)$/gm;

// 匹配加粗文本(**bold** 或 __bold__)
// 例如: **加粗文本**
export const boldRegex = /\*\*(.*)\*\*/gm;

// 匹配加粗文本(__bold__)替代格式
// 例如: __加粗文本__
export const boldAltRegex = /__(.*)__/gm;

// 匹配斜体文本(*italic* 或 _italic_)
// 例如: *斜体文本* 或 _斜体文本_
export const italicRegex = /\*(.*)\*/gm;

// 匹配斜体文本(_italic_)替代格式
// 例如: _斜体文本_
export const italicAltRegex = /_(.*)_/gm;

// 匹配删除线文本(~~strikethrough~~)
// 例如: ~~删除线文本~~
export const strikethroughRegex = /~~(.*)~~/gm;

// 匹配链接([text](url))
// 例如: [点击这里](https://example.com)
export const linkRegex = /\[(.*?)\]\((.*?)\)/gm;

// 匹配图片(![alt](url))
// 例如: ![图片描述](https://example.com/image.jpg)
export const imageRegex = /!\[(.*?)\]\((.*?)\)/gm;

// 匹配行内代码(`code`)
// 例如: `console.log("Hello, world!");`
export const inlineCodeRegex = /`(.*?)`/gm;

// 匹配代码块(```code block```)
// 例如: ```
//  function hello() {
//      console.log("Hello");
//  }
// ```
export const codeBlockRegex = /```([\s\S]*?)```/gm;

// 匹配换行符(用于处理换行)
// 例如: \n
export const lineBreakRegex = /\n/gm;

// 匹配无序列表项(星号 *)
// 例如: * 列表项
export const unorderedListItemRegex = /^\*\s(.*)$/gm;

// 匹配无序列表项(加号 +)
// 例如: + 列表项
export const unorderedListItemAltRegex = /^\+\s(.*)$/gm;

// 匹配无序列表项(减号 -)
// 例如: - 列表项
export const unorderedListItemAlt2Regex = /^-\s(.*)$/gm;

// 匹配有序列表项(数字加点)
// 例如: 1. 列表项
export const orderedListItemRegex = /^\d+\.\s(.*)$/gm;

设计亮点

  • 集中管理所有正则表达式,便于维护
  • 每个正则都有详细的注释说明
  • 支持多种 Markdown 语法变体(如 **bold**__bold__

📦 安装使用

安装

bash 复制代码
npm install markdown2html-parser

基本使用

ESM 导入
javascript 复制代码
import { convertMarkdownToHtml } from "markdown2html-parser";

const markdown = "# Hello World\n\nThis is **bold** text.";
const html = convertMarkdownToHtml(markdown);
console.log(html);
// 输出: <h1>Hello World</h1><br><br>This is <strong>bold</strong> text.
CommonJS 导入
javascript 复制代码
const { convertMarkdownToHtml } = require("markdown2html-parser");

const markdown = "# Hello World\n\nThis is **bold** text.";
const html = convertMarkdownToHtml(markdown);
console.log(html);
默认导入
javascript 复制代码
import convertMarkdownToHtml from "markdown2html-parser";

const html = convertMarkdownToHtml(markdown);

高级使用

如果需要单独使用词法分析器或语法分析器:

javascript 复制代码
import { lexMarkdown, parseMarkdown, convertMarkdownToHtml } from "markdown2html-parser";

// 只进行词法分析
const tokens = lexMarkdown("# Hello World");

// 进行词法和语法分析
const tokens = lexMarkdown("# Hello World");
const ast = parseMarkdown(tokens);

// 完整转换
const html = convertMarkdownToHtml("# Hello World");

📚 API 文档

convertMarkdownToHtml(input: string): string

将 Markdown 文本转换为 HTML 字符串。

参数

  • input (string): 要转换的 Markdown 文本

返回

  • string: 转换后的 HTML 字符串

示例

javascript 复制代码
const html = convertMarkdownToHtml("# Title\n\n**Bold** text");

lexMarkdown(input: string): Token[]

将 Markdown 文本解析为 Token 数组(词法分析)。

参数

  • input (string): 要解析的 Markdown 文本

返回

  • Token[]: Token 数组

parseMarkdown(tokens: Token[]): ASTNode

将 Token 数组解析为 AST(语法分析)。

参数

  • tokens (Token[]): Token 数组

返回

  • ASTNode: 抽象语法树根节点

📝 支持语法

标题

markdown 复制代码
# H1 标题
## H2 标题
### H3 标题
#### H4 标题
##### H5 标题
###### H6 标题

文本样式

markdown 复制代码
**加粗文本** 或 __加粗文本__
*斜体文本* 或 _斜体文本_
~~删除线文本~~

链接和图片

markdown 复制代码
[链接文本](https://example.com)
![图片描述](https://example.com/image.jpg)

代码

markdown 复制代码
行内代码:`const x = 1;`

代码块:

function hello() {

console.log("Hello");

}

复制代码

列表

markdown 复制代码
有序列表:
1. 第一项
2. 第二项

无序列表(三种方式):
* 项目一
+ 项目二
- 项目三

引用

markdown 复制代码
> 这是一个引用块

分隔线

markdown 复制代码
---

复杂组合

支持多种样式的组合,例如:

markdown 复制代码
**[加粗链接](https://example.com)**
*斜体加粗文本*

🛠️ 技术栈

  • TypeScript: 提供类型安全和更好的开发体验
  • Node.js: 运行环境
  • 正则表达式: 用于 Markdown 语法匹配

📄 许可证

本项目遵循 ISC 许可证。

👤 作者

Horizon

📅 版本历史

v0.0.5 (当前版本)

  • 完整的 Markdown 语法支持
  • 模块化架构设计
  • ESM 和 CommonJS 双模块支持

v0.0.2

  • 新增列表解析功能
  • 优化导入方式,支持多种导入模式

注意:本项目仍在积极开发中,欢迎提交 Issue 和 Pull Request!

相关推荐
涔溪14 小时前
Vue3 的核心语法
前端·vue.js·typescript
心随雨下1 天前
好会计自定义结转如何设置
typescript
lichong9511 天前
harmonyos 大屏设备怎么弹出 u 盘
前端·macos·华为·typescript·android studio·harmonyos·大前端
涔溪1 天前
Vue3 中ref和reactive的核心区别是什么?
前端·vue.js·typescript
by__csdn1 天前
Ajax与Axios终极对比指南全方位对比解析
前端·javascript·ajax·okhttp·typescript·vue·restful
by__csdn1 天前
Vue3+Axios终极封装指南
前端·javascript·vue.js·http·ajax·typescript·vue
不会写DN1 天前
如何实现UniApp登录拦截?
前端·javascript·vue.js·typescript·uni-app·vue
im_AMBER1 天前
Canvas架构手记 08 副作用与同步 | 不可变更新 | 调试与错误边界 | ESLint
前端·笔记·学习·react.js·架构·typescript·前端框架