Skip to navigation
6-9 minutes read
By Titus Wormer

入门

本文讲解了如何将 MDX 集成到您的项目中。 还展示了如何将 MDX 与您所使用的打包工具(bundler)和 JSX 运行时协同使用。 如需了解 MDX 格式的工作原理,我们建议您先去阅读 § MDX 是什么 章节。 当您已经设置并准备好使用 MDX 时,请参考 § 使用 MDX 章节。

目录

事先准备

MDX 依赖于 JSX,因此您的项目也得是支持 JSX 的。 任何 JSX 运行时(React、Preact、Vue 等)都可以。 注意,我们真的是把 JSX 编译成 JavaScript 了,因此您不必再做相关设置了。

所有 @mdx-js/* 匹配的软件包都是使用最新的 JavaScript 语法编写的。 您需要安装的 Node.js 版可以是 12.20、14.14、16.0 或 更新版本。 我们提供的软件包全是支持 ESM 格式 的,因此你必须使用 import 而不是 require

快速开始

打包工具(Bundler)

MDX 是一种编译成 JavaScript 的语言。 (我们还将普通的 markdown 也编译为 JavaScript。) 最简单的入门方法就是使用一个针对您所使用的打包工具的The easiest way to get started is to use an integration for your bundler if you have one:

  • 如果你使用的是 esbuild, 请安装并配置 @mdx-js/esbuild
  • 如果你使用的是 Rollup (或集成了 Rollup 的 Vite 或者 WMR), 请安装并配置 @mdx-js/rollup
  • 如果你使用的是 webpack (或集成了 webpack 的 Create React App (CRA)、Next.js 或 Vue CLI), 请安装并配置 @mdx-js/loader

如果你不使用打包工具(bundler),也可以使用 MDX:

有关上述工具的更多信息,请参见各自的专门文档: ¶ Create React App (CRA), ¶ esbuild, ¶ Next.js, ¶ Node.js, ¶ Rollup, ¶ Vite, ¶ Vue CLI, ¶ WMR, ¶ webpack.

除了官方提供的集成,还有来自于社区的。 由于我们刚刚进入 v2 版本,以下列表可能在 v2文档中已经过时了。 参见: ¶ Docusaurus¶ Gatsby¶ Parcel¶ Razzle¶ React Static¶ Snowpack

JSX

现在,您已经设置了一个集成 MDX 的打包工具或直接使用 @mdx-js/mdx 本体,是时候配置 您的 JSX 运行时了。

通过设置 options.jsxImportSource,其它 JSX 运行时也是支持的。 另请参见此处关于See also the different options there on how to use the classic JSX runtime and how to define a pragma and pragmaFrag for it.

有关上述工具的更多信息,请参见各自的专门文档: ¶ Emotion, ¶ Preact, ¶ React, ¶ Solid, ¶ Svelte, ¶ Theme UI, or ¶ Vue.

编辑器

一旦把项目中所用的工具设置好后,您就可以为您所使用的编辑器添加对 MDX 的支持来 提升开发体验了:

注意: 我们正在寻求适配 emacs 以及其它编辑器方面的帮助!

Types

Expand example of typed imports

First install the package:

Shell
npm install @types/mdx

…TypeScript should automatically pick it up:

example.js
import Post from './post.mdx' // `Post` is now typed.

我们提供的所有 API 都是支持 TypeScript 类型定义的。

To enable types for imported .mdx, .md, etcetera files, you should make sure the TypeScript JSX namespace is typed. This is done by installing and using the types of your framework, such as @types/react. Then you can install and use @types/mdx, which adds types to import statements of supported files.

You can also import several types about the API of MDX files from @types/mdx. For example:

example.ts
import type {MDXComponents} from 'mdx/types.js'

安全性

请牢记,MDX 是一门编程语言。 如果你相信你的用户,那就岁月静好。 但是一定要当心用户输入的内容,不要让任何人 上传 MDX 内容。 如果您确有需要,请同时使用 <iframe>sandbox,不过 security is hard, and that doesn’t seem to be 100%. 对于 Node,vm2 值得一试。 但是你还是应该使用 Docker 之类的工具对整个操作系统做沙箱化处理、 以及限制执行频率、以及在进程占用太多执行时间时能够将其 终止。

Integrations

Bundlers

esbuild
Expand example
example.js
import esbuild from 'esbuild'
import mdx from '@mdx-js/esbuild'

await esbuild.build({
  entryPoints: ['index.mdx'],
  outfile: 'output.js',
  format: 'esm',
  plugins: [mdx({/* jsxImportSource: …, otherOptions… */})]
})

