markdown-it、remarkable 和 showdown 都是用于将 Markdown 文本转换为 HTML 的 JavaScript 库,适用于在浏览器或 Node.js 环境中渲染用户输入的 Markdown 内容。它们都支持标准 CommonMark 语法,并提供扩展机制以支持自定义功能(如任务列表、脚注、高亮等)。这些库广泛用于博客系统、文档站点、富文本编辑器预览和内容管理系统中,核心目标是在保证安全性和性能的前提下,提供灵活、可配置的 Markdown 渲染能力。
在前端开发中,将用户输入的 Markdown 安全高效地渲染为 HTML 是常见需求。markdown-it、remarkable 和 showdown 是三个主流选择,但它们在架构、扩展性和维护状态上差异显著。本文从工程实践角度,对比三者的核心能力。
markdown-it 采用基于插件的模块化架构,所有核心功能(包括链接、图片)都可通过插件启用或替换。它暴露完整的解析器(parser)、渲染器(renderer)和规则链(rules),允许深度干预解析过程。
// markdown-it: 插件扩展
import MarkdownIt from 'markdown-it';
import markdownItTaskLists from 'markdown-it-task-lists';
const md = new MarkdownIt()
.use(markdownItTaskLists); // 启用任务列表插件
const html = md.render('- [x] done');
remarkable 提供有限的内置扩展(如 breaks、linkify),但不支持第三方插件。其扩展需通过直接修改内部规则实现,灵活性较低。
// remarkable: 内置选项配置
import Remarkable from 'remarkable';
const md = new Remarkable({
breaks: true, // 转换 \n 为 <br>
linkify: true // 自动识别 URL
});
const html = md.render('Visit https://example.com');
showdown 使用选项(options)和扩展(extensions)两种方式。扩展需通过全局注册或实例方法添加,但 API 较为冗长。
// showdown: 扩展注册
import showdown from 'showdown';
showdown.extension('myext', function() {
return [
{
type: 'output',
filter: function(text) {
return text.replace(/@\w+/g, '<code>$&</code>');
}
}
];
});
const converter = new showdown.Converter({ extensions: ['myext'] });
const html = converter.makeHtml('Mention @user');
💡 关键区别:
markdown-it的插件机制最灵活,适合复杂定制;showdown的扩展需手动编写过滤器;remarkable几乎无法扩展新语法。
三者默认均不自动转义 HTML 标签,这意味着如果用户输入包含 <script>,会直接输出到 HTML 中,造成 XSS 风险。开发者必须显式启用转义或使用 sanitize 库。
markdown-it 推荐通过 html: false 禁用原生 HTML,并配合 markdown-it-sanitizer 等插件处理。
// markdown-it: 禁用 HTML + 安全插件
const md = new MarkdownIt({ html: false })
.use(require('markdown-it-sanitizer'));
remarkable 同样需设置 html: false,但无官方安全插件。
// remarkable: 禁用 HTML
const md = new Remarkable({ html: false });
showdown 通过 noHeaderId、ghCompatibleHeaderId 等选项间接提升安全,但核心仍需依赖 sanitize-html 等外部库。
// showdown: 配合 sanitize-html
import sanitizeHtml from 'sanitize-html';
const dirty = converter.makeHtml(userInput);
const clean = sanitizeHtml(dirty);
⚠️ 重要提醒:无论选择哪个库,都必须额外处理 XSS 防护。不要依赖解析器默认行为。
markdown-it 严格遵循 CommonMark 0.30 规范,并通过官方测试套件验证。对 GFM(GitHub Flavored Markdown)的支持通过插件(如 markdown-it-gfm)实现。remarkable 基于早期 CommonMark 草案,部分边缘情况(如嵌套列表、引用块)与标准存在差异。showdown 兼容 CommonMark,但默认行为更接近传统 Markdown(如不支持表格),需开启 tables: true 等选项。// 表格支持对比
const tableMd = '| a | b |\n|---|---|\n| 1 | 2 |';
// markdown-it: 需插件
md.use(require('markdown-it-tables'));
// showdown: 需选项
new showdown.Converter({ tables: true });
// remarkable: 不支持表格(需手动 hack)
remarkable:已停止维护。其 GitHub 仓库 README 明确写道:“此项目不再积极开发。推荐使用 markdown-it。” 新项目应避免选用。markdown-it 和 showdown 均处于活跃维护状态,定期发布更新并修复安全问题。markdown-itmarkdown-it-highlightjs、markdown-it-container)可快速实现所需功能,且 AST 支持便于静态分析。// markdown-it 示例:自定义容器
md.use(require('markdown-it-container'), 'tip', {
validate: params => params.trim().match(/^tip\s+(.*)$/),
render: (tokens, idx) => {
if (tokens[idx].nesting === 1) {
return '<div class="tip">';
} else {
return '</div>';
}
}
});
showdownsanitize-html 即可满足安全需求。// showdown 示例:基础配置
const converter = new showdown.Converter({
simpleLineBreaks: true,
strikethrough: true
});
remarkable 集成,功能稳定无扩展需求。markdown-it。| 特性 | markdown-it | remarkable | showdown |
|---|---|---|---|
| 扩展机制 | 插件系统(高度灵活) | 内置选项(不可扩展) | 扩展 + 选项 |
| CommonMark 兼容 | ✅ 严格遵循 | ⚠️ 部分兼容 | ✅(需配置) |
| 维护状态 | ✅ 活跃 | ❌ 已停止 | ✅ 活跃 |
| XSS 防护 | 需手动配置 + 插件 | 需手动配置 | 需手动配置 + 外部库 |
| 适用场景 | 复杂定制、文档平台 | 遗留系统 | 快速集成、简单需求 |
markdown-it:它的插件生态和标准兼容性为未来扩展留足空间。remarkable:除非维护旧代码,否则不要引入已废弃的依赖。showdown 作为轻量备选:当项目简单、团队熟悉其 API 时可考虑,但需自行处理安全和高级功能。无论选择哪个库,请始终记住:Markdown 解析只是第一步,安全渲染才是关键。
选择 markdown-it 如果你需要高度可扩展的解析器,支持插件生态、细粒度的 AST 操作,以及对 CommonMark 标准的严格遵循。它适合构建需要深度定制(如自定义语法、安全过滤、语法高亮集成)的复杂应用,例如文档平台或开发者工具。
选择 showdown 如果你需要一个简单易用、开箱即用的解析器,且偏好基于选项(options)而非插件的配置方式。它适合快速集成到中小型项目中(如评论系统、静态站点生成器),尤其当你不需要复杂的 AST 操作或高性能要求时。
选择 remarkable 如果你追求极致的解析速度和简洁的 API,且不需要频繁扩展功能。但需注意:该库已不再积极维护,官方 GitHub 仓库明确建议新项目使用 markdown-it。因此,仅在遗留系统维护或对性能有极端要求且功能固定的场景下考虑。
Markdown parser done right. Fast and easy to extend.
Table of content
node.js:
npm install markdown-it
browser (CDN):
See also:
// node.js
// can use `require('markdown-it')` for CJS
import markdownit from 'markdown-it'
const md = markdownit()
const result = md.render('# markdown-it rulezz!');
// browser with UMD build, added to "window" on script load
// Note, there is no dash in "markdownit".
const md = window.markdownit();
const result = md.render('# markdown-it rulezz!');
Single line rendering, without paragraph wrap:
import markdownit from 'markdown-it'
const md = markdownit()
const result = md.renderInline('__markdown-it__ rulezz!');
(*) presets define combinations of active rules and options. Can be
"commonmark", "zero" or "default" (if skipped). See
API docs for more details.
import markdownit from 'markdown-it'
// commonmark mode
const md = markdownit('commonmark')
// default mode
const md = markdownit()
// enable everything
const md = markdownit({
html: true,
linkify: true,
typographer: true
})
// full options list (defaults)
const md = markdownit({
// Enable HTML tags in source
html: false,
// Use '/' to close single tags (<br />).
// This is only for full CommonMark compatibility.
xhtmlOut: false,
// Convert '\n' in paragraphs into <br>
breaks: false,
// CSS language prefix for fenced blocks. Can be
// useful for external highlighters.
langPrefix: 'language-',
// Autoconvert URL-like text to links
linkify: false,
// Enable some language-neutral replacement + quotes beautification
// For the full list of replacements, see https://github.com/markdown-it/markdown-it/blob/master/lib/rules_core/replacements.mjs
typographer: false,
// Double + single quotes replacement pairs, when typographer enabled,
// and smartquotes on. Could be either a String or an Array.
//
// For example, you can use '«»„“' for Russian, '„“‚‘' for German,
// and ['«\xA0', '\xA0»', '‹\xA0', '\xA0›'] for French (including nbsp).
quotes: '“”‘’',
// Highlighter function. Should return escaped HTML,
// or '' if the source string is not changed and should be escaped externally.
// If result starts with <pre... internal wrapper is skipped.
highlight: function (/*str, lang*/) { return ''; }
});
import markdownit from 'markdown-it'
const md = markdownit
.use(plugin1)
.use(plugin2, opts, ...)
.use(plugin3);
Apply syntax highlighting to fenced code blocks with the highlight option:
import markdownit from 'markdown-it'
import hljs from 'highlight.js' // https://highlightjs.org
// Actual default values
const md = markdownit({
highlight: function (str, lang) {
if (lang && hljs.getLanguage(lang)) {
try {
return hljs.highlight(str, { language: lang }).value;
} catch (__) {}
}
return ''; // use external default escaping
}
});
Or with full wrapper override (if you need assign class to <pre> or <code>):
import markdownit from 'markdown-it'
import hljs from 'highlight.js' // https://highlightjs.org
// Actual default values
const md = markdownit({
highlight: function (str, lang) {
if (lang && hljs.getLanguage(lang)) {
try {
return '<pre><code class="hljs">' +
hljs.highlight(str, { language: lang, ignoreIllegals: true }).value +
'</code></pre>';
} catch (__) {}
}
return '<pre><code class="hljs">' + md.utils.escapeHtml(str) + '</code></pre>';
}
});
linkify: true uses linkify-it. To
configure linkify-it, access the linkify instance through md.linkify:
md.linkify.set({ fuzzyEmail: false }); // disables converting email to link
If you are going to write plugins, please take a look at Development info.
Embedded (enabled by default):
Via plugins:
By default all rules are enabled, but can be restricted by options. On plugin load all its rules are enabled automatically.
import markdownit from 'markdown-it'
// Activate/deactivate rules, with currying
const md = markdownit()
.disable(['link', 'image'])
.enable(['link'])
.enable('image');
// Enable everything
const md = markdownit({
html: true,
linkify: true,
typographer: true,
});
You can find all rules in sources:
Here is the result of readme parse at MB Pro Retina 2013 (2.4 GHz):
npm run benchmark-deps
benchmark/benchmark.mjs readme
Selected samples: (1 of 28)
> README
Sample: README.md (7774 bytes)
> commonmark-reference x 1,222 ops/sec ±0.96% (97 runs sampled)
> current x 743 ops/sec ±0.84% (97 runs sampled)
> current-commonmark x 1,568 ops/sec ±0.84% (98 runs sampled)
> marked x 1,587 ops/sec ±4.31% (93 runs sampled)
Note. CommonMark version runs with simplified link normalizers for more "honest" compare. Difference is ≈1.5×.
As you can see, markdown-it doesn't pay with speed for its flexibility.
Slowdown of "full" version caused by additional features not available in
other implementations.
Available as part of the Tidelift Subscription.
The maintainers of markdown-it and thousands of other packages are working with Tidelift to deliver commercial support and maintenance for the open source dependencies you use to build your applications. Save time, reduce risk, and improve code health, while paying the maintainers of the exact dependencies you use. Learn more.
markdown-it is the result of the decision of the authors who contributed to 99% of the Remarkable code to move to a project with the same authorship but new leadership (Vitaly and Alex). It's not a fork.
Big thanks to John MacFarlane for his work on the CommonMark spec and reference implementations. His work saved us a lot of time during this project's development.
Related Links:
Ports