Markdown 渲染

Edge Markdown

Edge Markdown 构建于 Remark 和 MDC 之上,用于将 Markdown 文档转换为 HTML。

MDC(Markdown Components) 是一种扩展的 Markdown 格式,允许你直接在 Markdown 中使用组件。这让你可以轻松地将散文与交互式元素组合在同一文档中。

以下是 Edge Markdown 的一些关键特性。

  • GitHub Flavored Markdown (GFM):支持 GFM 特性,例如表格、任务列表、自动链接和删除线。

  • 嵌入 Edge 组件:使用 MDC 语法在 Markdown 文件中内联使用 Edge 组件。

  • 增强的代码块:代码示例使用 Shiki 渲染,提供语法高亮、代码块标题、行高亮以及内联 diff。

  • 自定义散文渲染:标准的 Markdown 元素(图片、标题、列表、段落等)可以使用你自己的自定义组件来渲染。

  • AST 检查工具:由于 Markdown 会被编译为 AST,你可以分析节点以检测失效链接、校验引用,或重写图片源。

安装

从 npm 包仓库安装 edge-markdown 插件。

npm i edge-markdown

接下来是将该插件注册到 Edge。

import edge from 'edge.js'
import { edgeMarkdown } from 'edge-markdown'
edge.use(edgeMarkdown())

基本用法

配置好插件后,你可以使用 @markdown 标签渲染 Markdown 内容和文件。你可以指定 markdown 文件的绝对路径,或者使用 content 属性指定原始内容。

@markdown({
file: absolutePathToMdFile,
})
@markdown({
content: contentAsString,
})

@markdown 标签会将生成的 HTML 写入输出。如果你想访问额外的属性,例如文档的 frontmatter 和目录(table of contents),请考虑使用 $markdown.render 方法。

@let(doc = await $markdown.render({
file: absolutePathToMdFile,
}))
<h1>{{{ doc.frontmatter.title }}}</h1>
<div>{{{ doc.content }}}</div>
<div>{{{ doc.toc }}}</div>

配置选项

edgeMarkdown 函数接受多种配置选项,用于自定义 Markdown 处理行为。你可以控制组件加载、语法高亮、HTML 处理、目录生成以及插件集成。

edge.use(edgeMarkdown({
prefix: 'markdown',
highlight: true,
allowHTML: true,
toc: {
enabled: true,
maxDepth: 2,
},
remarkPlugins: [],
rehypePlugins: [],
components: {},
allowedTags: [],
hooks: [
(node) => {},
]
}))
prefix

prefix 选项定义了用于在 components 目录中查找自定义组件的前缀。例如,默认情况下 note 组件会从 components/markdown/note.edge 文件加载。

highlight

启用/禁用默认的 Shiki 配置,用于高亮代码块。如果你决定禁用它,则需要使用自定义的 rehype 插件来处理代码块。

allowHTML

启用/禁用在 Markdown 中使用 HTML 标签。当你渲染用户编写的内容时,应将其禁用。

toc

启用/禁用目录的创建。toc 选项接受与 mdast-util-toc 包相同的选项集合。

allowedTags

你可以指定一个要渲染的 HTML 标签数组,一旦定义,所有其他标签都将被跳过。如果你要将 Markdown 内容转换为邮件,并希望限制为特定的 HTML 标签,此设置会很有用。

remarkPlugins

注册一组 remark 插件。

rehypePlugins

注册一组 rehype 插件。

hooks

hooks 属性是一个函数数组,会对每个 AST 节点执行。AST 节点将来自 HAST 语法树的 Element、Text 或 Comment。

生成 TOC

目录的 HTML 可以通过 $markdown.render 方法的返回值访问。

默认情况下,使用 level 2 及以下的标题来生成目录。不过,你可以通过 toc 配置选项来配置目录生成设置。

@let(doc = await $markdown.render({
file: absolutePathToMdFile,
toc: {
maxDepth: 2
}
}))
<div>
{{{ doc.content }}}
</div>
<div>
{{{ doc.toc }}}
</div>

配置 Shiki

默认情况下,Edge Markdown 使用 Shiki 进行语法高亮,并带有合理的默认值。你可以通过传入自定义主题和语言来自定义 Shiki 配置。

import { edgeMarkdown } from 'edge-markdown'
edge.use(edgeMarkdown({
shiki: {
theme: 'github-dark',
langs: ['javascript', 'typescript', 'json']
}
}))

你也可以完全禁用 Shiki,并使用自己的 rehype 插件来进行代码高亮。

