Skip to content
0

markdown 的古往今来

介绍 markdown 的诞生,以及如何使用 markdown-it 去解析 markdown,背后的解析流程和基本原理

什么是 markdown

现在大部分人都知道 markdown,并且了解一些例如标题、文本加粗、列表之类的最简单的 markdown 语法,实际上也正是如此

markdown 的语法其实并不复杂。它简洁、易读、易写,这也是它流行的原因之一。

markdown 是一种标记语言,标记语言是一种文本编码系统,它使用标记和其他符号(可能包括 "*"或 "#"字符)来确定文本在计算机处理时的显示方式。与 HTML 一样,标记语言也属于此类。

可以说 markdown 是 HTML 的一种预处理器。类似于 Less/Sass/Stylus 之于 CSS 的关系

我们举一个例子来看看 markdown 转换为 HTML 的例子:

## Hello world


1. Task 1
2. Task 2
3. Task 3
1. Sub-task 1
2. Sub-task 2
4. Task 4



This is **bold** text

.md/.markdown 文件中,特定的标记规则会被唯一准确地映射到对应的 HTML 中。二者是一一对应的关系

这使得 HTML 也很容易转为 markdown 文本

从轻量级标记语言说起

轻量级标记语言 Lightweight Markup Languages (LML),也称为简单标记语言,是一种语法简单、微妙的标记语言。它旨在使用任何通用文本编辑器轻松编写,并以原始形式轻松阅读。

此类语言的另一个用途是在基于网络的发布中提供数据输入,其中输入界面是一个简单的文本框。然后,服务器软件将输入转换为通用文档标记语言,例如 HTML。

LML 可以按其标签类型进行分类。大多数语言区分行或块的标记和较短文本范围的标记,但有些语言仅支持内联标记。一些标记语言是为特定目的而定制的,例如记录计算机代码或转换为某种输出格式(通常是 HTML),仅此而已,其他标记语言在应用中更通用。

AsciiDoc 是也一种文本标记语言,使用 = 标记标题、缩进表示代码块、反引号表示加粗:

= My Article
J. Smith

https://wikipedia.org[Wikipedia] is an
on-line encyclopedia, available in
English and *many* other languages.

== Software

You can install 'package-name' using
the `gem` command:

 gem install package-name

== Hardware

Metals commonly used include:

* copper
* tin
* lead

一种轻量级标记语言,但感觉上仍然是在写 HTML:

[i]italicized text[/i]
[s]strikethrough text[/s]
italicized textstrikethrough text

wikitext 是目前维基百科仍在使用的语法(出现的时候还没有 markdown)。它使用 [[ ]] 来标记跳转站内的其他关键词,使用单引号来标记粗体和斜体等:

"Take some more [[tea]]," the March Hare said to Alice, very earnestly.


"I've had '''nothing''' yet," Alice replied in an offended tone, "so I can't take more."


"You mean you can't take ''less''," said the Hatter. "It's very easy to take ''more'' than nothing."

"Take some more tea," the March Hare said to Alice, very earnestly.

"I've had nothing yet," Alice replied in an offended tone, "so I can't take more."

"You mean you can't take less," said the Hatter. "It's very easy to take more than nothing."

是一种轻量级标记语言,旨在成为 wiki 的通用标记语言,支持并简化不同 wiki 引擎之间的内容传输

一种轻量级标记语言,用于记录 Perl 编程语言以及 Perl 模块和程序

  • Markdown

我们今天的主角 markdown,我们开始探索探索它的魅力

markdown 历史

Markdown 于 2004 年推出时,它立即被博主和技术爱好者所喜爱。它还为未来的无代码工具奠定了基础。

近二十年后,这种“人性化”标记语言比以往任何时候都更加重要。想知道这一切是如何开始的吗?系好安全带,让我们回顾一下 Markdown 的历史。

从纯文本文件到 markdown

