Markdown 渲染

Edge Markdown

Edge Markdown 构建于 RemarkMDC 之上,用于将 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 语法树的 ElementTextComment

生成 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 方法接受 filecontent 属性,就像 $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 相同的选项,并返回 contentfrontmattermessages 属性。 如果文档没有 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 目录中创建自定义组件,用于渲染 imgh2 标签。

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>