@diplodoc/mdx-扩展
Diplodoc的标记下变压器的MDX扩展可以将MDX/JSX组件嵌入标记下的内容.
安装
npm install @diplodoc/mdx-extension
# or
yarn add @diplodoc/mdx-extension
特征
- 无缝地将JSX/MDX组件纳入下调内容
- 支持客户端(CSR)和服务器端(SSR)渲染
- 跟踪背景变化的背景支持
- 多个语法选项 :
- 明确
<MDX>...</MDX>标记 - JSX碎片
<>...</> - 直接反应组件使用
<Component />
- 明确
- MDX 输入验证的内置安全
- 对高级组件安装的门户支持
withPortal - 通过同步组件加载支持
idMdxComponentLoader - 缺失 MDX 引用的默认组件倒计时
recmaDefaultComponent
使用量
基本设置
首先,增加一个 mdxPlugin() 到您的 Diplodoc 变换插件 :
import transform from '@diplodoc/transform';
import DefaultPlugins from '@diplodoc/transform/lib/plugins';
import {mdxPlugin} from '@diplodoc/mdx-extension';
const result = transform(markdownContent, {
plugins: [...DefaultPlugins, mdxPlugin()],
});
使用默认组件倒置
这个 recmaDefaultComponent 插件为缺失的 MDX 组件引用提供优雅回落. 它不扔出错误,而是重定向到可配置的默认组件.
import {mdxPlugin, recmaDefaultComponent} from '@diplodoc/mdx-extension';
const result = transform(markdownContent, {
plugins: [
...DefaultPlugins,
mdxPlugin({
compileOptions: {
recmaPlugins: [recmaDefaultComponent({componentName: 'DefaultComponent'})],
},
}),
],
});
示例默认组件 :
const Components = {
DefaultComponent: ({tagName, ...props}) => (
<div style={{border: '1px dashed red'}}>Missing component: {tagName}</div>
),
};
启用 MDX 输入验证
这个 validateMdx 插件为 矩形插件 (Rhype Compatible MDAST to JavaScript AST 编译器)通过分析生成的抽象语法树(AST)来验证MDX内容的安全性. 它通过限制允许的JavaScript构造和模式来阻止潜在不安全代码的执行.
启用 MDX 输入的安全验证 :
import {mdxPlugin, validateMdx} from '@diplodoc/mdx-extension';
const result = transform(markdownContent, {
plugins: [
...DefaultPlugins,
mdxPlugin({
compileOptions: {
recmaPlugins: [validateMdx],
},
}),
],
});
插件验证
这个 validateMdx 插件通过 :
-
限制 JSX 道具值 只允许:
- 字数(字符串、数字、布尔)
- 标识符
- 阵列表达式( 有有效的元素)
- 对象表达式( 具有有效属性)
- 二进制表达式( 有有效的操作)
- JSX的具体呼吁
_jsxs, (中文(简体) ),_jsx(中文(简体) )
-
封锁危险道具 包括:
dangerouslySetInnerHTMLref- 事件处理器(从
on[A-Z](中文(简体) ) - 在不安全的 URL 协议中
href, (中文(简体) ),src, (中文(简体) ),action道具(块)javascript:财务报告和审定财务报表data:text/html(中文(简体) )
-
限制组件类型 仅改为:
- 已建MDX组件(
_Fragment, (中文(简体) ),MDXLayout, (中文(简体) ),_createMdxContent(中文(简体) ) - 来自
_components对象 - 文体组件名称
- 已建MDX组件(
-
取消不安全的 JavaScript 构造数字 :
- 函数声明(特定 MDX 函数除外)
- 可变声明(具体模式除外)
- 成员表达式(具体模式除外)
- 调用表达式( 特定函数除外)
- 新表达式( 只有
Error允许) - 散开元素( 仅特定模式)
- 逻辑表达式( 只有特定模式)
- Async/await、出产、转让、导入和函数表达式
-
执行深度限制 (最大深度50)防止过于复杂的AST
为什么使用这个插件?
- 警卫: 防止在 MDX 内容中执行任意 JavaScript 代码
- 服务器侧保护: 屏蔽安全代码, 可以在 SSR 期间执行
- 受控制环境:确保只允许批准的模式
- 早期错误检测: 在执行前抛出可疑模式的验证错误
校验错误
当插件检测出无效的图案时,它会抛出描述性错误,例如:
- 不可允许“ 危险” 的组件道具
- 这里不允许“ 组件标签类型... ”
- "功能宣言在这里是不允许的"
- "最大深度超过"
客户端渲染( CSR)
import React, {Fragment, useMemo, useRef} from 'react';
import transform from '@diplodoc/transform';
import DefaultPlugins from '@diplodoc/transform/lib/plugins';
import {mdxPlugin, useMdx, isWithMdxArtifacts, validateMdx} from '@diplodoc/mdx-extension';
const Components = {
CustomComponent: (props) => <div {...props}>Custom</div>,
};
const CONTENT = `
# Markdown Content
<CustomComponent style={{color: 'red'}} />
<MDX>
<div>This will be rendered as MDX</div>
</MDX>
`;
function App() {
const ref = useRef(null);
const {html, mdxArtifacts} = useMemo(() => {
const {result} = transform(CONTENT, {
plugins: [
...DefaultPlugins,
mdxPlugin({
compileOptions: {
recmaPlugins: [validateMdx],
},
}),
],
});
isWithMdxArtifacts(result);
return result;
}, []);
const portals = useMdx({
refCtr: ref,
html,
components: Components,
mdxArtifacts,
});
return (
<Fragment>
<div ref={ref}></div>
{portals}
</Fragment>
);
}
服务器端渲染( SSR)
import React from 'react';
import transform from '@diplodoc/transform';
import DefaultPlugins from '@diplodoc/transform/lib/plugins';
import {mdxPlugin, useMdxSsr, getSsrRenderer, validateMdx} from '@diplodoc/mdx-extension';
const Components = {
ServerComponent: (props) => <strong {...props}>Server Rendered</strong>,
};
const CONTENT = `
# Server Rendered Content
<ServerComponent />
`;
export async function getServerSideProps() {
const render = await getSsrRenderer({
components: Components,
compileOptions: {
recmaPlugins: [validateMdx],
},
});
const {result} = transform(CONTENT, {
plugins: [...DefaultPlugins, mdxPlugin({render})],
});
isWithMdxArtifacts(result);
const {html, mdxArtifacts} = result;
return {props: {html, mdxArtifacts}};
}
function ServerPage({html, mdxArtifacts}) {
const ref = useRef(null);
const portals = useMdxSsr({
refCtr: ref,
components: Components,
mdxArtifacts,
html,
});
const innerHtml = useMemo(() => {
return {__html: html};
}, [html]);
return (
<Fragment>
<div ref={ref} dangerouslySetInnerHTML={innerHtml}></div>
{portals}
</Fragment>
);
}
收集插件
收集插件在收集文物的同时提供处理和转换MDX内容的功能. 它同时以同步和同步两种版本出现.
同步收集插件
import {getMdxCollectPlugin} from '@diplodoc/mdx-extension';
const plugin = getMdxCollectPlugin({
tagNames: ['CustomComponent'], // Optional filter for specific tags
pureComponents: PURE_COMPONENTS,
compileOptions: {
// MDX compilation options
},
});
const transformedContent = plugin(originalContent);
一个同步收集插件
import {getAsyncMdxCollectPlugin} from '@diplodoc/mdx-extension';
const asyncPlugin = getAsyncMdxCollectPlugin({
tagNames: ['AsyncComponent'], // Optional filter for specific tags
pureComponents: PURE_COMPONENTS,
compileOptions: {
// MDX compilation options
},
});
const transformedContent = await asyncPlugin(originalContent);
占位符渲染器
占位器渲染器为MDX内容提供了两相渲染方法. 这对于您需要将解析与执行区分开来, 如延迟渲染, SSR 组件等, 或当您要在多个通道中处理 MDX 内容时, 都是有益的 .
如何运作
-
占位符生成: 在初始MDX处理过程中
getPlaceholderRenderer函数创建带有唯一ID的占位符 HTML 元素,并存储 MDX 的原始内容mdxArtifacts.idFragment。 。 。 . -
占位符渲染: 后来
createPlaceholderRender或者说createAsyncPlaceholderRender函数处理这些占位符,用实际制作的 HTML 取而代之,并将汇编的代码存储在idMdx财务报告和审定财务报表idTagName用于客户端水分.
使用量
import {getPlaceholderRenderer, createPlaceholderRender} from '@diplodoc/mdx-extension';
// Create a placeholder renderer
const placeholderRenderer = getPlaceholderRenderer();
// Use it in mdxPlugin
const {result} = transform(content, {
plugins: [
...DefaultPlugins,
mdxPlugin({
render: placeholderRenderer,
}),
],
});
// Later, render the placeholders
const render = createPlaceholderRender({
components: YOUR_COMPONENTS,
// ... other options
});
const finalHtml = render(result.html, result.mdxArtifacts);
同步占位符渲染
用于同步渲染(例如,组件具有 withInitialProps),使用同步版本:
import {createAsyncPlaceholderRender} from '@diplodoc/mdx-extension';
const asyncRender = createAsyncPlaceholderRender({
components: YOUR_COMPONENTS,
// ... other options
});
const finalHtml = await asyncRender(result.html, result.mdxArtifacts);
Raw MDX 内容插件
这个 remarkRawMdxContent 插件是一个注释插件,在指定的组件内取出原始的MDX内容并保存为文本,防止被解析为MDX. 这对需要输出原始MDX/JSX作为文本内容的组件(例如代码示例,文档生成器)有用.
如何运作
插件处理 MDX JSX 元素并针对指定的标签名,取出打开和关闭标签之间的原始文本内容. 然后用一个包含该原始内容的单一文本节点来取而代之,保留了原始格式.
使用量
import {remarkRawMdxContent} from '@diplodoc/mdx-extension';
import {unified} from 'unified';
import remarkParse from 'remark-parse';
import remarkMdx from 'remark-mdx';
const processor = unified()
.use(remarkParse)
.use(remarkMdx)
.use(remarkRawMdxContent, {
tagNames: ['CodeExample', 'RawContent'], // Specify which tags to process
});
const tree = processor.parse(`# Example
<CodeExample>
<Button>Click me</Button>
</CodeExample>`);
const transformedTree = processor.runSync(tree);
与 Diplodoc 变形合并
您可以使用插件作为您的 Diplodoc 变换管道的一部分 :
import transform from '@diplodoc/transform';
import DefaultPlugins from '@diplodoc/transform/lib/plugins';
import {mdxPlugin, remarkRawMdxContent} from '@diplodoc/mdx-extension';
const result = transform(content, {
plugins: [
...DefaultPlugins,
mdxPlugin({
compileOptions: {
remarkPlugins: [[remarkRawMdxContent, {tagNames: ['CodeExample']}]],
},
}),
],
});
这将确保内部内容 <CodeExample> 标记保存为原始文本,而不是解析为MDX.
API 参考
mdxPlugin(options?: { render?: MDXRenderer })
允许MDX处理的主要插件功能.
选项 :
render: 可选的渲染器功能,用于SSDgetSsrRenderertagNames?: string[]- 可选的标签名称阵列以过滤将处理哪些组件
useMdx(options: UseMdxProps): React.Fragment
用于客户端MDX处理的反应钩.
选项 :
refCtr: 参考容器元素html: 来自 Diplodoc 转换的 HTML 字符串components: 要使用的反应组件对象mdxArtifacts: MDX 变形器pureComponents?: 不应被水合物(MDX Contiments)组件的可选对象contextList?: 向 MDX 组件提供反射语境阵列idMdxComponentLoader?: 自定义 mdx 组件加载器
useMdxSsr(options: UseMdxSsrProps): React.Fragment
SSR处理过的MDX内容的回放钩.
选项 :
refCtr: 参考容器元素html: 来自 Diplodoc 转换的 HTML 字符串components: 要使用的反应组件对象mdxArtifacts: MDX 变形器pureComponents?: 不应被水合物(MDX Contiments)组件的可选对象contextList?: 向 MDX 组件提供反射语境阵列idMdxComponentLoader?: 自定义 mdx 组件加载器
getRenderer(options: GetRenderProps)
为客户端处理创建渲染器功能.
选项 :
compileOptions?: MDX 编译选项(参见 MDX 文档(中文(简体) )
getSsrRenderer(options: GetSsrRendererProps)
创建用于服务器侧处理的 SSR 渲染器功能.
选项 :
components: 要使用的反应组件对象pureComponents?: 不应被水合物(MDX Contiments)组件的可选对象compileOptions?: MDX 编译选项(参见 MDX 文档(中文(简体) )contextList?: 向 MDX 组件提供反应上下文阵列。 使用{ ctx, initValue }通过 SSR 初始值的格式
getAsyncSsrRenderer(options: GetAsyncSsrRendererProps)
创建支持的同步 SSR 渲染器 withInitialProps。 。 。 .
选项 :
components: 要使用的反应组件对象pureComponents?: 不应被水合物(MDX Contiments)组件的可选对象compileOptions?: MDX 编译选项(参见 MDX 文档(中文(简体) )contextList?: 向 MDX 组件提供反应上下文阵列。 使用{ ctx, initValue }通过 SSR 初始值的格式
getMdxCollectPlugin(options: Options)
创建用于处理 MDX 内容的同步收集插件.
选项 :
tagNames?: string[]- 可选的标签名称阵列以过滤处理pureComponents?: 应跳过客户端水合的组件compileOptions?: MDX 编译选项
getAsyncMdxCollectPlugin(options: AsyncOptions)
创建一个同步收集插件,支持带有初始道具的组件.
选项 :
tagNames?: string[]- 可选的标签名称阵列以过滤处理pureComponents?: 应跳过客户端水合的组件compileOptions?: MDX 编译选项
州管理背景
MdxStateCtx: Context<MdxStateCtxValue>
提供对当前 MDX 状态的访问 :
const state = useContext(MdxStateCtx);
MdxSetStateCtx: Context<MdxSetStateCtxValue>
提供状态设置函数( 只在 SSR 期间可用) :
const setState = useContext(MdxSetStateCtx);
// Usage in SSR:
setState?.({key: value});
组件增强器
withInitialProps: WithInitialProps
环绕一个组件,以便在 SSR 期间获取初始道具.
参数:
component: 反射组件来包接getInitProps: 接收道具和MDX状态的函数,返回道具(同步或sync)
withPortal: WithPortalProps
通过 React.createPortal 包成组件,允许更灵活的挂起.
参数:
component: 反射组件来包接fallback: 可选倒置组件在挂载门户前显示
用法 :
export const COMPONENTS = {
Tabs: withPortal(TabsLocal, () => <Skeleton />),
};
使用时 withPortal本构成部分将:
- 初始后退组件(如果提供)
- 准备时创建挂载实际组件的门户
- 卸载时清理门户
语法示例
明确的 MDX 标记
<MDX>
<MyComponent prop="value" />
</MDX>
JSX 碎片
<>
<div>Fragment content</div>
</>
直接组件使用
<Button onClick={() => console.log('click')}>
Click me
</Button>
高级特性
安全部门改革国家管理
库为服务器-Side渲染(SSR)期间管理状态提供两个上下文提供者:
-
MdxSetStateCtx- 提供更新MDX状态功能的背景。 此功能仅在SSD期间可用(null在客户端。 如果您使用此上下文设置了组件状态,则会是:- 序列化为
data-mdx-statesSR 期间属性 - 备查
MdxStateCtx当组件生成时
- 序列化为
-
MdxStateCtx- 提供一个访问当前 MDX 状态值的上下文
与初始道具同步 SSR
-
withInitialProps- 能够同步获取安全部门改革数据的更高级组件:- 将一个组件与此函数并用时
getAsyncSsrRenderer,则getInitialProps函数将调用 - 接收组件道具和 MDX 状态作为参数
- 可以返回静态或基于承诺的道具
- 将一个组件与此函数并用时
-
getAsyncSsrRenderer- 一个同步的版本getSsrRenderer即:- 支持用包起来的组件
withInitialProps - 允许在 SSR 期间获取同步数据
- 支持用包起来的组件
示例用法:
const getInitialProps: MDXGetInitialProps<CounterProps> = (props, mdxState) => {
mdxState.initialValue = 10; // Set initial state
return props;
};
export const SSR_COMPONENTS = {
...COMPONENTS,
Counter: withInitialProps(Counter, getInitialProps),
};
纯元件
库支持纯组件:
- 只在安全部门改革期间做过一次
- 客户端跳过水合
- 可通过
pureComponents选项useMdxuseMdxSsrgetSsrRenderergetAsyncSsrRenderer
示例
export const PURE_COMPONENTS = {
KatexFormula, // Will render once on server and not hydrate
Label, // on client
CompatTable,
Alert,
};
同步组件加载
这个 useMdx 财务报告和审定财务报表 useMdxSsr 钩子支持可选 idMdxComponentLoader 能够同步加载 MDX 组件的参数:
interface PageProps {
html: string;
mdxArtifacts?: MdxArtifacts;
withLoader?: boolean;
}
const Page: FC<PageProps> = ({html, mdxArtifacts}) => {
const [isSuccess, setSuccess] = React.useState(false);
const [data, setData] = React.useState<IdMdxComponentLoader['data']>(undefined);
useMdxSsr({
// ...other options
idMdxComponentLoader: {isSuccess, data},
});
useEffect(() => {
(async () => {
const idMdxComponent: Record<string, React.ComponentType<MDXProps>> = {};
for (const [artifactId, code] of Object.entries(mdxArtifacts?.idMdx ?? {})) {
const fn = await asyncExecuteCode(code);
idMdxComponent[artifactId] = fn(runtime).default;
}
setData(idMdxComponent);
setSuccess(true);
})();
}, [mdxArtifacts]);
// ...rest of the component
};
汇编选项
所有渲染器的功能( A)getSsrRenderer, (中文(简体) ), getAsyncSsrRenderer, (中文(简体) ), getRenderer)接受可选的MDX编译选项:
const renderer = await getAsyncSsrRenderer({
components: SSR_COMPONENTS,
pureComponents: PURE_COMPONENTS,
compileOptions: {
// MDX compilation options here
},
});
这允许在保持库核心功能的同时,对MDX编译过程进行精细控制.
许可证
麻省理工学院