Marked 在线 Demo 深度指南:实时预览、HTML 源码、Lexer 数据与 Quick Reference 四视图全解析
2026/9/19 13:02:36 网站建设 项目流程

Marked 在线 Demo 深度指南:实时预览、HTML 源码、Lexer 数据与 Quick Reference 四视图全解析

【免费下载链接】markedA markdown parser and compiler. Built for speed.项目地址: https://gitcode.com/gh_mirrors/ma/marked

Marked 是一个面向速度设计的 Markdown 解析与编译器,本仓库 docs/demo/initial.md 正是官方在线 Demo 的默认起始文档——打开 Demo 页面时,左侧输入框预载的这段文字会立即被解析成右侧的实时预览。本文以这份起始文档为核心,讲解 Demo 的完整交互方式、四种输出视图的含义,并结合 docs/demo/demo.js、docs/demo/worker.js 与 src/marked.ts 等源码,深入剖析「输入即解析」背后的 Worker 渲染管线、Lexer/HTML 两阶段编译原理、可分享的 Permalink 机制以及 Options 配置面板,帮助你把 Demo 当作学习、调试和验证 Markdown 语法的利器。

Marked 是什么:一次输入,实时转换

initial.md开篇即点明 Marked 的使命:把 Markdown 转换成 HTML。Markdown 是一种目标为"极易读写"的纯文本格式,即使不做任何转换,它本身也具备良好的可读性。而这份 Demo 的价值在于——你不再需要"等待"编译过程,在左侧输入的任何内容都会立刻在右侧看到转换结果。

核心使用步骤只有两步:

  1. 在左侧输入框中键入 Markdown 文本;
  2. 在右侧查看实时更新的结果。

正如initial.md所说:"That's it. Pretty simple."(就这样,非常简单)。但简单背后并不简陋:Demo 页面顶部提供了一个下拉菜单,可以在四种视图之间自由切换。

四种视图:从渲染结果到词法数据

initial.md完整列出了 Demo 支持的四种输出视图,这是理解 Demo 功能的关键骨架:

  • Preview(预览):在浏览器中实际渲染出的 HTML 效果,所见即所得。
  • HTML Source(HTML 源码):浏览器"美化"之前的原始 HTML 字符串,适合检查转换产物的细节,比如标签嵌套、属性编码、转义结果。
  • Lexer Data(词法数据):Marked 内部使用的 token 结构,也就是把 Markdown 切分出来的语法单元序列。正如文档原文所说,这是"in case you like gory stuff like this"——给喜欢探究内部机制的人准备的。
  • Quick Reference(快速参考):一份 Markdown 格式化方法的速查手册,即 docs/demo/quickref.md,它本身就是用 Markdown 写成的,你可以把其中的示例复制到左侧输入框逐一验证。

这四种视图与 Demo 的界面实现一一对应。查看 docs/demo/index.html 的右侧面板结构,可以找到四个同名面板元素:#preview(内嵌了一个指向preview.html的 iframe)、#html#lexer#quickref四个 textarea。而 docs/demo/demo.js 中的handleOutputChange()handleChange()负责根据#outputType下拉框的当前值,只显示对应的面板:

function handleChange(panes, visiblePane) { let active = null; for (let i = 0; i < panes.length; i++) { if (panes[i].id === visiblePane) { panes[i].style.display = ''; active = panes[i]; } else { panes[i].style.display = 'none'; } } return active; }

Preview:iframe 沙箱中的渲染结果

Preview 视图并不直接在主文档里渲染 HTML,而是把生成的 HTML 注入一个独立的 iframe(preview.html)。从 docs/demo/preview.html 可以看到,这个 iframe 页面本身近乎空白,其body在加载时为空,等待主页面在收到 Worker 解析结果后通过setParsed()写入:

function setParsed(parsed, lexed) { try { $previewIframe.contentDocument.body.innerHTML = parsed; } catch {} $htmlElem.value = parsed; $lexerElem.value = lexed; }

这段代码同时把同一份解析结果同步写入 Preview、HTML Source 和 Lexer Data 三个面板——所以三种视图的数据始终一致,只是展示角度不同。iframe 带有sandbox="allow-same-origin allow-top-navigation-by-user-activation"限制,起到一定隔离作用,其内部还内置了深色主题适配样式。

Lexer Data:窥探 Marked 的两阶段编译

Lexer Data 视图是理解 Marked 内部原理的最佳窗口。查看 src/marked.ts 的导出,可以看到 Marked 的编译被拆成了两个可独立调用的阶段:

marked.Parser = _Parser; marked.parser = _Parser.parse; marked.Lexer = _Lexer; marked.lexer = _Lexer.lex;

而 docs/demo/worker.js 的 parse 分支正是按这两个阶段串行的:

const lexed = marked.lexer(e.data.markdown, options); const lexedList = jsonString(lexed); const parsed = marked.parser(lexed, options);

