深入 VitePress 源码解析:Markdown 解析
在 VitePress 的内部,Markdown 文件的解析和渲染是通过一系列的插件和工具来完成的。本文将带你深入了解 VitePress 如何利用 markdown-it
和其他插件实现 Markdown 的解析和渲染过程。
1. Markdown 解析核心:createMarkdownRenderer
createMarkdownRenderer
是 VitePress 的 Markdown 渲染器核心,它的职责是创建一个 MarkdownIt
实例,并应用一系列插件来增强 Markdown 的功能。
export const createMarkdownRenderer = async (
srcDir: string,
options: MarkdownOptions = {},
base = '/',
logger: Pick<Logger, 'warn'> = console
): Promise<MarkdownRenderer> => {
const theme = options.theme ?? { light: 'github-light', dark: 'github-dark' }
const codeCopyButtonTitle = options.codeCopyButtonTitle || 'Copy Code'
const hasSingleTheme = typeof theme === 'string' || 'name' in theme
const md = MarkdownIt({
html: true,
linkify: true,
highlight: options.highlight || (await highlight(theme, options, logger)),
...options
})
// 配置链接设置和插件
md.linkify.set({ fuzzyLink: false })
md.use(restoreEntities)
...
}
- 基础配置:
- 创建
MarkdownIt
实例时,启用了html
和linkify
选项,支持 HTML 语法和自动链接。 - 使用
highlight
进行代码高亮处理,支持定制主题。
- 创建
- 插件的应用:
restoreEntities
: 用于还原 HTML 实体。linkify
设置了fuzzyLink: false
,以禁止模糊链接解析。
2. 自定义插件
VitePress 使用了多个自定义插件来扩展 Markdown 的功能,例如支持代码块高亮、行号、图片处理、外部链接处理等。
-
自定义组件插件:
componentPlugin
md.use(componentPlugin, { ...options.component })
这个插件支持在 Markdown 中使用 Vue 组件。
-
代码高亮插件:
highlightLinePlugin
md.use(highlightLinePlugin)
用于代码块的行高亮功能,通常用于演示代码片段时突出显示重要行。
-
预包装插件:
preWrapperPlugin
md.use(preWrapperPlugin, { codeCopyButtonTitle, hasSingleTheme })
为代码块添加额外的包装层,支持代码复制按钮和双主题显示。
-
代码片段插件:
snippetPlugin
md.use(snippetPlugin, srcDir)
用于从文件中插入代码片段的插件。
3. 第三方插件
VitePress 还使用了多种第三方插件,例如 markdown-it-anchor
、markdown-it-attrs
、markdown-it-emoji
等。
-
锚点插件:
anchorPlugin
md.use(anchorPlugin, { slugify, permalink: anchorPlugin.permalink.linkInsideHeader({ ... }) } as anchorPlugin.AnchorOptions)
用于为标题生成锚点链接,使得页面内导航更加便捷。
-
属性插件:
attrsPlugin
if (!options.attrs?.disable) { md.use(attrsPlugin, options.attrs) }
支持在 Markdown 中使用自定义属性。
-
表情符号插件:
emojiPlugin
md.use(emojiPlugin, { ...options.emoji })
使得 Markdown 支持 GitHub 风格的表情符号。
4. VitePress 专属插件
VitePress 定制了多个插件,以提供更丰富的 Markdown 体验。
-
GitHub Alerts 插件:
gitHubAlertsPlugin
if (options.gfmAlerts !== false) { md.use(gitHubAlertsPlugin) }
支持 GitHub 风格的警告框,默认启用。
-
容器插件:
containerPlugin
md.use(containerPlugin, { hasSingleTheme }, options.container)
这个插件允许你创建自定义的 Markdown 容器,例如提示、警告框等。
5. 自定义 Markdown 配置
MarkdownOptions
提供了丰富的选项以供用户自定义 Markdown 行为,包括语法高亮、数学公式支持、Markdown-it 插件配置等。
- 代码高亮主题:
theme
- 自定义语言别名:
languageAlias
- 代码复制按钮文本:
codeCopyButtonTitle
- Markdown-it 插件配置:如
anchor
、attrs
、emoji
等。
总结
通过 createMarkdownRenderer
函数,VitePress 使用 markdown-it
及其插件生态,结合自定义插件,实现了一个强大而灵活的 Markdown 渲染器。这样不仅提升了 Markdown 的解析能力,也使得内容展示更加多样化和用户友好。深入理解这些插件的实现细节,将有助于开发者更好地定制和扩展 VitePress 的功能。
js版本源码
// 引入各种插件和依赖项
const MarkdownIt = require('markdown-it');
const anchorPlugin = require('markdown-it-anchor');
const attrsPlugin = require('markdown-it-attrs');
const { full: emojiPlugin } = require('markdown-it-emoji');
const { slugify } = require('@mdit-vue/shared');
const { componentPlugin } = require('@mdit-vue/plugin-component');
const { frontmatterPlugin } = require('@mdit-vue/plugin-frontmatter');
const { headersPlugin } = require('@mdit-vue/plugin-headers');
const { sfcPlugin } = require('@mdit-vue/plugin-sfc');
const { titlePlugin } = require('@mdit-vue/plugin-title');
const { tocPlugin } = require('@mdit-vue/plugin-toc');
const { containerPlugin } = require('./plugins/containers');
const { gitHubAlertsPlugin } = require('./plugins/githubAlerts');
const { highlight } = require('./plugins/highlight');
const { highlightLinePlugin } = require('./plugins/highlightLines');
const { imagePlugin } = require('./plugins/image');
const { lineNumberPlugin } = require('./plugins/lineNumbers');
const { linkPlugin } = require('./plugins/link');
const { preWrapperPlugin } = require('./plugins/preWrapper');
const { restoreEntities } = require('./plugins/restoreEntities');
const { snippetPlugin } = require('./plugins/snippet');
/**
* @typedef {Object} ThemeOptions
* @property {string|Object} ThemeRegistrationAny 主题名称或注册对象
* @property {string} BuiltinTheme 内置主题
* @property {{ light: string, dark: string }} DualTheme 支持双主题模式
*/
/**
* @typedef {Object} MarkdownOptions
* @property {Function} preConfig - 在应用插件之前设置 `markdown-it` 实例
* @property {Function} config - 设置 `markdown-it` 实例
* @property {boolean} cache - 是否禁用缓存(实验性)
* @property {Object} externalLinks - 外部链接的配置对象
* @property {ThemeOptions} theme - 语法高亮的自定义主题
* @property {Array} languages - 用于语法高亮的语言列表
* @property {Object} languageAlias - 自定义语言别名
* @property {boolean} lineNumbers - 是否在代码块中显示行号
* @property {string} defaultHighlightLang - 代码块默认语言
* @property {Array} codeTransformers - 用于代码块的变换器
* @property {Function} shikiSetup - 设置 Shiki 实例
* @property {string} codeCopyButtonTitle - 代码块中复制按钮的工具提示文本
* @property {Object} anchor - `markdown-it-anchor` 插件的选项
* @property {Object} attrs - `markdown-it-attrs` 插件的选项
* @property {Object} emoji - `markdown-it-emoji` 插件的选项
* @property {Object} frontmatter - `@mdit-vue/plugin-frontmatter` 插件的选项
* @property {Object} headers - `@mdit-vue/plugin-headers` 插件的选项
* @property {Object} sfc - `@mdit-vue/plugin-sfc` 插件的选项
* @property {Object} toc - `@mdit-vue/plugin-toc` 插件的选项
* @property {Object} component - `@mdit-vue/plugin-component` 插件的选项
* @property {Object} container - `markdown-it-container` 插件的选项
* @property {boolean|Object} math - 数学公式支持选项
* @property {Object} image - 图片插件的选项
* @property {boolean} gfmAlerts - 是否启用 GitHub 风格的警告插件
*/
/**
* @typedef {Object} MarkdownRenderer
* @description 定义了 `MarkdownIt` 实例作为 Markdown 渲染器
*/
/**
* 创建一个新的 Markdown 渲染器
* @param {string} srcDir - Markdown 文件的源目录
* @param {MarkdownOptions} [options={}] - Markdown 渲染的选项
* @param {string} [base='/'] - 基础 URL
* @param {Object} [logger=console] - 日志记录器,用于输出警告
* @returns {Promise<MarkdownRenderer>} 返回 `MarkdownIt` 实例作为渲染器
*/
const createMarkdownRenderer = async (srcDir, options = {}, base = '/', logger = console) => {
const theme = options.theme ?? { light: 'github-light', dark: 'github-dark' };
const codeCopyButtonTitle = options.codeCopyButtonTitle || 'Copy Code';
const hasSingleTheme = typeof theme === 'string' || 'name' in theme;
// 创建 MarkdownIt 实例
const md = new MarkdownIt({
html: true,
linkify: true,
highlight: options.highlight || (await highlight(theme, options, logger)),
...options
});
md.linkify.set({ fuzzyLink: false }); // 配置 `linkify` 选项以禁用模糊链接检测
md.use(restoreEntities); // 使用 `restoreEntities` 插件
if (options.preConfig) {
options.preConfig(md); // 在应用插件之前设置 `markdown-it` 实例
}
// 使用自定义插件
md.use(componentPlugin, { ...options.component })
.use(highlightLinePlugin)
.use(preWrapperPlugin, { codeCopyButtonTitle, hasSingleTheme })
.use(snippetPlugin, srcDir)
.use(containerPlugin, { hasSingleTheme }, options.container)
.use(imagePlugin, options.image)
.use(
linkPlugin,
{ target: '_blank', rel: 'noreferrer', ...options.externalLinks },
base
)
.use(lineNumberPlugin, options.lineNumbers);
// 设置表格的 HTML 输出规则,使其具有键盘焦点支持
md.renderer.rules.table_open = function (tokens, idx, options, env, self) {
return '<table tabindex="0">\n';
};
// 启用 GitHub 风格的警告插件(如果选项启用)
if (options.gfmAlerts !== false) {
md.use(gitHubAlertsPlugin);
}
// 使用第三方插件
if (!options.attrs?.disable) {
md.use(attrsPlugin, options.attrs);
}
md.use(emojiPlugin, { ...options.emoji });
// 使用 mdit-vue 插件
md.use(anchorPlugin, {
slugify,
permalink: anchorPlugin.permalink.linkInsideHeader({
symbol: '​',
renderAttrs: (slug, state) => {
const idx = state.tokens.findIndex((token) => {
const attrs = token.attrs;
const id = attrs?.find((attr) => attr[0] === 'id');
return id && slug === id[1];
});
const title = state.tokens[idx + 1].content;
return {
'aria-label': `Permalink to "${title}"`
};
}
}),
...options.anchor
}).use(frontmatterPlugin, { ...options.frontmatter });
// 处理标题插件
if (options.headers) {
md.use(headersPlugin, {
level: [2, 3, 4, 5, 6],
slugify,
...(typeof options.headers === 'boolean' ? undefined : options.headers)
});
}
// 如果提供了配置回调,则调用配置回调
if (options.config) {
options.config(md);
}
return md; // 返回渲染器实例
};
module.exports = {
createMarkdownRenderer
};