Skip to content

diplodoc-platform/mdx-extension

Repository files navigation

@diplodoc/mdx-extension

npm version License

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-extension

Features

  • Seamlessly integrate JSX/MDX components within markdown content
  • Support for both client-side (CSR) and server-side (SSR) rendering
  • Multiple syntax options:
    • Explicit <MDX>...</MDX> tags
    • Short form JSX fragments <>...</>
    • Direct React component usage <Component />

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()],
});

Client-side Rendering (CSR)

import React, {useMemo, useRef} from 'react';
import transform from '@diplodoc/transform';
import DefaultPlugins from '@diplodoc/transform/lib/plugins';
import {mdxPlugin, useMdx, isWithMdxArtifacts} 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()],
    });

    isWithMdxArtifacts(result);

    return result;
  }, []);

  useMdx({
    refCtr: ref,
    html,
    components: Components,
    mdxArtifacts,
  });

  return <div ref={ref}></div>;
}

Server-side Rendering (SSR)

import React from 'react';
import transform from '@diplodoc/transform';
import DefaultPlugins from '@diplodoc/transform/lib/plugins';
import {mdxPlugin, useMdxSsr, getSsrRenderer} 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,
  });

  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);

  useMdxSsr({
    refCtr: ref,
    components: Components,
    mdxArtifacts,
    html,
  });

  const innerHtml = useMemo(() => {
    return {__html: html};
  }, [html]);

  return <div ref={ref} dangerouslySetInnerHTML={innerHtml}></div>;
}

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 use getSsrRenderer
  • tagNames?: string[] - Optional array of tag names to filter which components will be processed

useMdx(options: UseMdxProps)

React hook for client-side MDX processing.

Options:

  • refCtr: Ref to the container element
  • html: HTML string from Diplodoc transform
  • components: Object of React components to use
  • mdxArtifacts: MDX artifacts from transform
  • pureComponents?: Optional object of components that shouldn't hydrate (MDXComponents)

useMdxSsr(options: UseMdxSsrProps)

React hook for SSR-processed MDX content.

Options:

  • refCtr: Ref to the container element
  • html: HTML string from Diplodoc transform
  • components: Object of React components to use
  • mdxArtifacts: MDX artifacts from transform
  • pureComponents?: Optional object of components that shouldn't hydrate (MDXComponents)

getRenderer(options: GetRenderProps)

Creates an renderer function for client-side processing.

Options:

getSsrRenderer(options: GetSsrRendererProps)

Creates an SSR renderer function for server-side processing.

Options:

  • components: Object of React components to use
  • pureComponents?: Optional object of components that shouldn't hydrate (MDXComponents)
  • compileOptions?: MDX compilation options (see MDX documentation)

getAsyncSsrRenderer(options: GetAsyncSsrRendererProps)

Creates an asynchronous SSR renderer that supports withInitialProps.

Options:

  • components: Object of React components to use
  • pureComponents?: Optional object of components that shouldn't hydrate (MDXComponents)
  • compileOptions?: MDX compilation options (see MDX documentation)

getMdxCollectPlugin(options: Options)

Creates a synchronous collect plugin for processing MDX content.

Options:

  • tagNames?: string[] - Optional array of tag names to filter processing
  • pureComponents?: Components that should skip client-side hydration
  • compileOptions?: 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 processing
  • pureComponents?: Components that should skip client-side hydration
  • compileOptions?: 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 wrap
  • getInitProps: Function that receives props and MDX state, returns props (sync or async)

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 (null on client-side). If you set a component's state using this context, it will be:

    • Serialized into the data-mdx-state attribute during SSR
    • Available in MdxStateCtx when the component renders
  • 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, the getInitialProps function will be called
    • Receives the component's props and MDX state as arguments
    • Can return either static or promise-based props
  • getAsyncSsrRenderer - An asynchronous version of getSsrRenderer that:

    • Supports components wrapped with withInitialProps
    • Enables async data fetching during SSR

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 pureComponents option in:
    • useMdx
    • useMdxSsr
    • getSsrRenderer
    • getAsyncSsrRenderer

Example:

export const PURE_COMPONENTS = {
  KatexFormula, // Will render once on server and not hydrate
  Label, // on client
  CompatTable,
  Alert,
};

Compilation Options

All renderer functions (getSsrRenderer, getAsyncSsrRenderer, getRenderer) now 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

About

No description, website, or topics provided.

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Contributors 2

  •  
  •