从 marked.js 旧版本升级到最新版:API 变更与迁移指南
针对 marked.js 从旧版本升级至最新版的完整工程迁移指南。详细梳理包导出、实例隔离、marked.use 扩展流水线、Renderer Token 对象解构及外置安全净化等核心破坏性变更,提供步骤拆解、代码示例与验证清单。
marked.use())与 Token 字典对象解构”的范式转变。由于最新版彻底废弃了 marked() 直接调用、移除了内置的不安全净化配置(sanitize)、重构了 Renderer 的入参协议,并将标题锚点等辅助能力解耦为独立扩展包,直接替换版本号会导致项目直接中断运行。本文梳理核心破坏性变更矩阵,给出五步标准化平滑迁移流水线与完备的代码改造范例。
一、版本演进与核心破坏性变更矩阵
在旧版 marked.js(v0.x 至 v4.x 早期)中,库的设计偏向轻量但紧耦合的全局单例模式。所有扩展往往通过改写全局原型链或调用 marked.setOptions() 注入,这种方式在现代前端模块化构建、同构渲染(SSR)或多实例多规则共存场景下,极易引发不可逆的状态污染与安全漏洞。
最新版 marked.js 彻底完成了底层架构的重写,确立了以抽象语法树(AST)Tokens 为中心、以插件管道为承载的工程规范。在着手升级前,必须准确识别以下四项根本性变更:
| 功能维度 | 旧版用法 (v0.x - v4.x) | 最新版用法 (v12+ / v15+) | 迁移影响与行动项 |
|---|---|---|---|
| 执行入口 | marked(markdown, options) 直接作为函数调用 |
marked.parse(markdown, options) 或 new Marked().parse() |
直接报错 直接调用会抛出 TypeError: marked is not a function,需全局替换调用入口。 |
| 配置管理 | marked.setOptions({...}) 全局生效 |
marked.use({...}) 链式注册或通过 new Marked() 传入 |
行为变更 setOptions 已被废弃,应改用基于扩展包机制的 use()。 |
| Renderer 签名 | 位置参数:heading(text, level, raw, slugger)code(code, infostring, escaped) |
单一 Token 字典解构:heading({ depth, text, tokens, raw })code({ text, lang, escaped }) |
重大破坏 最易踩坑点。入参从多个普通标量变为包含 Token 信息的单一对象,原 level 属性变更为 depth。 |
| XSS 安全防线 | 内置 sanitize: true 及 sanitizer 回调 |
核心库彻底移除净化逻辑,官方强制要求外接 DOMPurify |
安全风险 旧配置项被静默忽略或报错,若不接入 DOMPurify 会导致严重的 XSS 注入隐患。 |
| 标题与锚点 | 内置 headerIds: true、mangle: true |
移出核心包,独立为 marked-gfm-heading-id 与 marked-mangle |
功能缺失 升级后标题默认不再自动挂载 id,需按需引入官方扩展。 |
| 语法高亮 | 在 options 中配置 highlight(code, lang) 回调 |
移出核心包,推荐配合 marked-highlight 插件或在 walkTokens 中处理 |
配置失效 options.highlight 不再被原生支持,需通过插件流水线拦截代码块。 |
二、从旧版到最新版的五步标准迁移流水线
迁移工作应当按照清晰的工程流水线顺序逐步推进。切忌在未经代码重构的情况下盲目升级版本依赖,否则分散的运行时异常将增加排查难度。按照以下五个步骤操作,可确保系统稳步过渡。
步骤一:依赖版本升级与废弃配置清理
首先更新项目中的 marked 包到最新版本。若项目中原先依赖了代码高亮或标题 ID,同时安装官方独立拆分的插件包:
# 1. 升级核心解析库
npm install marked@latest
# 2. 安装外置能力支持插件及安全净化库
npm install marked-gfm-heading-id marked-highlight dompurify
npm install --save-dev @types/dompurify
检查项目中现有的 marked 配置对象,检索并彻底删除以下在最新版中已失效或被禁用的配置属性:
sanitize与sanitizer:已完全移除,保留此项可能触发配置警告或误导开发者以为安全防护依然生效。mangle:邮箱地址转义混淆已移出核心。headerIds与headerPrefix:标题 ID 自动生成已移出核心。baseUrl:相对链接路径重写已移出核心(如需使用请引入marked-base-url)。highlight:代码高亮回调已移出核心。
步骤二:重构模块调用与实例隔离
旧版本代码常常使用 CommonJS 或默认导入直接把 marked 作为可执行函数调用:
// ❌ 旧版写法 (已失效)
const marked = require('marked');
const htmlOutput = marked('# Hello World', { gfm: true });
在最新版中,静态 marked.parse() 是执行解析的标准 API。对于多组件公用或服务端环境,推荐使用 new Marked() 创建独立实例,避免全局配置互相干扰:
// 最新版推荐写法:模块化与实例隔离
import {{ Marked }} from 'marked';
// 创建专属的 Markdown 解析器实例
const customMarked = new Marked();
// 通过实例解析文本
const markdownSource = '# 模块化改造验证
正文内容解析。';
const rawHtml = customMarked.parse(markdownSource);
步骤三:重构自定义 Renderer(重点:Token 对象解构)
heading(text, level, raw, slugger))。在最新版中,Renderer 的所有方法统一只接收一个 Token 对象,旧代码若直接解构第二个参数,level 将必定为 undefined!此外,原 level 属性在 Token 对象中已更名为 depth。
以下是常见渲染方法的重构对照:
// ❌ 旧版 Renderer 编写方式 (已废弃)
const oldRenderer = new marked.Renderer();
oldRenderer.heading = function (text, level, raw) {{
return `<h${{level}} class="doc-heading">${{text}}</h${{level}}>`;
}};
oldRenderer.code = function (code, infostring, escaped) {{
return `<pre class="lang-${{infostring}}"><code>${{code}}</code></pre>`;
}};
oldRenderer.link = function (href, title, text) {{
return `<a href="${{href}}" target="_blank" rel="noopener">${{text}}</a>`;
}};
在最新版中,改用 marked.use({ renderer: {{ ... }} }) 注入,并对入参字典进行解构:
// 最新版 Renderer 编写方式 (Token 对象模式)
import {{ Marked }} from 'marked';
const customMarked = new Marked();
customMarked.use({{
renderer: {{
// 注意:入参为单一 Token 对象,层级字段为 depth 而不是 level
heading({{ tokens, depth, text }}) {{
// 使用 this.parser.parseInline(tokens) 可保留标题内部的行内标记(如粗体、链接)
const parsedText = this.parser.parseInline(tokens);
return `<h${{depth}} class="doc-heading depth-${{depth}}">${{parsedText}}</h${{depth}}>
`;
}},
// 代码块接收 text 与 lang
code({{ text, lang, escaped }}) {{
const validLang = (lang || '').trim();
return `<pre class="code-block" data-lang="${{validLang}}"><code class="language-${{validLang}}">${{text}}</code></pre>
`;
}},
// 链接接收 href, title, tokens / text
link({{ href, title, tokens }}) {{
const label = this.parser.parseInline(tokens);
const titleAttr = title ? ` title="${{title}}"` : '';
return `<a href="${{href}}"${{titleAttr}} target="_blank" rel="noopener noreferrer">${{label}}</a>`;
}}
}}
}});
步骤四:装配官方扩展补齐外围能力
旧版本中 headerIds 自动为标题生成锚点,highlight 自动处理代码着色。最新版通过 marked.use() 插件机制将这些功能解耦接入。
import {{ Marked }} from 'marked';
import {{ gfmHeadingId }} from 'marked-gfm-heading-id';
import {{ markedHighlight }} from 'marked-highlight';
import hljs from 'highlight.js';
const markedInstance = new Marked();
// 1. 挂载标题锚点 ID 生成插件
markedInstance.use(gfmHeadingId({{
prefix: 'section-'
}}));
// 2. 挂载代码高亮扩展插件
markedInstance.use(markedHighlight({{
emptyLangClass: 'hljs',
langPrefix: 'hljs language-',
highlight(code, lang) {{
const language = hljs.getLanguage(lang) ? lang : 'plaintext';
return hljs.highlight(code, {{ language }}).value;
}}
}}));
步骤五:集成 DOMPurify 安全过滤与全量回归
旧版的 sanitize: true 仅对少量已知标签进行了简易字符串转义,极易因复杂的嵌套标签、畸形属性或协议绕过引发 XSS 漏洞,这也是官方决定彻底废弃内置净化的主因。
升级后,必须在数据消费前接入工业级 DOM 树净化库 DOMPurify。在 Node.js 服务端渲染环境下,需结合 jsdom 初始化 DOMPurify:
// 服务端 (Node.js 环境) 安全渲染标准模式
import {{ Marked }} from 'marked';
import DOMPurify from 'dompurify';
import {{ JSDOM }} from 'jsdom';
// 初始化服务端安全 DOM 上下文
const window = new JSDOM('').window;
const purify = DOMPurify(window);
const marked = new Marked();
export function renderMarkdownSafely(markdownSource) {{
// 1. marked 只负责将 Markdown 转换为原始 HTML
const rawHtml = marked.parse(markdownSource);
// 2. DOMPurify 负责阻断一切非法标签与脚本
const safeHtml = purify.sanitize(rawHtml, {{
USE_PROFILES: {{ html: true }},
ADD_ATTR: ['target', 'rel'] // 按需保留合法属性
}});
return safeHtml;
}}
三、现代 marked 扩展管道与安全架构解析
最新版 marked.js 的内部运行机制建立在严格的数据处理流水线之上。了解流水线各阶段的触发顺序,有助于开发者在正确的节点扩展自定义业务逻辑,而不是盲目在全局做字符串替换。
现代扩展管道包含以下核心生命周期:
- Hooks (preprocess):在源码解析前执行。可用于统一处理文本换行、多语言特殊字符转义、或是预先替换特定宏标记。
- Tokenizer / Lexer:词法分析器将连续的 Markdown 文本根据 CommonMark 与 GFM 规范切割为离散的 Token 节点树(如
paragraph、heading、code等)。 - walkTokens:遍历所有 Token 节点的公共拦截点。如果需要异步提取文中所有的代码块进行批量预高亮,或者统一为外部图片 URL 添加鉴权参数,应当在此阶段完成。
- Renderer:将 Token 转换为 HTML 字符串。此阶段仅负责单一类型节点的结构拼装。
- Hooks (postprocess):在整篇文档拼装完成后执行最后的 HTML 调整,例如外层包裹容器。
- DOMPurify 独立安全隔离层:在数据交付到浏览器 DOM 或存储到数据库前执行最终防御,彻底剥离恶意注入。
四、典型迁移场景实战与完整代码示例
为了直观展示升级改造后的代码组织形态,以下给出一个集成了现代 ESM 实例隔离、标题锚点生成、代码高亮以及 DOMPurify 净化防线的工业级完整组件代码:
import {{ Marked }} from 'marked';
import {{ gfmHeadingId }} from 'marked-gfm-heading-id';
import {{ markedHighlight }} from 'marked-highlight';
import hljs from 'highlight.js';
import DOMPurify from 'dompurify';
/**
* 创建标准化 Markdown 渲染服务实例
*/
export function createMarkdownEngine() {{
const engine = new Marked();
// 1. 注册标题锚点扩展
engine.use(gfmHeadingId({{
prefix: 'content-'
}}));
// 2. 注册代码高亮扩展
engine.use(markedHighlight({{
langPrefix: 'hljs language-',
highlight(code, lang) {{
const validLanguage = hljs.getLanguage(lang) ? lang : 'plaintext';
return hljs.highlight(code, {{ language: validLanguage }}).value;
}}
}}));
// 3. 注册自定义 Renderer(解构 Token 对象)
engine.use({{
renderer: {{
// 自定义外部链接默认增加 noopener 与新标签页打开
link({{ href, title, tokens }}) {{
const text = this.parser.parseInline(tokens);
const isExternal = href.startsWith('http://') || href.startsWith('https://');
const targetStr = isExternal ? ' target="_blank" rel="noopener noreferrer"' : '';
const titleStr = title ? ` title="${{title}}"` : '';
return `<a href="${{href}}"${{titleStr}}${{targetStr}}>${{text}}</a>`;
}},
// 自定义图片渲染增加 loading="lazy"
image({{ href, title, text }}) {{
const titleStr = title ? ` title="${{title}}"` : '';
return `<figure class="doc-image"><img src="${{href}}" alt="${{text}}"${{titleStr}} loading="lazy" /><figcaption>${{text}}</figcaption></figure>`;
}}
}}
}});
return {{
render(markdownText) {{
if (typeof markdownText !== 'string') {{
return '';
}}
// 阶段 1:解析 Markdown
const rawHtml = engine.parse(markdownText);
// 阶段 2:安全净化
const cleanHtml = DOMPurify.sanitize(rawHtml, {{
USE_PROFILES: {{ html: true }},
ADD_ATTR: ['target', 'rel', 'loading']
}});
return cleanHtml;
}}
}};
}}
// 验证调用
const parser = createMarkdownEngine();
const result = parser.render('### 示例标题
请访问 [示例网站](https://example.com) 测试。');
console.log(result);
五、常见故障排查、风险防范与迁移验收
升级完成后,应当针对常见的破坏性表现进行定向验证。以下总结了升级过程中最高频出现的故障现象及其成因对策,并提供验收基准。
常见运行时故障排查
-
现象一:抛出
TypeError: marked is not a function
原因:代码中仍保留了marked(text)的旧式调用写法。
修复:将marked(...)统一修改为marked.parse(...),或改用new Marked().parse(...)。 -
现象二:自定义标题样式失效,且标题等级全变成
<undefined>
原因:Renderer 中依然使用heading(text, level)。在最新版中,第一个参数是包含全部属性的 Token 字典对象,第二个参数为空,导致level值为undefined。
修复:将入参改为对象解构heading({ depth, text, tokens }),并将原代码中的level替换为depth。 -
现象三:控制台持续输出
headerIds option is deprecated警告,且页内锚点失效
原因:配置中仍保留了headerIds: true,但该配置在现代核心库中已被放弃,不再负责为<h1>~<h6>生成id属性。
修复:删除该配置项,改用npm install marked-gfm-heading-id并在marked.use(gfmHeadingId())中注册。 -
现象四:渲染出的 HTML 携带
<script>alert(1)</script>且无告警
原因:升级删除了sanitize: true,但未在解析完成后套接DOMPurify。
修复:严格遵循“Markdown 解析后必须调用DOMPurify.sanitize(html)”的安全规范。
系统上线验收清单
- 实例作用域隔离:在多个模块中分别引入并初始化解析器,确认各自扩展互不污染。
- GFM 规范回归测试:验证表格(Tables)、删除线(Strikethrough)、复选任务列表(Task Lists)均按标准解析。
- Renderer 自定义节点输出校验:检查自定义的
heading、code、link、image等标签属性是否与旧版本输出一致。 - 代码高亮挂载测试:使用带语言标识的代码块(如
```typescript)进行渲染,检查 CSS 类名与 DOM 节点是否正常上色。 - XSS 安全测试:输入带有恶意
<img src="x" onerror="alert(1)">与javascript:伪协议的测试用例,验证输出中危险代码已被 100% 剥离。
完成以上步骤后,marked.js 即成功升级至最新稳定架构。项目不仅彻底摆脱了遗留版本的内存污染隐患与安全缺陷,还获得了基于 AST 语法树扩展与 TypeScript 强类型支持带来的长期工程可维护性。
定制 Quill.js 主题样式:修改默认 CSS 与添加自定义类名
2026-11-04 11:21:35
混淆JS代码怎么在线反混淆并格式化阅读
2026-11-03 21:46:39
JS在线编辑器代码报错怎么调试?控制台使用技巧
2026-11-03 21:24:56
JS富文本编辑器Quill与代码编辑器CodeMirror的区别及应用场景
2026-11-03 16:52:09
JavaScript代码编辑器智能提示失效怎么解决?
2026-11-03 16:39:11








