@bacons/mdx

中文文档
1.0.3发布于 2026/03/30MITTypeScriptCommonJS构建来源已验证近 30 天下载 5889

Universal MDX renderer for Expo Router

原始使用文档
文档目录16 节
  1. Add the package to your npm dependencies
  2. Setup
  3. Usage
  4. Custom components
  5. Custom styles
  6. Typescript
  7. Experimental native errors
  8. Web-only components
  9. Next.js Usage
  10. Remote MDX
  11. Server-side Compilation
  12. Client-side Rendering
  13. How It Works
  14. Frontmatter
  15. Known Issues
  16. About this project

@bacons/mdx

Universal MDX for Expo apps and websites.

  • Build-time MDX to React Native or React DOM components.
  • Server-rendered MDX for downloading content over a CMS.
  • Custom components and style definitions.

Add the package to your npm dependencies

bun add @bacons/mdx

Setup

Add support for importing md and mdx files in your metro.config.js file.

metro.config.js

const { getDefaultConfig } = require("expo/metro-config");
const { withMdx } = require("@bacons/mdx/metro");

const config = withMdx(getDefaultConfig(__dirname));

module.exports = config;
Manual setup

If you need to customize the babelTransformerPath more, than use this manual setup:

metro.config.js

const { getDefaultConfig } = require("expo/metro-config");

const config = getDefaultConfig(__dirname);

config.resolver.sourceExts.push("md", "mdx");

config.transformer.babelTransformerPath = require.resolve("./transformer.js");

module.exports = config;

Create a custom metro transformer. This is used to transform MDX files into JS + React Native before transpiling with Babel.

./transformer.js

const upstreamTransformer = require("@expo/metro-config/babel-transformer");
const MdxTransformer = require("@bacons/mdx/metro-transformer");

module.exports.transform = async (props) => {
  // Then pass it to the upstream transformer.
  return upstreamTransformer.transform(
    // Transpile MDX first.
    await MdxTransformer.transform(props)
  );
};

Usage

Create a markdown file:

./demo.mdx

import { CustomComponent } from './my-custom-component';

# Hello World

I **am** a _markdown_ file!

<CustomComponent />

This file can be imported and treated as a React component:

./App.js

import Demo from "./demo.mdx";

export default function App() {
  return <Demo />;
}

Custom components

By default, this package uses an incomplete set of universal React Native components for DOM elements. You may wish to improve the components, add more, or swap them out for your own.

import { Text } from "react-native";
import { MDXComponents } from "@bacons/mdx";

export default function App() {
  return (
    <Demo
      components={{
        h1: (props) => <h1 {...props} />,
        // Add custom components which can be used as JSX elements.
        RedText: (props) => <Text {...props} style={{ color: "red" }} />,
        // This can be used as `<RedText />` without the need to import it.
      }}
    />
  );
}

Now inside of your markdown file, you can use the custom components:

# Hello World

<RedText />

You can set the components for all children using the MDXComponents React context component.

import { Text } from "react-native";
import { MDXComponents } from "@bacons/mdx";

export default function App() {
  // Pass any HTML element as a key to the MDXComponents component.
  return (
    <MDXComponents
      components={{
        h1: (props) => <Text {...props} />,
        // Add custom components which can be used as JSX elements.
        RedText: (props) => <Text {...props} style={{ color: "red" }} />,
        // This can be used as `<RedText />` without the need to import it.
      }}
    >
      <Demo />
    </MDXComponents>
  );
}

Be sure to pass the style prop down to the component you're using, this is how the styles are cascaded.

Custom styles

This package works similarly to most docs sites. You create high-level styles for the entire site. This can be cascaded down to reduce the scope of a style.

import { MDXStyles } from "@bacons/mdx";

export default function App() {
  // Pass any HTML element as a key to the MDXStyles component.
  return (
    <MDXStyles
      h1={{
        fontSize: 32,
        fontWeight: "bold",
        color: "red",
      }}
    >
      <Demo />
    </MDXStyles>
  );
}

The <MDXStyles> components can be stacked in different levels, think of these like CSS classes.

Typescript

You can add support for importing .mdx files in your tsconfig.json file.

tsconfig.json

{
  "compilerOptions": {
    "typeRoots": ["./global.d.ts"]
  },
  "extends": "expo/tsconfig.base"
}

Now create a file that declares the module.

./global.d.ts

declare module "*.mdx" {
  import React from "react";
  import { CustomComponentsProp } from "@bacons/mdx";
  const Component: React.FC<{
    components?: CustomComponentsProp;
  }>;
  export default Component;
}

Experimental native errors

Optional native-only step, not required for MDX to work.

React Native has suboptimal error messages for when you use React DOM components on native or render strings outside of <Text /> elements. This can make migration and code sharing very painful. This package has an experimental dev-only feature to print out optimized errors when you render react-dom built-in's such as div, p, h1, etc. on native.

Simply add the following to your babel.config.js, and clear the transform cache npx expo start --clear:

module.exports = function (api) {
  api.cache(true);
  return {
    presets: [["babel-preset-expo", { jsxImportSource: "@bacons/mdx/jsx" }]],
  };
};

Now when you render a div, p, h1, etc. on native, you will get a helpful error message.

export default function App() {
  return <div>Hey</div>;
}
ERROR  Unsupported DOM <p /> at: /Users/evanbacon/Documents/GitHub/bacons/mdx/apps/demo/src/App.tsx:1:11
This will break in production.

Web-only components

It's possible to parse MDX to DOM elements instead of universal components. This can be useful when building for web-only or migrating from web-only. To do this, pull in the getDOMComponents function and pass it to the components prop of the MDX component.

import { getDOMComponents } from "@bacons/mdx";

import Demo from "./readme.md";