早在 2000 年代初,网络写作还很令人困惑。当时的博客还处于起步阶段,WordPressTypePad 等平台在功能方面并不完善。如果你想写博客,就必须学习一些 HTML。

后来,约翰-格鲁伯(John Gruber)出现了。作为一名早期的博客作者和用户界面设计师,格鲁伯主要通过他的博客 Daring Fireball 发布许多技术文章。虽然他喜欢写作和在线发布,但他不喜欢使用 HTML 格式化所有内容的想法。

我知道 HTML,所以在写作上没有问题。我在技术上也没有问题,但最终还是厌倦了,感觉就像是在给自己找麻烦,而且我真的觉得 HTML 让我很难校对我的作品。因此,我所有的校对工作都是在浏览器或文本编辑器内置的HTML渲染器中预览,然后在那里预览。

John Gruber

受到纯文本电子邮件通信美学的启发,Gruber 在 2000 年代初开始研究 Markdown。他的首要目标是从程序员手中夺走创建数字文档的过程,并将其返还给文档作者。

早期的推动

亚伦·斯沃茨(Aaron Swartz)是一位熟练的程序员和政治活动家,在 markdown 的早期发布起了重要的作用

和格鲁伯一样,斯沃茨对文字的热爱使他面临着当时其他网络作家所面临的同样的挑战。作为一名早期的博主,他想要一种用户友好的方式来格式化文本,这种方式比使用 HTML 标签更加自然和直观。

“我厌倦了将我的写作降低到计算机的水平。为什么我必须将所有内容都放在烦人的尖括号中才能让它知道我的意思?在过去的十年里,我们对于计算机处理的文本已经有了很好的标准化约定;现在是时候采用一种文本格式来承认它们,而不是发明自己的做事方式。”

Aaron Swartzr

Swartz 提出了自己的语法 —— atx。Swartz 的 atx 语法基于 TeX 编码、reST 语法以及纯文本电子邮件约定。他重新定义了格式规则,包括使用列表、链接和 H1-H6 标题,这些标题现在由相应数量的“#”字符分隔。

This_is_ emphasis!

*Bold text!*

Time to write |code|

``Double quoting''

