xDocxDoc
AI
前端
后端
iOS
Android
Flutter
AI
前端
后端
iOS
Android
Flutter
    • Marked是什么
    • 高级用法
    • 扩展
    • 代码示例

扩展

本文翻译自 https://marked.js.org/using-pro,详细讲解了如何扩展 Marked.js 的功能,包含代码注释和实际案例。

扩展原则

为遵循单一职责原则和开闭原则,我们设计了灵活的扩展机制,让用户能够在不修改核心代码的情况下添加自定义功能。

marked.use()

推荐使用 marked.use(extension) 扩展 Marked。参数 extension 可包含任何 Marked 支持的可选配置项:

import { marked } from 'marked';

// 覆盖默认配置
marked.use({
  pedantic: false,  // 关闭严格模式
  gfm: true,        // 启用 GitHub Flavored Markdown
  breaks: false     // 禁用自动换行转换
});

同时支持传入多个配置对象:

marked.use(extension1, extension2, extension3);

合并规则

以下属性会被合并而非覆盖现有配置:

属性描述
renderer渲染器函数
tokenizer分词器函数
hooks生命周期钩子
walkTokens令牌后处理函数
extensions自定义扩展集合

重要提示:扩展应当仅在全局作用域添加一次。在重复调用的函数或 Svelte 等框架组件中添加扩展会导致递归错误。

Marked 处理流程

Markdown → HTML 的转换流程:

输入 → 分词器 → 令牌树 → 后处理 → 解析器 → HTML
  1. 分词器将 Markdown 分割为令牌对象
  2. walkTokens 遍历令牌树执行后处理
  3. 解析器将令牌转换为 HTML

渲染器扩展 (Renderer)

通过提供 renderer 配置覆盖默认渲染逻辑:

// 为标题添加锚点链接(类似 GitHub 风格)
const renderer = {
  heading({ depth, text }) {
    // 生成 URL 安全的锚点 ID
    const anchorId = text.toLowerCase().replace(/[^\w]+/g, '-');
    
    return `
      <h${depth}>
        <a name="${anchorId}" class="anchor" href="#${anchorId}">
          <span class="header-link"></span>
        </a>
        ${text}
      </h${depth}>`;
  }
};

marked.use({ renderer });

支持覆盖的渲染方法

块级渲染方法

space(token: Tokens.Space): string
code(token: Tokens.Code): string
blockquote(token: Tokens.Blockquote): string
html(token: Tokens.HTML | Tokens.Tag): string
heading(token: Tokens.Heading): string
hr(token: Tokens.Hr): string
list(token: Tokens.List): string
listitem(token: Tokens.ListItem): string
checkbox(token: Tokens.Checkbox): string
paragraph(token: Tokens.Paragraph): string
table(token: Tokens.Table): string
tablerow(token: Tokens.TableRow): string
tablecell(token: Tokens.TableCell): string

行内渲染方法

strong(token: Tokens.Strong): string
em(token: Tokens.Em): string
codespan(token: Tokens.Codespan): string
br(token: Tokens.Br): string
del(token: Tokens.Del): string
link(token: Tokens.Link): string
image(token: Tokens.Image): string
text(token: Tokens.Text | Tokens.Escape | Tokens.Tag): string

令牌类型定义详见 https://marked.js.org/using-pro#tokens

分词器扩展 (Tokenizer)

通过 tokenizer 配置自定义分词逻辑:

// 在行内代码中支持 LaTeX 语法
const tokenizer = {
  codespan(src) {
    const match = src.match(/^\$+([^\$\n]+?)\$+/);
    if (match) {
      return {
        type: 'codespan',
        raw: match[0],       // 原始匹配文本
        text: match[1].trim() // 提取的 LaTeX 内容
      };
    }
    return false; // 回退默认处理
  }
};

marked.use({ tokenizer });

支持的分词方法

块级分词方法

space(src: string): Tokens.Space
code(src: string): Tokens.Code
fences(src: string): Tokens.Code
heading(src: string): Tokens.Heading
hr(src: string): Tokens.Hr
blockquote(src: string): Tokens.Blockquote
list(src: string): Tokens.List
html(src: string): Tokens.HTML
def(src: string): Tokens.Def
table(src: string): Tokens.Table
lheading(src: string): Tokens.Heading
paragraph(src: string): Tokens.Paragraph
text(src: string): Tokens.Text