We support esbuild. Install and configure the esbuild plugin @mdx-js/esbuild. This plugin has an additional option allowDangerousRemoteMdx. Configure your JSX runtime depending on which one you use (React, Preact, Vue, etc.).

If you use more modern JavaScript features than what your users support, configure esbuild’s target.

Rollup
Expand example
rollup.config.js
import mdx from '@mdx-js/rollup'
import {babel} from '@rollup/plugin-babel'

export default {
  // …
  plugins: [
    // …
    mdx({/* jsxImportSource: …, otherOptions… */})
    // Babel is optional.
    babel({
      // Also run on what used to be `.mdx` (but is now JS):
      extensions: ['.js', '.jsx', '.cjs', '.mjs', '.md', '.mdx'],
      // Other options…
    })
  ]
}

我们提供了对 Rollup 的支持。 您只需安装并配置 Rollup 插件 @mdx-js/rollup 即可。 此插件还有两个额外的参数 includeexclude对 JSX 运行时的配置 取决于你所使用的是哪个(React、Preact、 Vue 或其他)

如果你所使用的 JavaScript 语法太新,你的用户所用的环境可能不支持, 那么你需要 安装并配置 @rollup/plugin-babel

另请参见 ¶ Vite¶ WMR (底层使用的是 Rollup) 以了解更多信息。

Webpack
Expand example
webpack.config.js
module.exports = {
  module: {
    // …
    rules: [
      // …
      {
        test: /\.mdx?$/,
        use: [
          // `babel-loader` is optional:
          {loader: 'babel-loader', options: {}},
          {
            loader: '@mdx-js/loader',
            /** @type {import('@mdx-js/loader').Options} */
            options: {/* jsxImportSource: …, otherOptions… */}
          }
        ]
      }
    ]
  }
}

我们提供了对 webpack 的支持。 你只需要安装并配置 webpack 加载器(loader) @mdx-js/loader 即可。 对 JSX 运行时的配置 取决于你所使用的是哪个(React、Preact、 Vue 或其他)。

如果你所使用的 JavaScript 语法太新,你的用户所用的环境可能不支持, 那么你需要 安装并配置 babel-loader

另请参见 ¶ Create React App (CRA)¶ Next.js 以及 ¶ Vue CLI (底层使用的是 webpack)以了解更多信息。

构建系统

Snowpack

Snowpack 有自己的插件来支持 MDX。 请参阅 snowpack-plugin-mdx 以了解如何将 MDX 于 Snowpack 一起使用。

Vite
Expand example
vite.config.js
import {defineConfig} from 'vite'
import mdx from '@mdx-js/rollup'

export default defineConfig({
  plugins: [
    mdx(/* jsxImportSource: …, otherOptions… */)
  ]
})

Vite 支持直接在 vite.config.js 文件中的 plugins 参数中配置 Rollup 插件。

安装并配置 Rollup 插件 @mdx-js/rollup

如果你所使用的 JavaScript 语法太新,你的用户所用的环境可能不支持, 请配置 Vite 的 build.target 参数。

Note: Vite 3 requires the older rollup@2. Make sure rollup@3 is not installed.

Note: If you also use vitejs/vite-plugin-react, you need to force @mdx-js/rollup to run in the pre phase before it:

vite.config.js
// …
export default defineConfig({
  plugins: [
    {enforce: 'pre', ...mdx(/* jsxImportSource: …, otherOptions… */)},
    react()
  ]
})
// …

另请参见 ¶ Rollup (Vite 使用的就是 Rollup)以及 ¶ Vue 以了解 更多信息。