`Single quoting'

--- (en-dash)

-- (em-dash)

在某个时候,格鲁伯和斯沃茨进行了沟通,后者成为了 Markdown 的“唯一 beta 测试者”。 Swartz 提供了有关语法的反馈,甚至编写了 html2text,这是一个用于将 HTML 转换为 Markdown 的免费转换工具。

开始受欢迎

随着 Markdown 在网络上越来越受欢迎,更多的网站和应用程序开始使用它。

最初的 Markdown 缺乏许多写作者所期望的功能:除非用户编写原始的嵌入式 HTML,否则不支持表格、自动生成目录、语法高亮、文件分割等。

不久之后,GitHubReddit 和 Stack Exchange 等社区就按照 Gruber 的意图提出了自己的 Markdown“风格”。

它从未成为正式的标准。Markdown 没有一个明确的规范,因此在过去的十年中,实现方法有很大的差异。Markdown 适用于网络上有组织的内容,可以顺利转换为简洁、结构化的 HTML。它在文档页面、在线论坛、即时通讯、自述文件和博客中很受欢迎。由于其流行性和简洁性,它被技术写作社区采用为编写文档的解决方案。

Markdown 存在多种变体,具有不同的规则和支持的不同功能。例如:# Heading 1 和 #Heading 1。在这里您可以看到,在第一种情况下,主题标签和“标题”一词之间有一个空格,而第二个版本则没有空格。

但所有这些创新都造成了歧义和混乱。虽然 Markdown 风格增加了更多的格式灵活性(表格、代码、嵌入),但它们也缩短了学习曲线。这使得驾驭生态系统变得越来越困难。

CommonMark 作为标准化 Markdown 的出现

2014 年,一群 Markdown 爱好者——其中最著名的是哲学教授 John MacFarlane 和 Discourse 联合创始人 Jeff Atwood——开始了标准化工作,旨在将所有实现集中统一在一起。

阿特伍德、麦克法兰以及代表 Reddit、GitHub、Meteor 和 Stack Overflow 的其他成员开始工作。这些努力最终以 StandardMark 的形式实现,“Markdown 的标准、明确的语法规范”。但成功是短暂的。

格鲁伯不喜欢标准化 Markdown 的想法。他不仅拒绝参与该项目,还强迫StandardMark更改名称。在格鲁伯的干预下,standardmarkdown.com 更名为 commonmark.org。

"约翰-格鲁伯(John Gruber),这位为我们带来 Markdown 的人,几乎成了阻碍 Markdown 前进和成熟的最大障碍。看到如此疏忽的开源代码管理,我感到非常难过。既然可以与社区合作,为什么要与社区作对呢?没必要这样。也不应该是这样"。

Jeff Atwood

双方之间的争执一直持续着,CommonMark 在没有格鲁伯的支持下继续努力。但这并没有阻止 Markdown 获得狂热的地位,近 20 年后,它仍然是一种广受欢迎的写作工具。

虽然 commonmark 的标准也一直在推进着:https://spec.commonmark.org(最新一次更新是 2024-01-28)

从 Gruber 的话来说就是:没有办法指定一个大家都同意的通用 Markdown 规范。不同的环境需要不同的规则

markdown 的扩展与降级

由于在 commonmark 定义的规范,提供的功能特性实在太少了。不适合复杂或大型文档,所以如之前所说各个网站会为它提供自己的扩展

TIP

一个好的扩展应该考虑到降级的处理,即 markdown 文件在非当前平台展示的时候,不应该出现文本内容异常的情况,从原始文本和渲染结果看,都应该是正常的

例如 GitHub 2022 年新发布的可以在 readme 文件当中的添加高亮代码块的 alert 语法,定义了 5 种类型:

```md
> [!NOTE]  
> Highlights information that users should take into account, even when skimming.

> [!TIP]
> Optional information to help a user be more successful.

> [!IMPORTANT]  
> Crucial information necessary for users to succeed.

> [!WARNING]  
> Critical content demanding immediate user attention due to potential risks.

> [!CAUTION]
> Negative potential consequences of an action.
```

当被当做标准 markdown 处理的时候,结果都降级为了下面的样子。从结果上看,不妨碍阅读。

<blockquote>
<p>[!NOTE]<br>Highlights information that users should take into account, even when skimming.</p>
</blockquote>
<blockquote>
<p>[!TIP]
Optional information to help a user be more successful.</p>
</blockquote>
<blockquote>
<p>[!IMPORTANT]<br>Crucial information necessary for users to succeed.</p>
</blockquote>
<blockquote>
<p>[!WARNING]<br>Critical content demanding immediate user attention due to potential risks.</p>
</blockquote>
<blockquote>
<p>[!CAUTION]
Negative potential consequences of an action.</p>
</blockquote>

[!NOTE]
Highlights information that users should take into account, even when skimming.

[!TIP] Optional information to help a user be more successful.

[!IMPORTANT]
Crucial information necessary for users to succeed.

[!WARNING]
Critical content demanding immediate user attention due to potential risks.

[!CAUTION] Negative potential consequences of an action.

而在 vuepress/vitepress 种,alert 块的语法(基于 markdown-it-container)是这样的:

```md
::: info
This is an info box.
:::

::: tip
This is a tip.
:::

::: warning
This is a warning.
:::

::: danger
This is a dangerous warning.
:::

