最近感觉到 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. 大白话讲讲 Go 语言的 sync.Map(二)

    上一篇文章 <大白话讲讲 Go 语言的 sync.Map(一)> 讲到 entry 数据结构,原因是 Go 语言标准库的 map 不是线程安全的,通过加一层抽象回避这个问题. 当一个 ke ...

  2. 如何编写难以维护的React代码?——滥用useEffect

    如何编写难以维护的React代码?--滥用useEffect 在许多项目中,我们经常会遇到一些难以维护的React代码.其中一种常见的情况是滥用useEffect钩子,特别是在处理衍生状态时.让我们来 ...

  3. Sealos 私有化部署完全指南

    Sealos 用了五年的时间从一个 K8s 一键安装工具蜕变成了一个真正的云操作系统,将产品体验提升到了极致,也收获了 10w+ 的社区用户. 一个多月前,Sealos 正式发布了公有云托管版本,社区 ...

  4. Python和PyTorch深入实现线性回归模型:一篇文章全面掌握基础机器学习技术

    1. 简介 1.1 线性回归模型概述 线性回归是一种统计学中的预测分析,该方法用于建立两种或两种以上变量间的关系模型.线性回归使用最佳的拟合直线(也称为回归线)在独立(输入)变量和因变量(输出)之间建 ...

  5. Cilium 系列-7-Cilium 的 NodePort 实现从 SNAT 改为 DSR

    系列文章 Cilium 系列文章 前言 将 Kubernetes 的 CNI 从其他组件切换为 Cilium, 已经可以有效地提升网络的性能.但是通过对 Cilium 不同模式的切换/功能的启用,可以 ...

  6. 学习OI两年我到底收获了什么

    做一个小小的总结 学习了两年的代码,刚刚要进入高中,留下一点文字给以前的学习做一个总结. 命中注定の邂逅-- 这两年之间,和编程产生了比学习更为低调的羁绊关系(我觉得用这个词语比较合适).编程给我带来 ...

  7. 记一次因为C#官方扩展导致自动补全出错的情况 (C# & Godot)

    现象 最近使用Vscode结合Godot使用时突然发现自动补全出问题了,发现一部分自动补全能弹出补全项目,但是确认后不起作用,还会吞掉弹出自动补全后输入的字符.大概是下图这样的感觉(截图时已修好,图为 ...

  8. 【解惑】介绍.net中的DataTable的AcceptChanges方法

    DataTable.AcceptChanges方法是一个用于DataTable对象的方法,它将所有对DataTable进行的更改标记为已接受.这意味着所有新增.修改和删除的行都将被标记为DataRow ...

  9. CentOS7系统初始化个人配置

    以下内容为个人最小化安装后的配置步骤 更换yum源为阿里云 yum install -y epel-release lrzsz wget yum-axelget mv /etc/yum.repos.d ...

  10. EXP 一款 Java 插件化热插拔框架

    EXP 一款 Java 插件化热插拔框架 前言 多年以来,ToB 的应用程序都面临定制化需求应该怎么搞的问题. 举例,大部分本地化软件厂家,都有一个标准程序,这个程序支持大部分企业的功能需求,但面对世 ...