Vue CLI
Expand example
vue.config.js
module.exports = {
  chainWebpack: config => {
    config.module
      .rule('mdx')
      .test(/\.mdx?$/)
      .use('babel-loader')
        .loader('babel-loader')
        .options({plugins: ['@vue/babel-plugin-jsx'], /* Other options… */})
        .end()
      .use('@mdx-js/loader')
        .loader('@mdx-js/loader')
        .options({jsx: true, /* otherOptions… */})
        .end()
  }
}

Vue CLI 目前处于 beta 阶段,版本号是 5,其支持 在 vue.config.js 文件中的 configureWebpack.plugins 参数中直接配置 webpack 的加载器(loaders)。

安装并配置 webpack 加载器(loader) @mdx-js/loader。 您还需要配置 Vue 和 Babel

See also ¶ webpack, which is used in Vue CLI, and see ¶ Vue, which you’re likely using, for more info.

Note: to support ESM in vue.config.js or vue.config.mjs, you currently have to use their v5.0.0-rc. See v5.0.0-beta.0 in their changelog for more info. Their latest beta release is currently v5.0.0-rc.2.

WMR
Expand example
wmr.config.mjs
import {defineConfig} from 'wmr'
import mdx from '@mdx-js/rollup'

export default defineConfig({
  plugins: [
    mdx({/* jsxImportSource: …, otherOptions… */})
  ]
})

WMR supports Rollup plugins directly by adding them to plugins in wmr.config.mjs.

Install and configure the Rollup plugin @mdx-js/rollup.

See also ¶ Rollup, which is used in WMR, and see ¶ Preact, if you’re using that, for more info.

编译器

Babel
Expand plugin and sample use

This plugin:

plugin.js
import path from 'node:path'
import parser from '@babel/parser'
import estreeToBabel from 'estree-to-babel'
import {compileSync} from '@mdx-js/mdx'

export function babelPluginSyntaxMdx() {
  // Tell Babel to use a different parser.
  return {parserOverride: babelParserWithMdx}
}

// A Babel parser that parses MDX files with `@mdx-js/mdx` and passes any
// other things through to the normal Babel parser.
function babelParserWithMdx(value, options) {
  if (
    options.sourceFileName &&
    /\.mdx?$/.test(path.extname(options.sourceFileName))
  ) {
    // Babel does not support async parsers, unfortunately.
    return compileSync(
      {value, path: options.sourceFileName},
      // Tell `@mdx-js/mdx` to return a Babel tree instead of serialized JS.
      {recmaPlugins: [recmaBabel], /* jsxImportSource: …, otherOptions… */}
    ).result
  }

  return parser.parse(value, options)
}

// A “recma” plugin is a unified plugin that runs on the estree (used by
// `@mdx-js/mdx` and much of the JS ecosystem but not Babel).
// This plugin defines `'estree-to-babel'` as the compiler, which means that
// the resulting Babel tree is given back by `compileSync`.
function recmaBabel() {
  Object.assign(this, {Compiler: estreeToBabel})
}

Can be used like so with the Babel API:

example.js
import babel from '@babel/core'
import {babelPluginSyntaxMdx} from './plugin.js'

// Note that a filename must be set for our plugin to know it’s MDX instead of JS.
await babel.transformAsync(file, {filename: 'example.mdx', plugins: [babelPluginSyntaxMdx]})

You should probably use webpack or Rollup instead of Babel directly as that gives the neatest interface. It is possible to use @mdx-js/mdx in Babel and it’s fast, because it skips @mdx-js/mdx serialization and Babel parsing, if Babel is used anyway.

Babel does not support syntax extensions to its parser (it has “syntax” plugins but those in fact turn certain flags on or off). It does support setting a different parser. Which in turn lets us choose whether to use the @mdx-js/mdx or @babel/parser.

Site generators

Astro

Astro has their own MDX integration. You can add the integration with the Astro CLI (recommended):

Shell
npx astro add mdx

This base setup allows you to import markdown, Astro components, and other MDX files as components. To use components from frameworks in your MDX files, see Astro’s Framework components guide.

