最近感觉到 Markdown 似乎已成为各大社区的编辑器标配所支持的格式,侧面看来其设计之初的目标 “ to be used as a format for writing for the web.” 已经成为了现实。不妨就扒一扒互联网 Markdown 的这些事儿。

Markdown 的演进

Markdown 是一种标记语言,对比于 HTML 这样的标记语言来说简洁很多,因此其描述为轻量级的标记语言(lightweight markup language,简称 LML)更为合适。当然,轻量级标记语言并非只有 Markdown,在其之前就有很多种。比如:Setext (Structure Enhanced Text) 用于一些纯文本的场景比如 Email、Usenet(新闻组)等。

也有能把格式文本转换为HTML类似早期语言 reStructuredText(rST),为了更好的阅读 Python源码和 Python 技术源码而设计。

Markdown 就是在这样的背景下诞生,在2004年由John Gruber和Aaron Swartz共同创建出来,有兴趣的可以看下最初文章《Introducing Markdown》。 这两个作者也有很多故事,比如Gruber是《The Talk Show》播客节目主持人;Swartz 2013年已经故去,其曾因使用麻省理工颁发给他的访客用户帐户从JSTOR系统下载学术期刊文章,随后被麻省理工院警方以违反国家和进入国家的指控逮捕。

(PS:笔者并非八卦博主就不多赘述了,感兴趣的自行了解)

到了2008年时候 Stack Overflow 的联合创始人 Jeff Atwood 推其成为 Stack Overflow的编辑方式,并且非常认同其设计;也就是这个时候开始大规模的在程序员中流行起来。Github 大概在2009年开始使用 Markdown,并推出扩展版 GitHub Flavored Markdown (GFM)。

2012年,Jeff 提议Stack Exchange、GitHub、Meteor、Reddit 等一些访问量大的公司组织一起制定出 Markdown的标准规范和其实现的标准测试用例。2014年的时候发布了一个Standard Markdown 的项目,不过 Gruber反对使用Markdown的名字,后来最终改成了 CommonMark,其中包含了 600+个测试用例以及 C语言和 JavaScript的实现。

2016年时候 IETF也发布了征求意见稿RFC7763,在media type中定义了 test/markdown 的类型;2017年 GFM基于CommonMark Spec正式发布了 GitHub Flavored Markdown Spec,支持表格、任务列表、禁止HTML等等。

当然到目前 Markdown 演进也远未结束,除开 GFM 其实还有更多的 基于 CommonMark 的扩展,比如微软的 Markdown for Microsoft Learn 有 !NOTE 的扩展语法。

Markdown 解析引擎

Markdown解析引擎也是非常多的,这里主要介绍一些 JavaScript 开源项目。

Showdown

正如演进提到的 2004年的时候,Gruber写了第一个版本的 Markdown 解析引擎 Markdown.pl ; 2007年时候 John Fraser 基于 Gruber的工作成果创建了 Showdown;不过,似乎 Fraser 早已经不再维护了,Corey Innis 将其迁移到了 Github , 从迁移后的提交看 Santos 维护比较多。它除开实现了 Markdown 原始标准外,也支持一些 Atx、Setset的语法规则。比如:# My Heading #等。

基本使用


var showdown = require('showdown'),
converter = new showdown.Converter(),
text = '# hello, markdown!',
html = converter.makeHtml(text);

Marked

Marked 同样也是非常早的 Markdown解析引擎,Christopher Jeffrey在 2011年最早创建的,目前GitHub上已经 30k+ 的收藏。很多产品都在使用,不过虽然其支持CommonMark以及GFM,但是似乎支持的还不够完整,截至到2022年11月的 V4.2.3 还并没有 100% 支持到两大标准。有个有意思的现象,今年其版本更新非常快 2023年3月份还是 V4.3.0 到如今11月份版本迭代到了 V9.1.5。

基本使用

import { marked } from 'marked';
// or const { marked } = require('marked'); const html = marked.parse('# Marked in Node.js');

Commonmark

应该说是 Commonmark Spec的亲闺女,John MacFarlane 2014年时候最早创建的,他同时也是标准制定人之一,还是 Pandoc的作者。将Markdown文档解析成 抽象语法树(AST),并通过渲染(AST)转换成 HTML 或者 XML;从笔者测试看其性能非常优异。

基本使用

var reader = new commonmark.Parser();
var writer = new commonmark.HtmlRenderer();
var parsed = reader.parse("Hello *world*"); // parsed is a 'Node' tree
// transform parsed if you like...
var result = writer.render(parsed); // result is a String

Remarkable 与 Markdown-it

Remarkable 是2014年左右的开源项目,目前已更新很少了;Markdown-it 是 Remarkable 核心两个作者后面开的开源项目,同时借鉴参考了 MacFarlane 的 Commonmark 的一些实现,也是功能、扩展性都非常不错的 Markdown解析引擎 目前有 16.2k 的收藏。

基本使用

var md = require('markdown-it')();
var result = md.render('# markdown-it rulezz!');

Micromark 与 Remark

