2025年3月30日 星期日 甲辰(龙)年 月廿九 设为首页 加入收藏
rss
您当前的位置:首页 > 计算机 > 服务器 > 万维网络 > 中间件

Markdoc 新一代Markdown文档内容发布框架

时间:12-17来源:作者:点击数:51

今天给大家介绍的主题是 Markdoc,即由 Stripe 开发的一种基于 Markdown 的文档格式和内容发布框架。

Markdoc官网:https://markdoc.dev/

Markdoc 新一代Markdown文档内容发布框架

什么是 Markdoc

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 渲染器解释自定义标签和节点定义,将文档数据结构转换为可渲染节点树,最终转换为所需的输出格式。 Markdoc 框架目前包括三个渲染器:

  • 一个 HTML 字符串渲染器
  • 一个将文档转换为 JavaScript 代码的静态 React 渲染器
  • 一个将可渲染树节点直接转换为 React 元素的动态 React 渲染器

Markdoc 的 React 渲染器使在 Markdown 内容中使用 React 组件成为可能,其支持选项卡切换器和可折叠部分等交互功能。 可以实现引入对其他输出格式和客户端框架的支持的自定义渲染器。

解析器生成器 PEG.js

PEG.js 是一个简单的 JavaScript 解析器生成器,可生成具有出色错误报告的快速解析器。开发者可以使用它来处理复杂的数据或计算机语言,并轻松构建转换器、解释器、编译器和其他工具。

Markdoc 新一代Markdown文档内容发布框架

PEG.js 具有以下显著特征:

  • 简单而富有表现力的语法
  • 集成词法和句法分析
  • 解析器具有开箱即用的出色错误报告
  • 基于解析表达式语法形式主义——比传统的 LL(k) 和 LR(k) 解析器更强大
  • 可从浏览器、命令行或通过 JavaScript API 使用

目前 PEG.js 在 Github 上有 4.6k 的 star、450+ 的 fork、191k 的项目依赖它。可以使用下面的示例快速使用 PEG.js:

  • npm install -g pegjs
  • $ pegjs -o arithmetics-parser.js arithmetics.pegjs

markdown-it 的助力

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 中添加标记

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',
  • },
  • }
  • };

在 React 中使用 Markdoc

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/

方便获取更多学习、工作、生活信息请关注本站微信公众号城东书院 微信服务号城东书院 微信订阅号
推荐内容
相关内容
栏目更新
栏目热门