For more on how to combine Astro and MDX, see their MDX integration docs.

Create React App (CRA)

Note: it’s currently probably not a good idea to use CRA.

Note: rewiring with CRACO is currently required for CRA 5, due to a bug in react-scripts (facebook/create-react-app#12166), which is also tracked at mdx-js/mdx#1870.

Note: warnings about CRACO having incorrect peer dependency "react-scripts@^4.0.0" can currently be ignored.

Expand example
src/content.mdx
# Hello, world!

This is **markdown** with <span style={{color: "red"}}>JSX</span>: MDX!
src/App.jsx
/* eslint-disable import/no-webpack-loader-syntax */
import Content from '!@mdx-js/loader!./content.mdx'

export default function App() {
  return <Content />
}
Expand CRACO example
craco.config.js
const {addAfterLoader, loaderByName} = require('@craco/craco')

module.exports = {
  webpack: {
    configure(webpackConfig) {
      addAfterLoader(webpackConfig, loaderByName('babel-loader'), {
        test: /\.mdx?$/,
        loader: require.resolve('@mdx-js/loader')
      })
      return webpackConfig
    }
  }
}
src/App.jsx
import Content from './content.mdx'

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

CRA supports webpack loaders through webpack loader syntax in imports.

Install the webpack loader @mdx-js/loader.

For importing MDX without the !@mdx-js/loader! prefix, you can add the loader to the webpack config, by rewiring react-scripts using CRACO.

See also ¶ Webpack, which is used in CRA, and see ¶ React, which you’re likely using, for more info.

Docusaurus

Docusaurus supports MDX by default. See MDX and React on their website for more on how to use MDX with Docusaurus.

Gatsby

Gatsby has their own plugin to support MDX. See gatsby-plugin-mdx on how to use MDX with Gatsby.

Next.js
Expand example
next.config.js
import nextMdx from '@next/mdx'

const withMdx = nextMdx({
  // By default only the .mdx extension is supported.
  extension: /\.mdx?$/,
  options: {/* providerImportSource: …, otherOptions… */}
})

export default withMdx({
  // Support MDX files as pages:
  pageExtensions: ['md', 'mdx', 'tsx', 'ts', 'jsx', 'js'],
})

Next.js has its own package to support MDX.

Install and configure @next/mdx. There is no need to configure your JSX runtime as React is already set up.

The MDX provider can be configured in pages/_app.js. In order to use it, you need to configure the providerImportSource as well.

Expand provider example
next.config.js
import nextMdx from '@next/mdx'

const withMdx = nextMdx({
  // By default only the .mdx extension is supported.
  extension: /\.mdx?$/,
  options: {providerImportSource: '@mdx-js/react',  /* otherOptions… */}
})

export default withMdx({
  // Support MDX files as pages:
  pageExtensions: ['md', 'mdx', 'tsx', 'ts', 'jsx', 'js'],
})
pages/_app.js
import {MDXProvider} from '@mdx-js/react'
import {Header} from '../components/Header.js'

const components = {
  h1: Header
}

export default function App({Component, pageProps}) {
  return (
    <MDXProvider components={components}>
      <Component {...pageProps} />
    </MDXProvider>
  )
}

See Using MDX with Next.js for more details.

Parcel

Parcel 2 has their own plugin to support MDX. See @parcel/transformer-mdx on how to use MDX with Parcel.

Razzle

Razzle has their own plugin to support MDX. See razzle-plugin-mdx on how to use MDX with Razzle.

React Static

React Static has their own plugin to support MDX. See react-static-plugin-mdx on how to use MDX with React Static.

JSX runtimes

Emotion
Expand example
example.js
import {compile} from '@mdx-js/mdx'

const js = String(await compile('# hi', {jsxImportSource: '@emotion/react', /* otherOptions… */}))

Emotion is supported when options.jsxImportSource is set to '@emotion/react'. You can optionally install and configure @mdx-js/react, which allows for context based component passing.

See also ¶ React, which is used in Emotion, and see ¶ Rollup and ¶ webpack, if you’re using them, for more info.

Ink
Expand example
example.mdx
# Hi!
example.js
import React from 'react'
import {render, Text} from 'ink'
import Content from './example.mdx' // Assumes an integration is used to compile MDX -> JS.

const components = {
  h1(props) {
    return React.createElement(Text, {bold: true, ...props})
  },
  p: Text
}

render(React.createElement(Content, {components}))

Can be used with:

Shell
node --experimental-loader=@mdx-js/node-loader example.js

Ink uses the React JSX runtime, so set that up. You will also want to swap HTML elements out for Ink’s components. See § Table of components for what those are and Ink’s documentation on what you can replace them with.

See also ¶ React and ¶ Node.js, which you’re using, for more info.

Preact
Expand example
example.js
import {compile} from '@mdx-js/mdx'

const js = String(await compile('# hi', {jsxImportSource: 'preact', /* otherOptions… */}))

Preact is supported when options.jsxImportSource is set to 'preact'. You can optionally install and configure @mdx-js/preact, which allows for context based component passing.

See also ¶ esbuild, ¶ Rollup, and ¶ webpack, which you might be using, for more info.

React

React is supported right out of the box. You can optionally install and configure @mdx-js/react, which allows for context based component passing.

See also ¶ esbuild, ¶ Rollup, and ¶ webpack, which you might be using, for more info.

Experiment: while currently in alpha and not shipping soon, React server components will work with MDX too. There is an experimental demo. And our website is made with them!

Theme UI
Expand example

Example w/o @mdx-js/react

example.js
import {base} from '@theme-ui/preset-base'
import {components, ThemeProvider} from 'theme-ui'
import Post from './post.mdx' // Assumes an integration is used to compile MDX -> JS.

<ThemeProvider theme={base}>
  <Post components={components} />
</ThemeProvider>

Example w/ @mdx-js/react

example.js
import {base} from '@theme-ui/preset-base'
import {ThemeProvider} from 'theme-ui'
import Post from './post.mdx' // Assumes an integration is used to compile MDX -> JS.

<ThemeProvider theme={base}>
  <Post />
</ThemeProvider>

Theme UI is a React-specific library that requires using context to access its effective components. You can optionally install and configure @mdx-js/react, which allows for context based component passing.

See also ¶ Emotion, ¶ React, ¶ esbuild, ¶ Rollup, and ¶ webpack, which you might be using, for more info.

Svelte
Expand example
example.js
import {compile} from '@mdx-js/mdx'

const js = String(await compile('# hi', {jsxImportSource: 'svelte-jsx', /* otherOptions… */}))

Svelte is supported when options.jsxImportSource is set to 'svelte-jsx', which is a small package that adds support for the JSX automatic runtime to Svelte.

See also ¶ esbuild, ¶ Rollup, and ¶ webpack, which you might be using, for more info.

Vue
Expand example
example.js
import {compile} from '@mdx-js/mdx'
import babel from '@babel/core'

const jsx = String(await compile('# hi', {jsx: true, /* otherOptions… */}))
const js = (await babel.transformAsync(jsx, {plugins: ['@vue/babel-plugin-jsx']})).code

Vue 3 is supported when using their custom Babel JSX transformer (@vue/babel-plugin-jsx) and configuring @mdx-js/mdx, @mdx-js/rollup, or @mdx-js/loader with jsx: true. You can optionally install and configure @mdx-js/vue, which allows for context based component passing.

See also ¶ Vite and ¶ Vue CLI, which you might be using, for more info.

Solid
Expand example
example.js
import {compile} from '@mdx-js/mdx'

const js = String(await compile('# hi', {jsxImportSource: 'solid-js/h', /* otherOptions… */}))

Solid is supported when options.jsxImportSource is set to 'solid-js/h'.

See also ¶ Vite and ¶ Rollup which you might be using, for more info.

JavaScript engines

Node.js

MDX files can be imported in Node by using @mdx-js/node-loader (strongly recommended) or alternatively they can be required with the legacy package @mdx-js/register. See their readmes on how to configure them.

Further reading