Micromark 和 Remark 创建者都是 Titus Wormer(wooorm),14年毕业于阿姆斯特丹大学,同时也是 unified 研发生态的创建者。unified 是一个文本处理库、插件和工具的生态系统,其生态内有500+的开源库,在Github 有1.3m+的项目在使用。

Remark目前就是unified生态中的一员,严格说来它还早于unified,是一个Monorepo风格的管理库,主要包含remark-parse 、remark-stringify、remark、remark-cli ,其功能相信从命名也能看出一二。

  • remark-parse — 用于将Markdown文本转化为语法树(mdast)的插件
  • remark-stringify — 用于将语法树(mdast)转化为Markdown文本的插件
  • remark — 包括unified、remark-parse和remark-stringify,适用于输入和输出都是Markdown的情况
  • remark-cli — 基于remark的命令行界面工具,用于在脚本中检查和格式化Markdown

PS:Markdown Abstract Syntax Tree 简称为 mdast

其中最被广泛使用的是 remark-parse,其解析上也应用了Micromark一些库。

基本使用

// micromark
import {micromark} from 'micromark'
console.log(micromark('## Hello, *world*!')) // remark
import {unified} from "unified";
import remarkParse from 'remark-parse'
import remarkRehype from 'remark-rehype'
import rehypeStringify from 'rehype-stringify' console.log(
unified().use(remarkParse)
.use(remarkRehype)
.use(rehypeStringify)
.processSync("Hellow,*world*").value
);

其他

或许会疑惑为什么没有看到 Tiptap、ProseMirror、React-markdown? Tiptap 围绕 PoroseMirror研发的开源项目,ProseMirror 主要聚焦在富文本编辑器领域 对于 Markdown解析引擎默认使用的是 Markdown-it;React-markdown 聚焦在 React component领域上,Markdown解析引擎用的是 Remark,其实类似还有 Milkdown 等等。而且围绕 Markdown 解析引擎研发的开源项目非常之多,仅 reamrk-parse 在写本文时就有 100w+代码库,2k+ Packages在使用。

当然受限于笔者知识也必然会有遗漏的Markdown解析引擎,行文见谅。

要用,选哪个?

这个问题比较直接也比较实际,但其实很难一言论断。应当从多方面考虑,比如是否需要100%支持 GFM?是否有自定义扩展语法的需要?自定义扩展语法是否比较复杂?又是否需要兼容Atx之类的写法?是否对性能有很高要求?是否需要使用到 AST抽象语法树?等等;作为一个向往和平与爱的博主,更多的建议还是根据自身需求来选择开源Markdown引擎,其实每一个产品都有自己独特的优劣。

当然什么也不考虑,就只是随意看看,可能 Commonmark 、unified生态会是一个不错的选择。

附上一张文章中提及到的Markdown解析引擎的NPM下载趋势图,可以看到近些年来发展非常迅速,特别注意下其数值大小并非代表项目好坏。客观上说每个项目定位不同、使用范围不同、年限长度不同,其npm上下载量的自然也就不同;根据自身需求来选择合适的Markdown解析引擎最为重要。

PS:这图为什么每年都有个陡降点?12月25日圣诞节

未来呢?

从大量轻量语言的出现,到 Gruber 和 Swartz 创建 Markdown;从 Jeff 对 Markdown 设计的热爱,推其广泛应用与制定标准 到 Gruber 反对标准使用 Markdown之名;从 CommonMark、GFM的出现,到 ETF征求意见稿发布;还有贯穿其中的各种 Markdown 扩展、应用到各种场景的开源项目,演进到如今不可谓不热闹,颇有一番春秋战国百家争鸣的味道。

即使今天这场“百家争鸣”也还未停息,目前最热的技术莫过于AI、AIGC,ChatGPT 默认显示就是Markdown语法;由于 Markdown 轻量的语法、结构化的文本,助其可能成为了AI时代文本格式的首选之一;此外轻量语言之间仍在相互借鉴,新的扩展语法还在不断涌现,各大开源生态的战场仍在继续书写历史。

但在 Markdown 演进中可以看到标准制定、基础解析引擎整体国内身影较少,但随着国内云社区、开源社区发展日益火热,更多的有志青年投身其中,相信未来也是璀璨的;也或许某天会有 Tencent Flavored Markdown Spec(TFM) 的出现。

欢迎关注 Java 研究者博客、公众号。