也就是说,先由 Lexer 把 Markdown 源码切成 token 序列,再由 Parser 把 token 序列拼装成 HTML。你在 Lexer Data 面板里看到的就是第一阶段的产物。从 src/Lexer.ts 的源码结构看,_Lexer类内部维护了一个tokens数组和inlineQueue队列:lex()先做预处理(把回车统一为\n),随后执行blockTokens()切分块级 token,再通过inlineTokens()处理行内 token,最终返回带links属性的 token 列表。Lexer 还会根据选项在normalgfmpedanticbreaks几组规则之间切换(见 src/Lexer.ts),这正是不同选项组合产生不同 token 结果的原因。

值得注意的细节是,worker.js 在处理marked 0.0.1这个远古版本时,由于当时 lexer 的第二个参数是 tokens 数组而非 options,专门做了兼容分支。这也说明 Demo 的 Lexer 数据视图横跨了 Marked 的多个历史版本。

Quick Reference:可直接复制的语法速查

Quick Reference 视图的内容来自 docs/demo/quickref.md,它在页面初始化时通过setInitialQuickref()fetch#quickref面板。这份速查覆盖了 Markdown 的核心语法:

  • 斜体(*stars*/_underscores_)、粗体(**double stars**)、粗斜体(***three together***);
  • 段落、多层级 blockquote(>与嵌套>>);
  • 行内代码(反引号)与缩进代码块(Tab 或 4 个空格起头,块内 Markdown 与 HTML 均保持字面文本);
  • 标题的两种写法:Setext 式(下方===---)与 Atx 式(#前缀,结尾的#会被忽略),######是最大层级;
  • 链接的三种形式:裸 URL、内联式text、引用式[text][ref]与快捷引用式[text],且引用名大小写不敏感;标题属性可加在链接后(如[inline link](https://example.com "title"));
  • 邮件地址的自动链接:裸文本test@example.com不会被链接,尖括号包裹<test@example.com>才会被链接并做混淆处理;
  • 无序列表(*-+三种符号)与有序列表(起始数字无关紧要,HTML 会自动续排);列表内可嵌套列表、段落、blockquote 与缩进代码块;
  • 水平线(一行内至少 3 个-*_,字符间可带空格);注意 3 个连字符紧跟文字会产生 Setext 标题,需用空行隔开;
  • 图片:与链接语法一致但前面加!,同样支持引用式与标题;
  • 行内 HTML:行内级 HTML 内仍可使用 Markdown(如<u>可以 *继续用* Markdown</u>),块级 HTML 需与正文空行分隔、前后不能有缩进,且大多数解析器在块级 HTML 内部不再处理 Markdown。

这份速查的价值在于:它同时是文档、又是可运行的示例,复制到左侧输入框即可观察每种语法的真实输出。

实时渲染的幕后:Web Worker 管线

Demo 的"实时"体验并非在主线程上边打字边解析,而是交给了一个 Web Worker。这是 docs/demo/demo.js 中messageWorker()的核心逻辑:每当输入发生变化,主线程先把任务postMessageworker.js创建的 Worker,Worker 内部完成 lexer + parser 之后再把结果回传,从而避免长时间解析阻塞 UI 渲染。

demo.js 中有几个值得注意的性能与交互设计:

  • 输入去抖checkForChanges()会以 10~100ms 的间隔轮询输入是否变化,只有lastInput(由版本 + markdown + options 三者拼接的哈希)真正变化时才发起解析请求;
  • Worker 超时保护workerTimeout()每秒检查一次,如果解析超过 1 秒未返回,就会在三个面板中显示超时错误提示("Marked has taken longer than N seconds to respond..."),防止因输入了引发正则性能问题的文本(如仓库test/specs/redos/目录下那些刻意构造的对抗性用例)而无限等待;
  • 响应时间展示:界面上的 Response Time 数值由 Worker 回传的timeendTime - startTime)驱动,setResponseTime()会把毫秒自动格式化为 ms / s / m 甚至 "Too Long",用于直观评估不同输入与不同版本下的解析耗时;
  • 错误降级:若 Worker 抛出异常,onerror会把错误信息写入三个输出面板并标红,便于定位语法问题。

Worker 端的版本加载机制同样值得一提。docs/demo/worker.js 的loadVersion()会按顺序尝试importESM 构建、marked.min.js、UMD 构建与lib/marked.js,并把加载到的marked缓存进versionCache。页面顶部的版本下拉框(docs/demo/index.html 中的#markedVersion)会通过 jsDelivr 的 API 动态拉取marked包的全部已发布版本,这意味着你可以在同一个 Demo 里对比任意两个历史版本对同一段 Markdown 的解析差异——这对排查"升级后行为变化"类问题非常实用。

Options 面板:把默认配置可视化

Demo 左侧除了 Markdown 输入框,还有一个Options面板(#options),用来以 JSON 形式注入marked的配置选项。输入类型通过#inputType下拉框在MarkdownOptions之间切换;切换后会调用setDefaultOptions()向 Worker 请求当前版本的默认配置并回填。

Worker 端 docs/demo/worker.js 的getDefaults()优先调用marked.getDefaults()(若存在),并显式剔除renderer等不可 JSON 序列化的字段;mergeOptions()则把用户输入的选项与默认值合并,同时过滤掉renderertokenizerwalkTokensextensionshighlightsanitizer这类函数型/扩展型选项——它们无法通过 JSON 在 Worker 与主线程之间传递。

以当前仓库的 src/defaults.ts 为例,_getDefaults()返回的默认选项集是:

{ async: false, breaks: false, extensions: null, gfm: true, hooks: null, pedantic: false, renderer: null, silent: false, tokenizer: null, walkTokens: null, }

在 Options 面板中尝试修改这些值,可以直观地看到它们如何影响输出:

  • gfm: true(默认开启):启用 GitHub Flavored Markdown 的表格、删除线、任务列表等扩展语法;改为false后行为回退到接近标准 Markdown;
  • breaks: true:把 Markdown 源码中的换行直接渲染为<br>,与 GFM 默认的"软换行"行为不同;
  • pedantic: true:切换到pedantic规则集(对应 src/Lexer.ts 中的block.pedantic/inline.pedantic),模拟老式 Markdown 的行为,例如 Tab 会被展开为 4 空格;
  • silent: true:抑制解析错误输出。

交互细节:Permalink、Clear 与主题切换

Permalink:分享当前实验状态

initial.md末尾提到可以用clear everything一键清空输入。与之配套的还有Permalink链接:docs/demo/demo.js 的updateLink()会把当前 Markdown 文本、Options JSON 和所选版本编码进 URL 查询参数:

$permalinkElem.href = '?' + outputType + 'text=' + encodeURIComponent($markdownElem.value) + '&options=' + encodeURIComponent($optionsElem.value) + '&version=' + encodeURIComponent($markedVerElem.value);

并通过history.replaceState同步地址栏。反过来,页面初始化时setInitialText()会读取 URL 中的textoptionsoutputTypeversion参数并恢复对应状态;fetch('./initial.md')只有在 URL 未带text参数时才加载默认起始文档。这意味着:

  • 你可以把一个正在排查的案例连同配置打包成一个 URL 分享给协作者,对方打开即是同样的输入与视图;
  • 使用/demo/?text=即可获得一个清空输入的干净起始状态。

Clear 按钮与主题切换

输入区上方的Clear按钮(docs/demo/index.html 中的#clear)执行handleClearClick():清空 Markdown 输入、把版本重置为最新版,并重新拉取默认 Options。主题切换按钮则支持 System / Light / Dark 三态循环,偏好写入localStorage(键名theme-preference,并兼容旧键theme),深色模式下预览 iframe 内部也会同步切换,保证渲染效果所见一致。

为什么用 Markdown:可读性优先的设计哲学

initial.md用一个"为什么选择 Markdown"的章节收尾,引用了 Markdown 创始人对格式语法设计目标的阐述:Markdown 格式化的首要设计目标是尽可能可读,一份 Markdown 文档应当能原样以纯文本形式发布,而不像被打上了标签或格式化指令的标记

这段话正是 Marked 设计取向的注脚:解析器追求速度(项目描述即 "A markdown parser and compiler. Built for speed."),而 Markdown 本身追求的是"即使不转换也易读"。用 Demo 做个小实验即可体会:把左侧输入换成纯文本段落、无序列表和引用块,在 Preview 与 HTML Source 两种视图之间切换,你会同时看到"作为纯文本的可读性"和"作为 HTML 的结构化产物"两个侧面。仓库测试目录test/specs/下数百个 markdown/html 成对用例(commonmark、gfm、new、original 等子目录)也印证了这一点:Markdown 的每一次"美化"都有明确、可验证的预期输出。

小结

initial.md虽然篇幅简短,却是 Marked 在线 Demo 的使用说明书,浓缩了该工具的全部核心交互:左输入、右预览,四种视图覆盖"渲染效果、HTML 产物、内部 token、语法速查"四个观察层次。结合 docs/demo/demo.js 的 Worker 渲染管线、docs/demo/worker.js 的版本加载与 lexer/parser 两阶段调用、src/defaults.ts 的可视化默认选项,以及 Permalink 的分享机制,你可以把 Demo 从一个演示页升级为学习 Markdown 语法、调试解析差异、验证配置行为的工作台——所有实验都可以通过 URL 存档、跨版本复现。

【免费下载链接】markedA markdown parser and compiler. Built for speed.项目地址: https://gitcode.com/gh_mirrors/ma/marked

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询