export default function App() {
  return <Demo components={getDOMComponents()} />;
}

This will render the following MDX as DOM elements:

# Hello World

I **am** a _markdown_ file!

And the DOM:

<h1>Hello World</h1>
<p>I <strong>am</strong> a <em>markdown</em> file!</p>

Next.js Usage

  1. Follow the steps detailed here: https://nextjs.org/docs/advanced-features/using-mdx
  2. Add the following packages to transpile-modules within your next.config.js:
    '@bacons/mdx',
    '@bacons/react-views',
    '@expo/html-elements',
    

Remote MDX

Load and render MDX content from a remote server. This is useful for CMS-driven content, blog posts, or any MDX that needs to be fetched at runtime.

Server-side Compilation

First, compile MDX on your server (API route, edge function, or build script):

// app/api/mdx+api.ts (Expo Router API route)
import { compileMDX } from "@bacons/mdx/server";

export async function GET(request: Request) {
  const url = new URL(request.url);
  const slug = url.searchParams.get("slug");

  // Fetch MDX content from your CMS or file system
  const mdxSource = `
# Hello World

This is **bold** text with a <CustomButton>Click me</CustomButton>.
  `;

  const compiled = await compileMDX(mdxSource);

  return Response.json(compiled);
}

The compileMDX function accepts optional remark and rehype plugins:

const compiled = await compileMDX(mdxSource, {
  remarkPlugins: [remarkGfm],
  rehypePlugins: [rehypeHighlight],
});

Client-side Rendering

Use the RemoteMDX component and useMDXFetch hook to fetch and render remote MDX:

import { RemoteMDX, useMDXFetch, MDXComponents } from "@bacons/mdx";
import { Text, ActivityIndicator } from "react-native";

function BlogPost({ slug }: { slug: string }) {
  const { content, loading, error } = useMDXFetch(`/api/mdx?slug=${slug}`);

  if (loading) return <ActivityIndicator />;
  if (error) return <Text>Error: {error.message}</Text>;
  if (!content) return null;

  return (
    <MDXComponents
      components={{
        CustomButton: (props) => (
          <Text {...props} style={{ color: "blue" }} />
        ),
      }}
    >
      <RemoteMDX source={content} />
    </MDXComponents>
  );
}

You can also fetch the content yourself and pass it directly:

import { RemoteMDX, type CompiledMDX } from "@bacons/mdx";

function BlogPost({ content }: { content: CompiledMDX }) {
  return <RemoteMDX source={content} />;
}

How It Works

  1. Server: Compiles MDX to a JSON-serializable AST format (not executable JavaScript)
  2. Client: Fetches the JSON and renders it using the existing component system
  3. Security: No eval() or code execution - the AST is pure data
  4. Components: Custom components work via MDXComponents context or components prop
  5. Styles: MDXStyles context is fully supported

Frontmatter

Frontmatter is extracted and available in the compiled output:

const compiled = await compileMDX(`
---
title: My Post
date: 2024-01-01
---

# Content here
`, {
  remarkPlugins: [remarkFrontmatter, remarkMdxFrontmatter],
});

console.log(compiled.frontmatter);
// { title: "My Post", date: "2024-01-01" }

Access frontmatter on the client:

function BlogPost({ slug }: { slug: string }) {
  const { content } = useMDXFetch(`/api/mdx?slug=${slug}`);

  return (
    <>
      <Text>{content?.frontmatter?.title}</Text>
      <RemoteMDX source={content} />
    </>
  );
}

Known Issues

  • ol, li, ul are all buggy. PRs welcome.
  • Native image ratios are weird.

About this project

This is a universal MDX implementation for Expo (React & Metro). It aims to be a general-purpose MDX implementation for Expo projects that leverage universal Metro (Expo CLI).

  • Test in the apps/demo project.

This library powers my (Evan Bacon) personal blog, the source can be found here: Evan Bacon portfolio.

版本与发布记录

@bacons/mdx 共发布 28 个版本,首次发布于 2022/12/14;过去 12 个月发布了 7 个版本。最新版本附带 npm provenance 构建来源证明,可追溯到公开的 CI 构建记录。

版本发布日期距今
1.0.3latest2026/03/307 个月前
1.0.22026/01/149 个月前
1.0.12026/01/149 个月前
0.5.52026/01/149 个月前
0.5.32026/01/149 个月前
0.5.22026/01/149 个月前

关于 @bacons/mdx 的常见问题

@bacons/mdx 是什么?

Universal MDX renderer for Expo Router

如何安装 @bacons/mdx?

在项目目录运行 npm install @bacons/mdx,或使用 pnpm add / yarn add / bun add。它还需要宿主项目提供 peer 依赖:react-native(*)。npm 7 及以上与 pnpm 8 及以上默认会自动安装必需的 peer 依赖,Yarn 需要手动安装。

@bacons/mdx 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@bacons/mdx 支持 ESM 还是 CommonJS?

以 CommonJS 形式发布,可以直接 require();在 ES Module 项目中也可以用 import 引入(Node.js 会把 module.exports 作为默认导出)。

@bacons/mdx 可以商用吗?

@bacons/mdx 使用 MIT 许可。宽松许可:允许商业使用、修改和再分发,通常只需在分发时保留原版权与许可声明。以上为概括,不构成法律意见,请以许可原文为准。

@bacons/mdx 的下载量有多少?

根据 npm 官方下载统计,@bacons/mdx 在 2026/10/08 – 2026/11/06 这 30 天内被下载 5,889 次,日均约 196 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@bacons/mdx 还在维护吗?

最新版本 1.0.3 发布于 2026/03/30(7 个月前);至今共发布 28 个版本,其中过去 12 个月发布了 7 个;首次发布于 2022/12/14。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。