今天给大家介绍的主题是 Markdoc,即由 Stripe 开发的一种基于 Markdown 的文档格式和内容发布框架。
Markdoc官网:https://markdoc.dev/
Markdoc 是一种基于 Markdown 的文档格式和内容发布框架, 它由是 Stripe 内部设计的,以满足面向用户的产品文档的需求。 Markdoc 使用标签和注释的自定义语法扩展了 Markdown,提供了一种为个人用户定制内容并引入交互元素的方法。
可以通过如下方式快速使用 Markdoc:
npm install @markdoc/markdoc
// 或者
yarn add @markdoc/markdoc
安装后就可以在代码中直接引用:
const Markdoc = require('@markdoc/markdoc');
//或者
import Markdoc from '@markdoc/markdoc';
然后调用 parse, transform 和 render 函数来渲染内容。
const source = '# Markdoc';
const ast = Markdoc.parse(source);
const content = Markdoc.transform(ast, /* config */);
const html = Markdoc.renderers.html(content);
目前 Markdoc 在 Github 上有 6.2k 的 star、150+ 的 fork、超过 1k 的项目依赖它,是一个值得长期关注的前端项目。
按照设计,Markdoc 不是一种成熟的模板语言,并且不允许混合任意代码和内容。 然而,它是一种完全声明的格式,从上到下都是机器可读的:它解析为可以遍历的数据结构,以支持强大的静态分析、验证和程序化内容转换。
Markdoc 渲染器解释自定义标签和节点定义,将文档数据结构转换为可渲染节点树,最终转换为所需的输出格式。 Markdoc 框架目前包括三个渲染器:
Markdoc 的 React 渲染器使在 Markdown 内容中使用 React 组件成为可能,其支持选项卡切换器和可折叠部分等交互功能。 可以实现引入对其他输出格式和客户端框架的支持的自定义渲染器。
PEG.js 是一个简单的 JavaScript 解析器生成器,可生成具有出色错误报告的快速解析器。开发者可以使用它来处理复杂的数据或计算机语言,并轻松构建转换器、解释器、编译器和其他工具。
PEG.js 具有以下显著特征:
目前 PEG.js 在 Github 上有 4.6k 的 star、450+ 的 fork、191k 的项目依赖它。可以使用下面的示例快速使用 PEG.js:
npm install -g pegjs
$ pegjs -o arithmetics-parser.js arithmetics.pegjs
Markdoc 的解析器建立在一个名为 markdown-it 的流行开源 Markdown 库之上。 Markdoc 使用 markdown-it 作为标记器,从 markdown-it 输出的标记数组构建抽象语法树 (AST)。
Markdown-it 解析器的特性包括:具有 100% CommonMark 支持、 扩展支持、语法插件、安全和极致的速度。目前 Markdown-it 在 Github 上有 15.2k 的 star、1.6k 的 fork、432k 的项目依赖它。可以使用下面的示例快速使用 markdown-it:
// node.js经典方式
var MarkdownIt = require('markdown-it'),
md = new MarkdownIt();
var result = md.render('# markdown-it rulezz!');
// node.js的语法糖
var md = require('markdown-it')();
var result = md.render('# markdown-it rulezz!');
// 没有 AMD 的浏览器,在脚本加载时添加到 window
// 注意,“markdownit”中没有破折号。
var md = window.markdownit();
var result = md.render('# markdown-it rulezz!');
Markdoc 的自定义标记语法在 markdown-it 插件中实现, 解析标记语法的逻辑是从 peg.js 语法生成的。 Markdoc 有自己专用的渲染架构,而不是依赖 markdown-it 来生成它的输出。 为了处理 Markdoc 的自定义标签和支持多种输出格式,其开发了一个独立的渲染系统。
Markdoc 选择 Markdown 作为起点,因为它易于阅读和推理,许多工程师和技术作家已经非常熟悉,并且得到众多现有工具的大型生态系统的广泛支持。 然而,Markdown 本身并不适合编写复杂的、高度结构化的内容,如文档等等。
Markdoc 提供了一个可扩展的系统,用于定义可以在 Markdown 内容中无缝使用的自定义标签。 使用自定义标记语法,开发者能够表达更精细的文档层次结构,插入交互式组件,并支持条件内容、内容包含和变量插值等功能。 Markdoc 对 Markdown 语法的扩展被设计为可组合且侵入性最小,在不影响可读性的情况下提供关键功能。
比如下面示例使用 .my-class-name 和 #my-id 作为 class=my-class-name 和 id=my-id 的简写。
# Examples {% #examples %}
{% table .striped #exampletable %}
- One
- Two
- Three
{% /table %}
Markdoc 也允许开发者为每个标签配置自定义属性类型,比如以下示例定义了 Callout 标记的属性。 默认情况下,该属性设置为注意并根据匹配数组进行验证。
{
render: 'Callout',
children: ['paragraph', 'tag', 'list'],
attributes: {
type: {
type: String,
default: 'note',
required: true,
matches: ['caution', 'check', 'note', 'warning'],
errorLevel: 'critical',
},
}
};
Markdoc 支持使用 React 开箱即用地渲染 Markdoc 语法。按照以下步骤使用 create-react-app 和 express 构建 Markdoc 应用程序。
按照 create-react-app 入门步骤创建初始应用程序
设置 Markdoc 架构
schema/
├── Callout.markdoc.js
└── heading.markdoc.js
schema/Callout.markdoc.js 的内容如下:
// schema/Callout.markdoc.js
module.exports = {
render: 'Callout',
children: ['paragraph', 'tag', 'list'],
attributes: {
type: {
type: String,
default: 'note',
matches: ['check', 'error', 'note', 'warning'],
},
},
};
schema/heading.markdoc.js 的内容如下:
// schema/heading.markdoc.js
const { nodes } = require('@markdoc/markdoc');
function generateID(children, attributes) {
if (attributes.id && typeof attributes.id === 'string') {
return attributes.id;
}
return children
.filter((child) => typeof child === 'string')
.join(' ')
.replace(/[?]/g, '')
.replace(/\s+/g, '-')
.toLowerCase();
}
module.exports = {
...nodes.heading,
transform(node, config) {
const base = nodes.heading.transform(node, config);
base.attributes.id = generateID(base.children, base.attributes);
return base;
},
};
解析服务器上的 Markdoc 文档
// ...
const rawText = fs.readFileSync(file, 'utf-8');
const ast = Markdoc.parse(rawText);
在服务端调用 Markdoc.transform
// server.js
const express = require('express');
const app = express();
const callout = require('./schema/callout.markdoc');
const heading = require('./schema/heading.markdoc');
// ...
app.get('/markdoc', (req, res) => {
const ast = contentManifest[req.query.path];
const config = {
tags: {
callout,
},
nodes: {
heading,
},
variables: {},
};
const content = Markdoc.transform(ast, config);
return res.json(content);
});
app.listen(4242, () => {
console.log(`Example app listening on port ${4242}`);
});
在客户端调用 Markdoc.renderers.react
// src/App.js
import React from 'react';
import Markdoc from '@markdoc/markdoc';
import { Callout } from './Callout';
export default function App() {
const [content, setContent] = React.useState(null);
React.useEffect(() => {
(async () => {
const response = await fetch(
`/markdoc?` + new URLSearchParams({ path: window.location.pathname }),
{ headers: { Accept: 'application/json' } }
);
if (response.status === 404) {
setContent('404');
return;
}
const content = await response.json();
setContent(content);
})();
}, []);
if (content === '404') {
return <p>Page not found.</p>;
}
if (!content) {
return <p>Loading...</p>;
}
const components = {
Callout,
};
return Markdoc.renderers.react(content, React, { components });
}
启动客户端和服务器
npm run start:client
npm run start:server
除了与React集成外,Markdoc还支持与HTML、Next.js等集成。
https://markdoc.dev/docs/overview
https://github.com/markdown-it/markdown-it
https://pegjs.org/
https://github.com/pegjs/pegjs
https://markdoc.dev/docs/attributes
https://stripe.com/blog/markdoc
https://markdoc.dev/docs/examples/react#setup
https://transloadit.com/blog/2022/06/devtimes-055/