@mdx-js/react vs @mdx-js/mdx
Architecting MDX Rendering: Compilation vs. Runtime Integration
@mdx-js/react@mdx-js/mdx类似的npm包:

Architecting MDX Rendering: Compilation vs. Runtime Integration

@mdx-js/mdx and @mdx-js/react are the two core pillars of the MDX ecosystem, serving distinct but complementary roles in the content rendering pipeline. @mdx-js/mdx is the compiler responsible for transforming Markdown syntax mixed with JSX into valid JavaScript code (usually ES modules) that can be executed by a runtime. It handles parsing, syntax transformation, and plugin integration during the build step. @mdx-js/react, on the other hand, is the runtime library that provides the React components necessary to render the compiled output. It includes the MDXProvider for context management and default mappings for HTML elements to React components, ensuring the compiled JavaScript renders correctly within a React application.

npm下载趋势

3 年

GitHub Stars 排名

统计详情

npm包名称
下载量
Stars
大小
Issues
发布时间
License
@mdx-js/react30,804,29119,84014.4 kB211 年前MIT
@mdx-js/mdx15,916,43519,840164 kB211 年前MIT

Architecting MDX Rendering: Compilation vs. Runtime Integration

When integrating Markdown with JSX in a React ecosystem, developers often encounter two distinct packages: @mdx-js/mdx and @mdx-js/react. While they share the "MDX" name and work together seamlessly, they solve completely different problems in the software lifecycle. One operates at build time to translate syntax, while the other operates at runtime to render components. Understanding this separation is critical for setting up robust content systems, custom frameworks, or advanced documentation sites.

🛠️ Core Responsibility: Compiler vs. Renderer

The fundamental difference lies in their job description. @mdx-js/mdx is a compiler. It takes raw text (Markdown + JSX) and outputs JavaScript code. It does not know about React specifically; it just generates standard ES modules that export a function. @mdx-js/react is a runtime library. It provides the specific React logic needed to take that generated JavaScript function and turn it into visible UI elements in the browser.

@mdx-js/mdx runs during your build process (Node.js environment). It parses the file, applies plugins, and emits code.

// @mdx-js/mdx usage (typically in a build script or loader)
import { compile } from '@mdx-js/mdx';

const mdxContent = '# Hello\n\n<Counter />';

// Compiles raw string into JavaScript code string
const result = await compile(mdxContent, {
  outputFormat: 'function-body',
  development: false
});

console.log(result.value); 
// Output: "function _createMdxContent(props) { ... }"

@mdx-js/react runs in the browser (or server-side rendering context). It supplies the components that the compiled code calls.

// @mdx-js/react usage (in your React App)
import { MDXProvider } from '@mdx-js/react';
import { CompiledMdxComponent } from './generated-file'; // Output from compiler

const components = {
  h1: (props) => <h1 style={{ color: 'tomato' }} {...props} />
};

export default function App() {
  return (
    <MDXProvider components={components}>
      <CompiledMdxComponent />
    </MDXProvider>
  );
}

⚙️ Configuration and Plugin Ecosystem

Configuring how content is processed happens entirely in @mdx-js/mdx. This is where you attach remark plugins (for Markdown analysis) and rehype plugins (for HTML manipulation). If you need to automatically add anchors to headings, transform images, or validate syntax, you do it here. @mdx-js/react has no knowledge of these plugins; it simply receives the final result.

@mdx-js/mdx accepts a rich configuration object to define transformations.

// @mdx-js/mdx: Configuring plugins for build-time transformation
import { compile } from '@mdx-js/mdx';
import remarkGfm from 'remark-gfm';
import rehypeSlug from 'rehype-slug';

await compile(fileContent, {
  remarkPlugins: [remarkGfm], // Enables tables, strikethrough, etc.
  rehypePlugins: [rehypeSlug], // Adds ID attributes to headings
  providerImportSource: '@mdx-js/react' // Tells compiler where to import runtime helpers
});

@mdx-js/react does not accept plugin configurations. Its "configuration" is purely the React Context provided via MDXProvider, which dictates how the final components behave visually.

// @mdx-js/react: No plugin config, only component mapping
import { MDXProvider } from '@mdx-js/react';

// You configure behavior by swapping components, not by passing plugins
const CustomWrapper = ({ children }) => <div className="prose">{children}</div>;

export function Layout({ children }) {
  return (
    <MDXProvider components={{ wrapper: CustomWrapper }}>
      {children}
    </MDXProvider>
  );
}

🔄 Data Flow: From String to Screen