互联网那些技术 | 扒一扒互联网Markdown的那些事儿的更多相关文章

  1. 斯诺登称NSA攻破互联网加密技术

    据财新网报道,本已渐渐平静的斯诺登泄密事件在9月6日再掀波澜.英国<卫报>.美国<纽约时报>和美国非盈利调查新闻机构ProPublica联合报道称,根据斯诺登提供的大量文件,美 ...

  2. 从DOS时代至移动互联网的技术路线回顾

    从DOS时代至移动互联网的技术路线回顾 Normal 0 7.8 磅 0 2 false false false EN-US ZH-CN X-NONE /* Style Definitions */ ...

  3. View绘制详解(三),扒一扒View的测量过程

    所有东西都是难者不会,会者不难,Android开发中有很多小伙伴觉得自定义View和事件分发或者Binder机制等是难点,其实不然,如果静下心来花点时间把这几个技术点都研究一遍,你会发现其实这些东西都 ...

  4. 扒一扒EOS的前世今生

    扒一扒EOS的前世今生 EOS是什么?   EOS可以认为是Enterprise Operation System的缩写,即商用的一款分布式区块链操作系统,EOS主要为了解决百万级用户的使用问题,为企 ...

  5. 扒一扒@Retryable注解,很优雅,有点意思!

    你好呀,我是歪歪. 前几天我 Review 代码的时候发现项目里面有一坨逻辑写的非常的不好,一眼望去简直就是丑陋之极. 我都不知道为什么会有这样的代码存在项目里面,于是我看了一眼提交记录准备叫对应的同 ...

  6. 扒一扒Bean注入到Spring的那些姿势,你会几种?

    大家好,我是三友~~ 这篇文章我准备来扒一扒Bean注入到Spring的那些姿势. 其实关于Bean注入Spring容器的方式网上也有很多相关文章,但是很多文章可能会存在以下常见的问题 注入方式总结的 ...

  7. Sentinel为什么这么强,我扒了扒背后的实现原理

    大家好,我是三友~~ 最近我在整理代码仓库的时候突然发现了被尘封了接近两年之久的Sentinel源码库 两年前我出于好奇心扒了一下Sentinel的源码,但是由于Sentinel本身源码并不复杂,在简 ...

  8. linux2.6.24内核源代码分析(2)——扒一扒网络数据包在链路层的流向路径之一

    在2.6.24内核中链路层接收网络数据包出现了两种方法,第一种是传统方法,利用中断来接收网络数据包,适用于低速设备:第二种是New Api(简称NAPI)方法,利用了中断+轮询的方法来接收网络数据包, ...

  9. linux2.6.24内核源代码分析(1)——扒一扒sk_buff

    最近研究了linux内核的网络子系统上的网络分组的接收与发送的流程,发现这个叫sk_buff的东西无处不在,内核利用了这个结构来管理分组,在各个层中传递这个结构,因此sk_buff可以说是linux内 ...

  10. 扒一扒ReentrantLock以及AQS实现原理

    提到JAVA加锁,我们通常会想到synchronized关键字或者是Java Concurrent Util(后面简称JCU)包下面的Lock,今天就来扒一扒Lock是如何实现的,比如我们可以先提出一 ...

随机推荐

  1. Vue-treeselect 实现下拉树懒加载,末节点不要箭头

    项目需要,可选择的下拉树,由于数据过多,显示要有层级感,所以使用了懒加载模式 vue-treeselect官网:https://www.vue-treeselect.cn/ 1.前端代码 1.下载依赖 ...

  2. Smali语言

    什么是Smali smali语言是Davlik的寄存器语言,语法上和汇编语言相似,DalvikVM与JVM的最大的区别之一就是DalvikVM是基于寄存器的.基于寄存器的意思是,在smali里的所有操 ...

  3. C++(继承)

    继承 struct Person { int age; int sex; }; struct Teacher { int age; int sex; int level; int classId; } ...

  4. Python中的弱引用与基础类型支持情况探究

    背景 最近有一个业务场景需要用Python自行实现一个简单的LRU cache,不可避免的接触到了弱引用这一概念,这里记录一下. 强引用 Python内存回收由垃圾回收器自动管理,当一个对象的引用计数 ...

  5. Anaconda 使用时,conda activate 失败

    今天使用一台电脑上新安装的 anaconda 时,运行 conda activate, 出现如下报错: 错误提示中,说要把 . C:\ProgramData\Anaconda3\etc\profile ...

  6. C#数据去重的这几种方式,你知道几种?

    前言 今天我们一起来讨论一下关于C#数据去重的常见的几种方式,每种方法都有其特点和适用场景,我们根据具体需求选择最合适的方式.当然欢迎你在评论区留下你觉得更好的数据去重的方式. 使用HashSet去重 ...

  7. React:如何在普通函数中使用Hook

    解决方案

  8. BUUCTF Reverse-[FlareOn6]Overlong-动态调试

    没有什么问题,直接进 三个函数,字符串也没啥特殊的 应该是个加密 返回上面分析 数据很大,你忍一下 也就是说它会找28位加密 然后我们接着分析 这个提示刚好28位 也就是说28位对应这个框 如果我们修 ...

  9. [ABC128E] Roadwork

    2023-01-14 题目 题目传送门 翻译 翻译 难度&重要性(1~10):4 题目来源 AtCoder 题目算法 区间覆盖,线段树,双堆 解题思路 可以将问题转化为区间覆盖问题和单点查询问 ...

  10. Inno SetUp安装包:如何在程序安装时卸载驱动程序

    pnputil命令行方式卸载 如果您想通过命令行卸载.INF文件的驱动程序,您需要使用PnPUtil命令.以下是一个示例: pnputil /delete-driver oem0.inf /unins ...