::: details
This is a details block.
:::
```

可能是为了扩展语法的灵活性,降级之后的渲染结果可读性一般:

<p>::: info
This is an info box.
:::</p>
<p>::: tip
This is a tip.
:::</p>
<p>::: warning
This is a warning.
:::</p>
<p>::: danger
This is a dangerous warning.
:::</p>
<p>::: details
This is a details block.
:::</p>

::: info This is an info box. :::

::: tip This is a tip. :::

::: warning This is a warning. :::

::: danger This is a dangerous warning. :::

::: details This is a details block. :::

markdown 的解析

当下,有两种 markdown 的流行的解析器:markedjs/markedmarkdown-it/markdown-it

marked

最流行、最简单的 JavaScript Markdown 解析器之一,也可作为 CLI 工具使用。它允许您将 Markdown 转换为 HTML,并支持 GitHub 风格的 Markdown。

import { marked } from 'marked'

console.log(marked.parse('# Marked in the browser\n\nRendered by **marked**.'))
/**
<h1>Marked in the browser</h1>
<p>Rendered by <strong>marked</strong>.</p>
 */

markdown-it

遵循 CommonMark 规范并支持扩展和插件的 Markdown 解析器。它是用JavaScript编写的,可以在node.js或浏览器环境中使用。它速度快、易于扩展且默认安全。

import markdownit from 'markdown-it'
const md = markdownit()

console.log(md.render('# markdown-it rulezz!'))

/**
<h1>markdown-it rulezz!</h1>
 */

marked 的解析处理

marked 对于 markdown 的解析总体分为两步,tokenizerender。tokenize 会将 markdown 源码转为 tokens 数组,render 会将 tokens 转为 html。

marked 的词法分析是基于正则表达式的,所以 marked 在解析速度上会比 markdown-it 快,但是在扩展性上会差一些。

在解析开始阶段,直接拿 src 去匹配正则表达式,如果匹配成功了,则生成为一个特定类型的 Token。然后截取 src 的剩余未匹配的部分,继续去用正则匹配。

Block 的 tokenize 生成 tokens:

Block 的 render 生成 html 字符串:

下面从解析到生成的整体流程:

undefined
undefined

我们以一个最简单的 markdown 结构为例子:

import { marked } from './src/marked'

console.log(
  marked.parse(`
## hello

this is **bold** world
`)
)

在经过 lexer 之后,得到 tokens 数组,以及渲染

[
{ type: 'space', raw: '\n' },
{
type: 'heading',
raw: '## hello\n\n',
depth: 2,
text: 'hello',
tokens: [] // src: hello
},
{
type: 'paragraph',
raw: 'this is **bold** world\n',
text: 'this is **bold** world',
tokens: [] // src: this is **bold** world
},
]

markdown-it 的解析处理

markdown-it 是较为经典的解析器,vitepress 底层的 md 解析就是使用的 markdown-it

markdown-it 处理 md 文本的流程和 marked 几乎是一致的:

不同点:

  • marked 使用正则表达式作为词法分析,而 markdown-it 则纯靠 js 代码解析
  • markdown-it 对 token 分的更细致

例子:标题的解析

## Hello world

例子:对于 inline 的解析

This is **bold** text

现在的 markdown

组件时代的 Markdown

打破现代组件和 markdown 的边界:

JSX 派系:文件名以 .mdx 结尾

MDX:代表有 docusaurusastro 等等

```mdx
import {Chart} from './snowfall.js'
export const year = 2023

# Last year’s snowfall

In {year}, the snowfall was above average.
It was followed by a warm spring which caused
flood conditions in many of the nearby rivers.

<Chart year={year} color="#fcb32c" />
```

模板派系:

mdit-vue: md 中使用 vue

```md
---
title: About
---

<div class="text-center">
  <!-- You can use Vue components inside markdown -->
  <div i-carbon-dicom-overlay class="text-4xl -mb-6 m-auto" />
  <h3>About</h3>
</div>

[Vitesse](https://github.com/antfu/vitesse) is an opinionated [Vite](https://github.com/vitejs/vite) starter template made by [@antfu](https://github.com/antfu) for mocking apps swiftly. With **file-based routing**, **components auto importing**, **markdown support**, I18n, PWA and uses **UnoCSS** for styling and icons.


Check out the [GitHub repo](https://github.com/antfu/vitesse) for more details.
```

mdsvex:md 中使用 svelte

Released under the MIT License.