To visualize the data flow, imagine a pipeline. The raw .mdx file enters @mdx-js/mdx. The package outputs a .js file. This .js file is then bundled by your tool (Vite, Webpack, etc.) and imported into your application. Once imported, the code executes and relies on @mdx-js/react to resolve the JSX tags.

If you try to use @mdx-js/mdx in the browser to compile content on the fly, you will face significant performance penalties and large bundle sizes because you are shipping a parser to the client. Conversely, if you try to render compiled MDX without @mdx-js/react, you will get errors about missing components like MDXProvider or useMDXComponents.

@mdx-js/mdx generates code that explicitly imports helpers.

// Output generated by @mdx-js/mdx
import { jsx as _jsx } from 'react/jsx-runtime';
import { useMDXComponents as _provideComponents } from '@mdx-js/react'; // Runtime dependency

export function Content(props) {
  const _components = _provideComponents();
  return _jsx(_components.h1, { children: "Hello World" });
}

@mdx-js/react exports the hooks and providers that satisfy those imports.

// Internal logic simplified from @mdx-js/react
import { useContext, createContext } from 'react';

const MDXContext = createContext({});

export function useMDXComponents(components) {
  // Logic to merge user components with defaults
  return { ...useContext(MDXContext), ...components };
}

export function MDXProvider({ components, children }) {
  return <MDXContext.Provider value={components}>{children}</MDXContext.Provider>;
}

🌐 Deployment and Environment Constraints

Your choice of environment dictates which package is active. @mdx-js/mdx is strictly a Node.js tool. It relies on file system access and heavy parsing libraries that are unsuitable for client-side execution in production. @mdx-js/react is designed for the frontend (React DOM, React Native, or Server Components) and must be included in your final bundle.

@mdx-js/mdx should never be imported in a client-side component unless you are building a specific "live editor" tool and accept the performance cost.

// ❌ Bad Practice: Importing compiler in a client component
import { compile } from '@mdx-js/mdx'; 
// This bloats your bundle with parser logic unnecessary for rendering

// ✅ Good Practice: Using the compiler in a build script (build.mjs)
import { compile } from '@mdx-js/mdx';
// Runs once during deployment to generate static JS files

@mdx-js/react is safe and required for client-side rendering.

// ✅ Correct: Importing runtime in components
import { MDXProvider } from '@mdx-js/react';

export default function BlogPost({ content: Content }) {
  return (
    <article>
      <MDXProvider>
        <Content />
      </MDXProvider>
    </article>
  );
}

🤝 Similarities: Shared Goals

Despite their different roles, both packages are maintained by the same core team and follow strict semantic versioning together. They both aim to decouple content authoring from presentation logic.

1. 🔗 Tight Version Coupling

  • Both packages must be on compatible major versions (e.g., v2 with v2, v3 with v3).
  • Mismatched versions often lead to runtime errors regarding missing exports or changed function signatures.
// package.json
{
  "dependencies": {
    "@mdx-js/mdx": "^3.0.0",
    "@mdx-js/react": "^3.0.0" // Must match major version
  }
}

2. 🧩 Component Customization

  • Both facilitate the goal of overriding default HTML elements.
  • The compiler allows setting the import source, while the runtime allows providing the actual components.
// Compiler setting the stage
await compile(content, { providerImportSource: '@mdx-js/react' });
// Runtime delivering the actors
<MDXProvider components={{ p: ParagraphComponent }} />

3. 📝 Standard Compliance

  • Both adhere to the CommonMark specification for Markdown and standard JSX syntax.
  • They support modern ECMAScript features and tree-shaking to ensure efficient bundles.
// Both support modern import/export syntax
import { compile } from '@mdx-js/mdx'; // ESM
import { MDXProvider } from '@mdx-js/react'; // ESM

📊 Summary: Key Differences

Feature@mdx-js/mdx@mdx-js/react
Primary RoleCompiler (Text → JS)Runtime (JS → UI)
Execution EnvNode.js (Build Time)Browser/Server (Run Time)
DependenciesRemark, Rehype, AcornReact, React-JSX-Runtime
OutputJavaScript Code StringReact Components/Context
ConfigurationPlugins (Remark/Rehype)Component Mapping (Props)
Bundle ImpactDev Dependency (Not shipped)Prod Dependency (Shipped)

💡 The Big Picture

Think of @mdx-js/mdx as the translator. It takes the author's mixed language of Markdown and JSX and translates it into pure JavaScript that the computer can execute. You interact with it when you set up your build pipeline, configure Vite/Webpack loaders, or write scripts to generate pages.