edge.use(edgeMarkdown({
highlight: false,
rehypePlugins: [yourCustomCodePlugin]
}))

已启用的转换器

默认情况下启用了来自 Shiki 的以下 transformers。

Transformer用途
transformerNotationDiff使用 [!code --] 和 [!code ++] 内联注释显示 diff 标记。
transformerNotationHighlight使用 [!code highlight] 内联注释高亮当前行。
transformerNotationWordHighlight在该词所在行之前使用 [!code word:Hello] 注释高亮给定的词。

此外,代码块反引号中的元数据可通过 pre 标签的 node.properties 属性获取。

```ts title=start/routes.ts
console.log('Highlighted') // [!code highlight]
console.log('Not highlighted')
console.log('hewwo') // [!code --]
console.log('hello') // [!code ++]
// [!code word:Hello]
const message = 'Hello World'
```

显示错误与警告

Markdown 渲染过程中产生的错误和警告,可以通过 $markdown.render 方法返回的 messages 属性访问。

这些可能包括通过 remark 和 rehype 插件报告的默认和自定义错误消息。每条消息都是 VFileMessage 类的一个实例。

@let(doc = await $markdown.render({
file: absolutePathToMdFile
}))
@each(message in doc.messages)
{{ message.line }}:{{ message.column }} {{ message.reason }}
@end

解析 front-matter

所有 Markdown 文件的 front-matter 都会被自动提取,你可以通过 $markdown.render 方法返回的 frontmatter 属性访问它。

@let(doc = await $markdown.render({
file: absolutePathToMdFile
}))
{{ doc.frontmatter.title }}
{{ doc.frontmatter.summary }}

如果你只需要 frontmatter 而不渲染 Markdown 内容,请使用 $markdown.frontmatter 方法。这是一个轻量操作,只解析 YAML frontmatter 并跳过整个 remark/rehype 管道。

@let(meta = await $markdown.frontmatter({
file: absolutePathToMdFile
}))
{{ meta.title }}
{{ meta.summary }}

$markdown.frontmatter 方法接受 file 或 content 属性,就像 $markdown.render 一样。

@let(meta = await $markdown.frontmatter({
content: rawMarkdownString
}))

渲染预览

你可以使用 $markdown.preview 方法渲染 Markdown 文档的预览。该方法只渲染第一个 h2 标题之前的内容,这对于在列表页生成文章摘要或内容预览很有用。

@let(doc = await $markdown.preview({
file: absolutePathToMdFile
}))
<article>
<div>{{{ doc.content }}}</div>
</article>

$markdown.preview 方法接受与 $markdown.render 相同的选项,并返回 content、frontmatter 和 messages 属性。 如果文档没有 h2 标题,则会渲染完整内容。

编写组件

你可以创建可复用的组件,通过交互式元素和自定义样式来增强 Markdown 内容。Edge Markdown 使用 MDC(Markdown Components)语法,将 Edge 组件直接嵌入 Markdown 文件中。

组件会自动从 components/[prefix] 目录加载,其中 prefix 默认为 markdown。这让你可以将 Markdown 专用的组件与常规 Edge 组件分开组织。

另请参阅:MDC 组件语法参考

在 components/markdown/alert.edge 创建一个组件文件:

components/markdown/alert.edge
<div class="alert alert-{{ type ?? 'note' }}">
<h3>{{ title }}</h3>
@markdownSlot()
</div>

然后在你的 Markdown 文件中使用它:

:alert{title="Important" type="warning"}
This is a warning alert with **markdown content** inside.
:
:alert{title="Tip"}
You can also use components with slots.
:

使用插槽

MDC 允许在 Markdown 组件中使用插槽。插槽通过 # 标签后跟插槽名来定义,你可以使用 @markdownSlot 标签渲染其内容。

:hero
Default slot text
#description
This will be rendered inside the `description` slot.
:
components/markdown/hero.edge
<div>
<h1>
@markdownSlot()
</h1>
<div>
@markdownSlot('description')
</div>
</div>

将散文元素渲染为组件

你可以创建具有特定名称的组件,来覆盖标准 Markdown 元素的渲染方式。这让你可以自定义标题、段落、图片以及其他元素的输出。

在下面的示例中,我们在 components/markdown 目录中创建自定义组件,用于渲染 img 和 h2 标签。

components/markdown/img.edge
<figure>
<img src="{{ src }}" alt="{{ alt }}" />
@if(title)
<figcaption>{{ title }}</figcaption>
@end
</figure>
components/markdown/h2.edge
<h2 id="{{ id }}" class="section-heading">
@markdownSlot()
</h2>