@diplodoc/mdx-extension
v1.7.1
Published
[](https://www.npmjs.com/package/@diplodoc/mdx-extension) [](https://opensource.org/licenses/MIT)
Downloads
473
Keywords
Readme
@diplodoc/mdx-extension
MDX extension for Diplodoc's markdown transformer that allows embedding MDX/JSX components within markdown content.
Installation
npm install @diplodoc/mdx-extension
# or
yarn add @diplodoc/mdx-extensionFeatures
- Seamlessly integrate JSX/MDX components within markdown content
- Support for both client-side (CSR) and server-side (SSR) rendering
- Context support with tracking of context changes
- Multiple syntax options:
- Explicit
<MDX>...</MDX>tags - Short form JSX fragments
<>...</> - Direct React component usage
<Component />
- Explicit
- Built-in security with MDX input validation
- Portal support for advanced component mounting with
withPortal - Asynchronous component loading support via
idMdxComponentLoader
Usage
Basic Setup
First, add the mdxPlugin() to your Diplodoc transform plugins:
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()],
});Enabling MDX Input Validation
To enable security validation of MDX input and prevent execution of potentially unsafe code:
import {mdxPlugin, validateMdx} from '@diplodoc/mdx-extension';
const result = transform(markdownContent, {
plugins: [
...DefaultPlugins,
mdxPlugin({
compileOptions: {
recmaPlugins: [validateMdx],
},
}),
],
});This will:
- Validate all user-provided MDX/JSX content
- Prevent execution of unsafe code on the server
- Throw validation errors for suspicious patterns
Client-side Rendering (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>
);
}Server-side Rendering (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>
);
}Collect Plugin
The collect plugin provides functionality to process and transform MDX content while collecting artifacts. It comes in both synchronous and asynchronous versions.
Synchronous Collect Plugin
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);Asynchronous Collect Plugin
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);API Reference
mdxPlugin(options?: { render?: MDXRenderer })
The main plugin function that enables MDX processing.
Options:
render: Optional renderer function, for SSR usegetSsrRenderertagNames?: string[]- Optional array of tag names to filter which components will be processed
useMdx(options: UseMdxProps): React.Fragment
React hook for client-side MDX processing.
Options:
refCtr: Ref to the container elementhtml: HTML string from Diplodoc transformcomponents: Object of React components to usemdxArtifacts: MDX artifacts from transformpureComponents?: Optional object of components that shouldn't hydrate (MDXComponents)contextList?: Array of React contexts to provide to MDX componentsidMdxComponentLoader?: Custom mdx component loader
useMdxSsr(options: UseMdxSsrProps): React.Fragment
React hook for SSR-processed MDX content.
Options:
refCtr: Ref to the container elementhtml: HTML string from Diplodoc transformcomponents: Object of React components to usemdxArtifacts: MDX artifacts from transformpureComponents?: Optional object of components that shouldn't hydrate (MDXComponents)contextList?: Array of React contexts to provide to MDX componentsidMdxComponentLoader?: Custom mdx component loader
getRenderer(options: GetRenderProps)
Creates an renderer function for client-side processing.
Options:
compileOptions?: MDX compilation options (see MDX documentation)
getSsrRenderer(options: GetSsrRendererProps)
Creates an SSR renderer function for server-side processing.
Options:
components: Object of React components to usepureComponents?: Optional object of components that shouldn't hydrate (MDXComponents)compileOptions?: MDX compilation options (see MDX documentation)contextList?: Array of React contexts to provide to MDX components. Use{ ctx, initValue }format to pass initial values for SSR
getAsyncSsrRenderer(options: GetAsyncSsrRendererProps)
Creates an asynchronous SSR renderer that supports withInitialProps.
Options:
components: Object of React components to usepureComponents?: Optional object of components that shouldn't hydrate (MDXComponents)compileOptions?: MDX compilation options (see MDX documentation)contextList?: Array of React contexts to provide to MDX components. Use{ ctx, initValue }format to pass initial values for SSR
getMdxCollectPlugin(options: Options)
Creates a synchronous collect plugin for processing MDX content.
Options:
tagNames?: string[]- Optional array of tag names to filter processingpureComponents?: Components that should skip client-side hydrationcompileOptions?: MDX compilation options
getAsyncMdxCollectPlugin(options: AsyncOptions)
Creates an asynchronous collect plugin that supports components with initial props.
Options:
tagNames?: string[]- Optional array of tag names to filter processingpureComponents?: Components that should skip client-side hydrationcompileOptions?: MDX compilation options
State Management Contexts
MdxStateCtx: Context<MdxStateCtxValue>
Provides access to the current MDX state:
const state = useContext(MdxStateCtx);MdxSetStateCtx: Context<MdxSetStateCtxValue>
Provides state setter function (only available during SSR):
const setState = useContext(MdxSetStateCtx);
// Usage in SSR:
setState?.({key: value});Component Enhancers
withInitialProps: WithInitialProps
Wraps a component to enable initial props fetching during SSR.
Parameters:
component: React component to wrapgetInitProps: Function that receives props and MDX state, returns props (sync or async)
withPortal: WithPortalProps
Wraps a component to render it through React.createPortal, allowing for more flexible mounting.
Parameters:
component: React component to wrapfallback: Optional fallback component to show before portal is mounted
Usage:
export const COMPONENTS = {
Tabs: withPortal(TabsLocal, () => <Skeleton />),
};When using withPortal, the component will:
- Render the fallback component (if provided) initially
- Create a portal to mount the actual component when ready
- Clean up the portal when unmounted
Syntax Examples
Explicit MDX tags
<MDX>
<MyComponent prop="value" />
</MDX>JSX Fragments
<>
<div>Fragment content</div>
</>Direct Component Usage
<Button onClick={() => console.log('click')}>
Click me
</Button>Advanced Features
State Management in SSR
The library provides two context providers for managing state during Server-Side Rendering (SSR):
MdxSetStateCtx- A context that provides a function to update the MDX state. This function is only available during SSR (nullon client-side). If you set a component's state using this context, it will be:- Serialized into the
data-mdx-stateattribute during SSR - Available in
MdxStateCtxwhen the component renders
- Serialized into the
MdxStateCtx- A context that provides access to the current MDX state value
Asynchronous SSR with Initial Props
withInitialProps- A higher-order component that enables asynchronous data fetching for SSR:- When wrapping a component with this function and using
getAsyncSsrRenderer, thegetInitialPropsfunction will be called - Receives the component's props and MDX state as arguments
- Can return either static or promise-based props
- When wrapping a component with this function and using
getAsyncSsrRenderer- An asynchronous version ofgetSsrRendererthat:- Supports components wrapped with
withInitialProps - Enables async data fetching during SSR
- Supports components wrapped with
Example usage:
const getInitialProps: MDXGetInitialProps<CounterProps> = (props, mdxState) => {
mdxState.initialValue = 10; // Set initial state
return props;
};
export const SSR_COMPONENTS = {
...COMPONENTS,
Counter: withInitialProps(Counter, getInitialProps),
};Pure Components
The library supports pure components that:
- Are only rendered once during SSR
- Skip hydration on the client side
- Can be specified via the
pureComponentsoption in:useMdxuseMdxSsrgetSsrRenderergetAsyncSsrRenderer
Example:
export const PURE_COMPONENTS = {
KatexFormula, // Will render once on server and not hydrate
Label, // on client
CompatTable,
Alert,
};Asynchronous Component Loading
The useMdx and useMdxSsr hooks support an optional idMdxComponentLoader parameter that enables asynchronous loading of MDX components:
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
};Compilation Options
All renderer functions (getSsrRenderer, getAsyncSsrRenderer, getRenderer) accept optional MDX compilation options:
const renderer = await getAsyncSsrRenderer({
components: SSR_COMPONENTS,
pureComponents: PURE_COMPONENTS,
compileOptions: {
// MDX compilation options here
},
});This allows for fine-grained control over the MDX compilation process while maintaining the library's core functionality.
License
MIT
