@blocz/mdx-live
@blocz/mdx-live
allows you to dynamically render a MDX string.
It understands the import statements, and you can provide how they will get resolved.
Exports statements are also executed. WARNING: this allows XSS so be sure to be in a safe environment.
You can also provide a scope for all the variables and components used in the MDX.
MDX 2
Since the v0.2.0, it's based on MDX v2. It you want to use it with MDX v1, you can look at the v0.1.0.
If you’re looking to upgrade to the v0.2.0, the list of breaking changes is listed here.
This package requires you to also install @mdx-js/mdx
:
yarn add @mdx-js/mdx @blocz/mdx-live
ESM
Warning
This package is only published as an ESM package, it doesn't provide any CJS exports.
MDX also switched to ESM only in their v2 (see https://mdxjs.com/migrating/v2/#esm).
How to use
Simple MDX
import { MDX } from "@blocz/mdx-live";
const simpleMDX = `
### How it works
1. First item
2. Second item
---
### TL;DR
- First item
- Second item
- Nested list
- First nested \`item\`
- Second _nested_ item
- **Third** nested item
`;
const App = () => {
return <MDX code={simpleMDX} />;
};
With scope
import { MDX } from "@blocz/mdx-live";
const Button = ({ label, variant, onClick }) => (
<button data-variant={variant} onClick={onClick}>
{label}
</button>
);
const scopedMDX = `
<Button
variant="blue"
label="Click Me!"
onClick={() => alert('Hello there!')}
/>
`;
const App = () => {
return <MDX code={scopedMDX} defaultScope={{ Button }} />;
};
With export statement
import { MDX } from "@blocz/mdx-live";
const Button = ({ label, variant, onClick }) => (
<button data-variant={variant} onClick={onClick}>
{label}
</button>
);
const exportMDX = `
export const label = "Click Me!";
<Button variant="blue" label={label} />
`;
const App = () => {
return <MDX code={exportMDX} defaultScope={{ Button }} />;
};
With import statement
import { MDX } from "@blocz/mdx-live";
const Button = ({ label, variant, onClick }) => (
<button data-variant={variant} onClick={onClick}>
{label}
</button>
);
const importMDX = `
import { Button } from 'example';
<Button variant="blue" label="Click Me!" />
`;
const resolveImport = async (option) => {
if (
option.kind === "named" &&
option.path === "example" &&
option.variable === "Button"
) {
return Button;
}
return undefined;
};
const App = () => {
return <MDX code={importMDX} resolveImport={resolveImport} />;
};
export type ResolveImport = (
option:
| { kind: "named"; path: string; variable: string }
| { kind: "namespace" | "default"; path: string },
) => Promise<any>;
Plugins
You can use the props recmaPlugins
, rehypePlugins
, and remarkPlugins
to pass remark (plugins based on the markdown AST), rehype (plugins based on the html AST), and recma (plugins based on the JS AST) plugins to the MDX compiler.
See https://mdxjs.com/packages/mdx/#optionsremarkplugins for more information.
Custom renderers
If you want to use custom renderers, you’ll have to use @mdx-js/react
and the prop useMDXComponents
:
import { useMDXComponents, MDXProvider } from "@mdx-js/react";
import { MDX } from "@blocz/mdx-live";
<MDXProvider components={{ h3: () => <p>I am a custom h3</p> }}>
<MDX
useMDXComponents={useMDXComponents}
code={`
### This header will be replaced
`}
/>
</MDXProvider>;
Provider
If you need to have access to more information in a custom renderer (like for instance a custom code block renderer), you can provide a Provider
to MDX
.
Provider
will be provided an object with:
text
and isReady
, like useMDX
’s returned value,- a
scope
object, which is a merge between:
- the
defaultScope
prop, - resolved imports thanks to
resolveImport
, - exported values in the MDX.
For instance, with the following example:
<MDX
Provider={Provider}
defaultScope={{ variant: "blue" }}
code={`
import { Button } from 'example';
export const label = "Click Me!";
<Button variant={variant} label={label} />
`}
resolveImport={async () => ButtonVariable}
/>
The Provider
will be called with a scope
of:
{
Button: ButtonVariable,
label: "Click Me!",
variant: "blue",
}
useMDX
hook
Just like MDX
, the useMDX
hook accepts those arguments: code
, resolveImport
, recmaPlugins
, rehypePlugins
, remarkPlugins
.
But in addition to those, it also accepts @mdx-js/mdx
’s providerImportSource
.
import { useMDX } from "@blocz/mdx-live";
const Button = ({ label, variant, onClick }) => (
<button data-variant={variant} onClick={onClick}>
{label}
</button>
);
const importMDX = `
import { Button } from 'example';
<Button variant="blue" label="Click Me!" />
`;
const resolveImport = async (option) => {
if (
option.kind === "named" &&
option.path === "example" &&
option.variable === "Button"
) {
return Button;
}
return undefined;
};
const App = () => {
const { resolvedImports, text, isReady } = useMDX({
code: importMDX,
resolveImport,
});
};