Think of @mdx-js/react as the actor. It takes the script produced by the translator and performs it on the stage (the DOM). You interact with it in your React components to ensure the content looks right, applies your theme, and handles interactive elements.

Final Thought: In 95% of projects using frameworks like Next.js or Gatsby, you won't import @mdx-js/mdx directly because the framework handles the compilation step for you. However, you will almost always need @mdx-js/react in your package.json and your code to render the content. If you are building a custom engine or a live playground, you will need to orchestrate both: using the compiler to generate code on the server and the runtime to display it on the client.

如何选择: @mdx-js/react vs @mdx-js/mdx

  • @mdx-js/react:

    Choose @mdx-js/react if you are developing a React application that needs to display MDX content. This package is mandatory in your frontend dependencies to provide the MDXProvider and the internal components that map standard HTML tags (like h1, p, img) to React components. You need this library whenever you import compiled MDX files or use the useMDXComponents hook to customize how content is rendered, regardless of whether the compilation happened via Vite, Next.js, or a custom script.

  • @mdx-js/mdx:

    Choose @mdx-js/mdx if you are building a build tool, a framework plugin (like for Next.js, Vite, or Webpack), or a custom CLI that needs to convert .mdx files into JavaScript before the application runs. This package is essential when you need to process content at compile-time to optimize performance, enable static site generation (SSG), or apply complex transformations via remark/rehype plugins. It is not used directly in the browser or for dynamic runtime parsing without a build step.

@mdx-js/react的README

@mdx-js/react

Build Coverage Downloads Size Sponsors Backers Chat

React context for MDX.

Contents

What is this?

This package is a context based components provider for combining React with MDX.

When should I use this?

This package is not needed for MDX to work with React. See ¶ MDX provider in § Using MDX for when and how to use an MDX provider.

If you use Next.js, do not use this. Add an mdx-components.tsx (in src/ or /) file instead. See Configuring MDX on nextjs.org for more info.

Install

This package is ESM only. In Node.js (version 16+), install with npm:

npm install @mdx-js/react

In Deno with esm.sh:

import {MDXProvider} from 'https://esm.sh/@mdx-js/react@3'

In browsers with esm.sh:

<script type="module">
  import {MDXProvider} from 'https://esm.sh/@mdx-js/react@3?bundle'
</script>

Use

/**
 * @import {MDXComponents} from 'mdx/types.js'
 */

import {MDXProvider} from '@mdx-js/react'
import Post from './post.mdx'
// ^-- Assumes an integration is used to compile MDX to JS, such as
// `@mdx-js/esbuild`, `@mdx-js/loader`, `@mdx-js/node-loader`, or
// `@mdx-js/rollup`, and that it is configured with
// `options.providerImportSource: '@mdx-js/react'`.

/** @type {MDXComponents} */
const components = {
  em(properties) {
    return <i {...properties} />
  }
}

console.log(
  <MDXProvider components={components}>
    <Post />
  </MDXProvider>
)

👉 Note: you don’t have to use MDXProvider and can pass components directly:

-<MDXProvider components={components}>
-  <Post />
-</MDXProvider>
+<Post components={components} />

See ¶ React in § Getting started for how to get started with MDX and React. See ¶ MDX provider in § Using MDX for how to use an MDX provider.

API

This package exports the identifiers MDXProvider and useMDXComponents. There is no default export.

MDXProvider(properties?)

Provider for MDX context.

Parameters
  • properties (Props) — configuration
Returns

Element (ReactElement).

useMDXComponents(components?)

Get current components from the MDX Context.

Parameters
Returns

Current components (MDXComponents from mdx/types.js).

MergeComponents

Custom merge function (TypeScript type).

Parameters
Returns

Additional components (MDXComponents from mdx/types.js).

Props

Configuration for MDXProvider (TypeScript type).

Fields

Types

This package is fully typed with TypeScript. It exports the additional types MergeComponents and Props.

For types to work, make sure the TypeScript JSX namespace is typed. This is done by installing and using the types of your framework, as in @types/react.

Compatibility

Projects maintained by the unified collective are compatible with maintained versions of Node.js.

When we cut a new major release, we drop support for unmaintained versions of Node. This means we try to keep the current release line, @mdx-js/react@^3, compatible with Node.js 16.

Security

See § Security on our website for information.

Contribute

See § Contribute on our website for ways to get started. See § Support for ways to get help.

This project has a code of conduct. By interacting with this repository, organization, or community you agree to abide by its terms.

License

MIT © Compositor and Vercel