行内分词方法

escape(src: string): Tokens.Escape
tag(src: string): Tokens.Tag
link(src: string): Tokens.Link | Tokens.Image
reflink(src: string, links: object): Tokens.Link | Tokens.Image | Tokens.Text
emStrong(src: string, maskedSrc: string, prevChar: string): Tokens.Em | Tokens.Strong
codespan(src: string): Tokens.Codespan
br(src: string): Tokens.Br
del(src: string): Tokens.Del
autolink(src: string): Tokens.Link
url(src: string): Tokens.Link
inlineText(src: string): Tokens.Text

WalkTokens 后处理

遍历令牌树并修改令牌内容:

// 所有标题级别+1 (h1 → h2)
const walkTokens = (token) => {
  if (token.type === 'heading') {
    token.depth += 1;
  }
};

marked.use({ walkTokens });

钩子函数 (Hooks)

生命周期钩子允许接入处理流程关键点:

钩子描述
preprocess(markdown)Markdown 预处理
postprocess(html)HTML 后处理
processAllTokens(tokens)令牌全局处理
provideLexer()提供自定义分词器
provideParser()提供自定义解析器

示例:使用 Front Matter 设置选项

import fm from 'front-matter';

marked.use({
  hooks: {
    preprocess(markdown) {
      // 解析 YAML front matter
      const { attributes } = fm(markdown);
      
      // 将 front matter 属性合并到配置
      Object.assign(this.options, attributes);
      return markdown;
    }
  }
});

示例:HTML 消毒处理

import DOMPurify from 'isomorphic-dompurify';

marked.use({
  hooks: {
    postprocess(html) {
      return DOMPurify.sanitize(html); // 过滤危险 HTML
    }
  }
});

自定义扩展 (Extensions)

extensions 数组支持添加完整自定义语法:

const descriptionList = {
  name: 'descriptionList',
  level: 'block',  // 块级扩展
  start(src) { return src.match(/:[^:\n]/)?.index },
  tokenizer(src) {
    // 匹配 :: 分隔的描述列表
    const rule = /^(:[^:\n]+:[^:\n]*(?:\n|$))+/;
    const match = rule.exec(src);
    if (match) {
      return {
        type: 'descriptionList',
        raw: match[0],
        tokens: [] // 存放子令牌
      };
    }
  },
  renderer(token) {
    return `<dl>${this.parser.parseInline(token.tokens)}</dl>`;
  }
};

marked.use({ extensions: [descriptionList] });

扩展参数详解

参数必填描述
name✓扩展标识符
level✓block/inline 层级
start()✓检测扩展起始位置
tokenizer()✓生成自定义令牌
renderer()✓令牌渲染逻辑
childTokens需要遍历的子令牌字段

推荐使用 https://github.com/markedjs/marked-extension-template 创建扩展包

异步处理

启用 async: true 后,Marked 将返回 Promise:

// 验证链接是否有效
marked.use({
  async: true,
  walkTokens: async (token) => {
    if (token.type === 'link') {
      try {
        await fetch(token.href);
      } catch {
        token.title = 'invalid'; // 标记失效链接
      }
    }
  }
});

const html = await marked.parse(markdown);

直接访问 Lexer 和 Parser

// 手动执行分词和解析
const tokens = marked.lexer(markdown, options);
const html = marked.parser(tokens, options);

// 查看内置分词规则
console.log(marked.Lexer.rules.block); 
console.log(marked.Lexer.rules.inline);

总结

  1. 扩展方式

    • 使用 marked.use() 进行全局配置
    • 支持覆盖渲染器(renderer)和分词器(tokenizer)
    • 通过 extensions 添加全新语法支持
  2. 处理流程

    • 分层处理(块级/行内)
    • 令牌树机制实现复杂嵌套
    • 钩子函数介入关键生命周期
  3. 最佳实践

    • 扩展应在全局作用域添加
    • 优先使用官方扩展模板
    • 异步操作需启用 async 选项
  4. 高级访问

    • 可直接调用 lexer() 和 parser()
    • 支持查看和修改内置分词规则

通过灵活运用这些扩展机制,开发者可以为 Marked.js 添加各种自定义语法和处理逻辑,满足特定场景的 Markdown 处理需求。

最后更新: 2025/8/26 10:07
Prev
高级用法
Next
代码示例