使用统一、备注和重新炒作将 Markdown 文件转换和自定义为 HTML
使用统一、备注和重新炒作将 Markdown 文件转换和自定义为 HTML
托管自己的博客是每个软件开发人员都想做的事情。它通常更便宜,您可以控制自己的数据,并且可以选择将其货币化。
无论您是否是 Web 开发人员,如果您想经常发帖,编写 HTML 可能会很麻烦。大多数时候,您会希望专注于帖子的内容,而不是您为托管它而构建的网页的代码质量。
在这里,Markdown 是一个很好的工具来加速你的博客发布能力。
降价
Markdown 定义了一组规则,指示文本应该具有哪种格式。以下是一些最基本的规则:
Markdown 使编写格式化文本变得容易。例如,下面的 Markdown 片段:
**# 欢迎到我的博客!** 从[第一篇文章](/blog/2022/9/markdown-to-html)开始
格式如下:
欢迎到我的博客!
从 第一篇
有许多工具可以从 Markdown 构建博客。 杰基尔 是使用 Markdown 构建静态网站的流行选择。
但是,当您撰写技术博客时,您会开始注意到使用开箱即用解决方案的局限性。我达到这些限制的关键在于 代码格式化 .
限制:代码格式
Markdown 有两种高亮代码的方式。块元素:
```
公共类应用程序{
公共静态 void main(String[] args) { ... }
}
```
和内联元素
`System.out.println("Hello")`
棱镜JS 和 高亮.js 是两个 JavaScript 库,用于突出显示多种编程语言的代码关键字。
使用 PrismJS,设置格式化语言就像引入库(连同给定主题的 CSS)然后将语言名称添加到代码块声明中一样简单。对于上面的示例,我们得到以下结果:
```java
公共类应用程序{
公共静态 void main(String[] args) { ... }
}
```
转换为:
公共类应用程序{
公共静态 void main(String[] args) { ... }
}
但是,内联代码会发生什么?降价规则不允许我们做类似的事情
`System.out.println("Hello")`java
如果我们想将自定义 CSS 类添加到 Markdown 生成的组件中,我们发现 Markdown 没有指定这样做的语法。像 Jekyll 这样的工具确实允许在 Markdown 中使用 HTML 元素,但它开始有点违背使用 Markdown 来加速编写的目的。
如果我们想扩展 Markdown 语法,我们必须修改 Markdown 转换为 HTML 的方式。
弄脏我们的手:引入统一
统一 是一个处理 Markdown 的框架。它是一个基于插件的工具,允许您检查和修改 Markdown 转换为其他格式(如 HTML)的方式。
它的一般结构如下所示:
从“统一”导入{统一} 常量结果 = 等待统一()
.use(...) // 添加一个插件
.process(等待读取('./example.md'))
Unified 依赖于两个工具集 评论 和 再炒作 :
- 评论 : “remark 是一个用插件转换 markdown 的工具。这些插件可以检查和更改您的标记。您可以在服务器、客户端、CLI、deno 等上使用备注。”
- 再炒作 : “rehype 是一种使用插件转换 HTML 的工具。这些插件可以检查和更改 HTML。你可以在服务器、客户端、CLI、deno 等上使用 rehype。”
结合这两个工具集中的插件,我们可以完全自定义将 Markdown 解析为 HTML 的方式。
一些常见的插件是:
- 注释解析 : remark 插件添加对解析 markdown 输入的支持。
- 重新炒作 : 将 markdown 转换为 HTML 以支持重新炒作的备注插件。
- 再炒作文件 : rehype 插件将片段包装在文档中。
- 再炒作格式 : rehype 插件来格式化(漂亮打印)一个 HTML 文档。
- 重新炒作字符串化 : rehype 插件添加对序列化 HTML(到字符串)的支持。
将所有这些插件放在一起,示例如下所示:
从“统一”导入{统一}
从'remark-parse'导入remarkParse
从'remark-rehype'导入remarkRehype
从'rehype-document'导入rehypeDocument
从 'rehype-format' 导入 rehypeFormat
从 'rehype-stringify' 导入 rehypeStringify
从'to-vfile'导入{读取} 常量结果 = 等待统一()
.use(remarkParse)
.use(remarkRehype)
.use(rehypeDocument)
.use(再炒作格式)
.use(重新炒作字符串化)
.process(等待读取('./test/example.md'))
在哪里 结果
是一个 文件
- HTML 文件的虚拟表示 - 可以轻松保存为 HTML 文件。
分析转换过程
下图表示 Markdown 文件变成 HTML 的过程:
以下是该过程的分步说明:
- 读取 Markdown 文件内容并将其放入
文件
. 评论
解析和处理 Markdown 文本,并将其转换为 抽象语法树 (AST) .这棵树由 单体 节点,它遵循 Markdown 抽象语法树 (mdast) 结构体。重新炒作
翻译mdast
结构成一个 超文本抽象语法树(hast) 结构,也由单体
节点。再炒作
过程和转变仓促
结构成 HTML。
Unified 允许我们构建插件并同时处理 mdast
和 仓促
结构。
mdast 和 hast
在我们尝试自定义转换过程之前,让我们看看如何 mdast
和 仓促
被代表。
在 mdast
,每个节点代表一个抽象元素。例如, 段落 或者 p
元素表示如下:
{
“类型”:“段落”,
“孩子”:[{“类型”:“文本”,“价值”:“阿尔法布拉沃查理。” }]
}
同一个节点在 仓促
如下:
{
“类型”:“元素”,
“标签名”:“p”,
“特性”: {},
“孩子”:[{“类型”:“文本”,“价值”:“阿尔法布拉沃查理。” }]
}
请注意 mdast
具有不同的抽象级别 仓促
: 仓促
表示 HTML 元素,其中“段落”是用标签名称定义的 p
.
我们可以看到相同的差异 代码
.以下降价...
```
富()
```
…变成以下 mdast
:
{
“类型”:“代码”,
“朗”:空,
“元”:空,
“价值”:“富()”
}
…以及以下 仓促
:
{
“类型”:“元素”,
"tagName": "pre",
“特性”: {},
“孩子们”: [
{
“类型”:“元素”,
"tagName": "代码",
“特性”: {},
“孩子们”: [
{
“类型”:“文本”,
"值": "foo()\n"
}
]
}
]
}
并且内联版本 bar()
成为 mdast
:
{
“类型”:“内联代码”,
“价值”:“酒吧()”
}
……还有 仓促
:
{
“类型”:“元素”,
"tagName": "代码",
“特性”: {},
“孩子们”: [
{
“类型”:“文本”,
“价值”:“酒吧()”
}
]
}
请注意,块版本 代码
包裹 <code/>
中的 HTML 元素 <pre/>
, 以保留对齐和空格。然而,内联版本只是创建了一个 <code/>
元素。
块版本允许我们向 郎
财产。以下马当:
```js
富();
```
…解析如下:
{
“类型”:“代码”,
"lang": "js",
“元”:空,
“价值”:“富();”
}
和:
{
“类型”:“元素”,
"tagName": "pre",
“特性”: {},
“孩子们”: [
{
“类型”:“元素”,
"tagName": "代码",
“特性”: {
“类名”:[“语言-js”]
},
“孩子们”: [
{
“类型”:“文本”,
"值": "foo();\n"
}
]
}
]
}
Markdown 解析器遵循 HTML5 规范 为了 <code/>
定义该块中使用的语言:使用 郎
属性值为 language-[LANG]
.
如我们所见,我们可以使用 CSS 来查找 <code/>
具有给定语言的类名的块(例如 语言-js
) 并相应地处理它们。
事实上,PrismJS 寻找 <code class="language-[LANG]></code/>
块以标记文本内容并突出显示关键字。
虽然 HTML5 规范允许使用 郎
在 <code/>
元素 不是 包装成 <pre/>
元素,Markdown 语法中没有定义方法来设置该属性。
在内联代码中添加“lang”
我们将扩展 Rehype 流程来解释内联代码块,如下所示:
`highlighted^const inline = "code";`
我们将代码块的内容一分为二: 分隔符前的文本 ^
以及之后的文字:
const [part1, part2] = ['markdown', 'const inline = "code";']
然后,我们将使用第一部分作为 代码
的类名,第二部分作为其内容。
以下代码片段是 Rehype 插件的摘录 rehype-inline-code-classname 我发布到 NPM 以允许将类名设置为内联代码块:
从'hast'导入{元素,根,文本}
从“统一”导入{变压器}
从'unist-util-visit'导入{访问} 导出函数 rehypeInlineCodeClassNamePlugin(
选项?:选项
): 无效 |变压器<Root, Root>{
常量分隔符=选项?。分隔符?? '^'
const trailing = options?.trailing ??错误的 返回函数(根){
函数提取(值:字符串){
常量 [_, 第 1 部分, 第 2 部分] =
value.match(new RegExp(`(.+)\\${separator}(.+)`)) ?? [] 返回尾随
? { 类名:part2,内容:part1 }
: { 类名:part1,内容:part2 }
} 访问(
根,
'元素',
功能访问者(节点:元素,我:数字|空,父:任何){
if (node.tagName !== 'code') 返回
if (!parent || parent.tagName === 'pre') 返回 const [{ value, ...rest }] = node.children as Text[]
if (value.includes(separator) && value.length > 2) {
常量 { 类名,内容 } = 提取(值)
node.children = [{ value: content, ...rest }] as Text[]
节点属性 = {
类名:类名,
}
}
}
) 返回根
}
}
导出类型选项 =
| {
分隔符:字符串
尾随:布尔值
}
|空白
|不明确的
使用 AST:访问
在这个插件中,我们使用了函数 访问
从 unist-util-访问 导航 仓促
树并找到所有节点:
访问(根,'元素',
功能访问者(节点:元素,我:数字|空,父:任何){
//..
}
请注意 访问
接收三个参数:
- 的根节点
仓促
. - 我们要访问的节点类型。在我们的示例中,它将遍历所有类型的节点
元素
. - 将接收对
节点
, 和索引一世
和节点的父母
.
从这里,我们可以修改 仓促
我们需要以任何方式:删除、添加或更新节点。
处理所有“代码”元素
我们过滤掉所有没有 标签名
的 代码
, 和所有 标记名称:代码
具有 标记名称:前
节点作为父节点,因为这些是块类型的代码元素:
if (node.tagName !== 'code') 返回
if (!parent || parent.tagName === 'pre') 返回
我们得到节点的 价值
属性并检查它是否包含预期的分隔符 ^
(并且代码元素实际上有足够的字符来包含左右子字符串):
const [{ value, ...rest }] = node.children as Text[]
if (value.includes(separator) && value.length > 2) {
//...
}
如果可以使用分隔符解析该值,则我们使用 正则表达式
:
常量 [_, 第 1 部分, 第 2 部分] =
value.match(new RegExp(`(.+)\\${separator}(.+)`)) ?? []
该插件允许配置对象定义自定义分隔符以及我们是否要在块末尾设置类名,如下所示:
`const inline = "code";^highlighted`
根据配置,我们设置 班级名称
和 内容
和 第1部分
和 第2部分
;然后我们更新 仓促
节点如下:
常量 { 类名,内容 } = 提取(值)
node.children = [{ value: content, ...rest }] as Text[]
节点属性 = {
类名:类名,
}
我们重置节点的 孩子们
属性与代码块的实际内容,我们设置 班级名称
我们使用分隔符提取的子字符串的属性。
使用插件
要包含我们的插件,我们只需要将其添加到插件链中 重新炒作
,因为我们正在使用 仓促
:
从 '../src/plugin/index.ts' 导入 { myRehypePlugin } 常量结果 = 等待统一()
.use(remarkParse)
.use(remarkRehype)
.use(myRehypePlugin) // 我们的再炒作插件
.use(rehypeDocument)
.use(再炒作格式)
.use(重新炒作字符串化)
.process(等待读取('./test/example.md'))
我们现在可以为内联代码块设置我们需要的任何类名:
`语言-java^foo()`
会变成:
<code class="markdown">^</code>
重新审视代码格式
由于我们现在可以为内联代码块设置类名,因此我们可以使用 CSS 和 JavaScript 来进一步格式化和设置这些元素的样式:
代码[class*='语言-'] {
背景颜色:#f2f5ff;
填充:0.1em;
边界半径:0.3em;
空白:正常;
}
不幸的是,我们所做的更改不足以使用像这样的插件进行操作 再炒作 ,正如那些假设 <code/>
块被包裹 <pre/>
元素。为了在我们的代码中突出关键字,我们必须使用一个工具来标记和突出关键字,这正是 棱镜JS
做。
在以后的博客文章中,我们将扩展此示例以使用 折射镜 创建自定义 再炒作
扩展的插件 再炒作
的功能,并允许我们突出显示内联代码块中的关键字。
结论
Unified 提供了一个插件生态系统,允许我们扩展和自定义 Markdown 转换为 HTML 的方式。
创建一个 再炒作
或者 评论
插件要求我们处理抽象语法树,但是 单体
像这样的工具 unist-util-访问
提供帮助函数来降低这些任务的复杂程度。
像这样的框架 NextJS 与 Unified/remark 插件很好地集成,允许将 Markdown 文件转换为可以在应用程序中轻松呈现的 HTML。他们甚至提供与 MDX , Markdown 的超集,允许使用 React 组件 里面 降价文件。
然而,Unified 可用于直接生成静态 HTML 页面,使我们能够不断更新我们的自托管博客。
关于托管
那里有自我管理博客的免费托管。一个很好的例子是 Github 页面 ,它与 Jekyll 等工具很好地集成在一起。但是我们可以只使用通过 Unified 创建的 HTML 文件。
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明