这篇文章解释了如何将 MDX 集成到您的项目中。它展示了如何使用您选择的捆绑器和JSX 运行时与 MDX 一起使用。要了解 MDX 格式的工作原理,我们建议您从§ 什么是MDX 开始。在您设置好并准备好使用 MDX 时,请参阅使用MDX。

先决条件

MDX 依赖于 JSX,因此您的项目也必须支持 JSX。任何 JSX 运行时(React、Preact、Vue 等)都可以。请注意,我们会为您将 JSX 编译为 JavaScript,因此您不必自行设置。

所有 @mdx-js/* 包都是用现代 JavaScript 编写的。需要 Node.js 版本 16.0 或更高版本才能使用它们。我们的包也仅支持 ESM only,因此它们必须以 import 形式引入,而不是 require 形式。

注意:想使用 Rust 而不是 Node.js 吗?试试 mdxjs-rs

快速入门

打包器

MDX 是一种编译为 JavaScript 的语言。(我们还将常规 markdown 编译为 JavaScript。)如果您有一个打包器,最简单的开始方法就是使用适用于您的打包器的集成:

您也可以在没有打包器的情况下使用 MDX:

有关这些工具的更多信息,请参阅它们各自的专用部分:¶ Next.js¶ Node.js¶ Rollup¶ Vite¶ esbuild¶ webpack

JSX

现在您已经设置了集成或 @mdx-js/mdx 本身,是时候配置您的 JSX 运行时了。

其他 JSX 运行时可通过设置 ProcessorOptions 中的 jsxImportSource 来支持。

有关这些工具的更多信息,请参阅它们各自的专用部分:¶ Emotion¶ Preact¶ React¶ Solid¶ Svelte¶ Theme UI¶ Vue

编辑器

您可以通过为编辑器添加 MDX 支持来增强使用 MDX 的体验:

**,请尝试 JetBrains/mdx-intellij-plugin

驱动我们 VS Code 扩展的语法高亮显示也用于 GitHub 上的代码块高亮显示,这些语法高亮显示的维护工作在 wooorm/markdown-tm-language 中进行。

类型

扩展类型导入的示例

首先安装该包:

Shell

  1. npm install @types/mdx

……TypeScript 应该会自动识别到:

example.js

  1. import Post from './post.mdx' // `Post` 现在已经有了类型。

我们的包是用 TypeScript 编写的。要使类型起作用,必须对 JSX 命名空间进行类型化。可以通过安装和使用您的框架的类型,比如 @types/react 来完成这一点。

要为导入的 .mdx.md 等文件启用类型,请安装并使用 @types/mdx。该包还导出了一些有用的类型,例如 MDXComponents,它表示 components 属性。您可以这样导入它们:

example.ts

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

安全性

MDX 是一种编程语言。如果您信任您的作者,那没问题。如果不信任,那就不安全。

不要让互联网上的随机人员编写 MDX。如果您这样做了,您可能会考虑使用带有 sandbox<iframe>,但是安全性很难保证,似乎也不是 100%。对于 Node.js,vm2 听起来很有趣。但是您可能还应该使用 Docker 或类似的东西沙盒整个操作系统,执行速率限制,并确保进程在运行时间过长时可以被终止。

集成

打包器

esbuild

扩展示例

example.js

  1. import mdx from '@mdx-js/esbuild'
  2. import esbuild from 'esbuild'
  3. await esbuild.build({
  4. entryPoints: ['index.mdx'],
  5. format: 'esm',
  6. outfile: 'output.js',
  7. plugins: [mdx({/* jsxImportSource: …, otherOptions… */})]
  8. })

我们支持 esbuild。安装并配置 esbuild 插件 @mdx-js/esbuild。根据您使用的 JSX 运行时(React、Preact、Vue 等),配置您的 JSX 运行时

要使用比您的用户支持的更现代的 JavaScript 特性,配置 esbuild 的 target

Rollup

扩展示例

rollup.config.js

  1. import mdx from '@mdx-js/rollup'
  2. import {babel} from '@rollup/plugin-babel'
  3. /** @type {import('rollup').RollupOptions} */
  4. const config = {
  5. // …
  6. plugins: [
  7. // …
  8. mdx({/* jsxImportSource: …, otherOptions… */})
  9. // Babel 是可选的:
  10. babel({
  11. // 也在之前所用的 `.mdx` 上运行(但现在是 JS):
  12. extensions: ['.js', '.jsx', '.cjs', '.mjs', '.md', '.mdx'],
  13. // 其他选项…
  14. })
  15. ]
  16. }
  17. export default config

我们支持 Rollup。安装并配置 Rollup 插件 @mdx-js/rollup。根据您使用的 JSX 运行时(React、Preact、Vue 等),配置您的 JSX 运行时

要使用比您的用户支持的更现代的 JavaScript 特性,安装并配置 @rollup/plugin-babel

如果您通过 Vite 使用 Rollup,请参阅 ¶ Vite 获取更多信息。

webpack

扩展示例

webpack.config.js

  1. /** @type {import('webpack').Configuration} */
  2. const webpackConfig = {
  3. module: {
  4. // …
  5. rules: [
  6. // …
  7. {
  8. test: /\.mdx?$/,
  9. use: [
  10. // Babel 是可选的:
  11. {loader: 'babel-loader', options: {}},
  12. {
  13. loader: '@mdx-js/loader',
  14. /** @type {import('@mdx-js/loader').Options} */
  15. options: {/* jsxImportSource: …, otherOptions… */}
  16. }
  17. ]
  18. }
  19. ]
  20. }
  21. }
  22. export default webpackConfig

我们支持 webpack。安装并配置 webpack loader @mdx-js/loader。根据您使用的 JSX 运行时(React、Preact、Vue 等),配置您的 JSX 运行时

要使用比您的用户支持的更现代的 JavaScript 特性,安装并配置 babel-loader

如果您通过 Next 使用 webpack,请参阅 ¶ Next.js 获取更多信息。

构建系统

Vite

扩展示例

vite.config.js

  1. import mdx from '@mdx-js/rollup'
  2. import {defineConfig} from 'vite'
  3. const viteConfig = defineConfig({
  4. plugins: [
  5. mdx(/* jsxImportSource: …, otherOptions… */)
  6. ]
  7. })
  8. export default viteConfig

我们支持 Vite。安装并配置 Rollup 插件 @mdx-js/rollup。根据您使用的 JSX 运行时(React、Preact、Vue 等),配置您的 JSX 运行时

要使用

比您的用户支持的更现代的 JavaScript 特性,配置 Vite 的 build.target

注意:如果您还使用了 vitejs/vite-plugin-react,您必须在它之前强制运行 @mdx-js/rolluppre 阶段:

vite.config.js

  1. // …
  2. const viteConfig = defineConfig({
  3. plugins: [
  4. {enforce: 'pre', ...mdx(/* jsxImportSource: …, otherOptions… */)},
  5. react({include: /\.(jsx|js|mdx|md|tsx|ts)$/})
  6. ]
  7. })
  8. // …

另请参阅 ¶ Rollup,它在 Vite 中使用,有关更多信息,请参见 ¶ Vue。使用的更多信息。

编译器

Babel

扩展插件和示例用法

这个插件:

plugin.js

  1. import path from 'node:path'
  2. import parser from '@babel/parser'
  3. import {compileSync} from '@mdx-js/mdx'
  4. import estreeToBabel from 'estree-to-babel'
  5. export function babelPluginSyntaxMdx() {
  6. // 告诉 Babel 使用不同的解析器。
  7. return {parserOverride: babelParserWithMdx}
  8. }
  9. // 一个 Babel 解析器,它使用 `@mdx-js/mdx` 解析 MDX 文件,并将任何其他内容传递给正常的 Babel 解析器。
  10. function babelParserWithMdx(value, options) {
  11. if (options.sourceFileName && /\.mdx?$/.test(options.sourceFileName)) {
  12. // 不幸的是,Babel 不支持异步解析器。
  13. return compileSync(
  14. {value, path: options.sourceFileName},
  15. // 告诉 `@mdx-js/mdx` 返回 Babel 树而不是序列化的 JS。
  16. {recmaPlugins: [recmaBabel], /* jsxImportSource: …, otherOptions… */}
  17. ).result
  18. }
  19. return parser.parse(value, options)
  20. }
  21. // “recma” 插件是在 estree 上运行的统一插件(由 `@mdx-js/mdx` 和许多 JS 生态系统的其他部分使用,但不是 Babel)。
  22. // 该插件将 `'estree-to-babel'` 定义为编译器,这意味着由 `compileSync` 返回 Babel 树。
  23. function recmaBabel() {
  24. this.compiler = estreeToBabel
  25. }

……可以像这样使用 Babel API:

example.js

  1. import babel from '@babel/core'
  2. import {babelPluginSyntaxMdx} from './plugin.js'
  3. // 请注意,必须为我们的插件设置文件名,以便它知道这是 MDX 而不是 JS。
  4. await babel.transformAsync(file, {filename: 'example.mdx', plugins: [babelPluginSyntaxMdx]})

最好使用 Rollup 或 webpack 而不是直接使用 Babel,因为这提供了最好的接口。可以在 Babel 中使用 @mdx-js/mdx,而且速度比较快,因为它跳过了 @mdx-js/mdx 的序列化和 Babel 解析,如果无论如何都使用了 Babel。

Babel 不支持对其解析器的语法扩展(它有“语法”插件,但那些只是打开或关闭内部标志)。它支持设置不同的解析器。这反过来又让我们可以选择使用 @mdx-js/mdx@babel/parser

站点生成器

Astro

Astro 具有自己的 MDX 集成。您可以使用 Astro CLI 添加集成:npx astro add mdx

这个基本设置允许您将 markdown、Astro 组件和 MDX 文件作为组件导入。有关如何在 MDX 文件中使用框架组件的信息,请参阅 Astro 的 Framework components guide

有关如何结合 Astro 和 MDX,请参阅 Astro 的 MDX 集成文档

Docusaurus

Docusaurus 默认支持 MDX。有关如何在 Docusaurus 中使用 MDX 的信息,请参阅 Docusaurus 的 MDX 和 React 指南

Gatsby

Gatsby 有自己的插件来支持 MDX。请参阅 gatsby-plugin-mdx,了解如何在 Gatsby 中使用 MDX。

Next.js

扩展示例

next.config.js

  1. import nextMdx from '@next/mdx'
  2. const withMdx = nextMdx({
  3. // 默认情况下只支持 `.mdx` 扩展名。
  4. extension: /\.mdx?$/,
  5. options: {/* otherOptions… */}
  6. })
  7. const nextConfig = withMdx({
  8. // 将 MDX 文件作为页面支持:
  9. pageExtensions: ['md', 'mdx', 'tsx', 'ts', 'jsx', 'js'],
  10. })
  11. export default nextConfig

Next.js 具有自己的 MDX 集成。安装并配置 @next/mdx

请勿在 Next 中使用 providerImportSource@mdx-js/react 注入组件。而是在 src// 中添加一个 mdx-components.tsx 文件。有关更多信息,请参阅 nextjs.org 上配置 MDX

Parcel

Parcel 有自己的插件来支持 MDX。请参阅 @parcel/transformer-mdx,了解如何在 Parcel 中使用 MDX。

JSX 运行时

Emotion

扩展示例

example.js

  1. import {CacheProvider} from '@emotion/react'
  2. import {mdx} from '@mdx-js/react'
  3. import {cache} from 'emotion'
  4. /** @type {import('@mdx-js/react').MDXProviderProps['components']} */
  5. const components = {}
  6. const content = mdx('Hello, world!', {components})
  7. // 在组件中:
  8. ReactDOM.render(
  9. <CacheProvider value={cache}>{content}</CacheProvider>,
  10. document.body
  11. )

Emotion 是使用我们的 MDX 插件时的默认支持的 JSX 运行时。安装并配置 @mdx-js/react。有关如何在 Emotion 中使用 MDX 的信息,请参阅 Emotion 的 MDX 文档

Preact

扩展示例

example.js

  1. import {h, render} from 'preact'
  2. import {mdx} from '@mdx-js/preact'
  3. /** @type {import('@mdx-js/preact').MDXProviderComponentsProp} */
  4. const components = {}
  5. const content = mdx('Hello, world!', {components})
  6. // 在组件中:
  7. render(content, document.body)

Preact 是使用我们的 MDX 插件时的默认支持的 JSX 运行时。安装并配置 @mdx-js/preact。有关如何在 Preact 中使用 MDX 的信息,请参阅 Preact 的 MDX 文档

React

扩展示例

example.js

  1. import ReactDOM from 'react-dom'
  2. import {mdx} from '@mdx-js/react'
  3. /** @type {import('@mdx-js/react').MDXProviderComponentsProp} */
  4. const components = {}
  5. const content = mdx('Hello, world!', {components})
  6. // 在组件中:
  7. ReactDOM.render(content, document.body)

React 是使用我们的 MDX 插件时的默认支持的 JSX 运行时。安装并配置 @mdx-js/react。有关如何在 React 中使用 MDX 的信息,请参阅 React 的 MDX 文档

Solid

扩展示例

example.js

  1. import {createSignal, JSX} from 'solid-js'
  2. import {mdx} from '@mdx-js/solid'
  3. const components = {} as Record<string, (props: any) => JSX.Element>
  4. const content = mdx('Hello, world!', {components})
  5. // 在组件中:
  6. const [content, setContent] = createSignal<JSX.Element>()
  7. setContent(content)

Solid 是使用我们的 MDX 插件时的默认支持的 JSX 运行时。安装并配置 @mdx-js/solid。有关如何在 Solid 中使用 MDX 的信息,请参阅 Solid 的 MDX 文档

Svelte

扩展示例

example.js

  1. import {render} from '@testing-library/svelte'
  2. import {mdx} from '@mdx-js/svelte'
  3. /** @type {import('@mdx-js/svelte').ComponentMap} */
  4. const components = {}
  5. const content = mdx('Hello, world!', {components})
  6. // 在组件中:
  7. const {container} = render(content)

Svelte 是使用我们的 MDX 插件时的默认支持的 JSX 运行时。安装并配置 @mdx-js/svelte。有关如何在 Svelte 中使用 MDX 的信息,请参阅 Svelte 的 MDX 文档

Theme UI

扩展示例

example.js

  1. import {ThemeProvider} from '@theme-ui/react'
  2. import {mdx} from '@mdx-js/react'
  3. import {theme} from './theme'
  4. /** @type {import('@mdx-js/react').MDXProviderComponentsProp} */
  5. const components = {}
  6. const content = mdx('Hello, world!', {components})
  7. // 在组件中:
  8. ReactDOM.render(
  9. <ThemeProvider theme={theme}>{content}</ThemeProvider>,
  10. document.body
  11. )

Theme UI 是使用我们的 MDX 插件时的默认支持的 JSX 运行时。安装并配置 @mdx-js/react。有关如何在 Theme UI 中使用 MDX 的信息,请参阅 Theme UI 的 MDX 文档

Vue

扩展示例

example.js

  1. import {createApp} from 'vue'
  2. import {mdx} from '@mdx-js/vue'
  3. const app = createApp({/* … */})
  4. /** @type {import('@mdx-js/vue').MDXProviderComponentsProp} */
  5. const components = {}
  6. const content = mdx('Hello, world!', {components})
  7. // 在组件中:
  8. app.mount(content)

Vue 是使用我们的 MDX 插件时的默认支持的 JSX 运行时。安装并配置 @mdx-js/vue。有关如何在 Vue 中使用 MDX 的信息,请参阅 Vue 的 MDX 文档

TypeScript

扩展类型导入的示例

首先安装该包:

Shell

  1. npm install @types/mdx

……TypeScript 应该会自动识别到:

example.js

  1. import Post from './post.mdx' // `Post` 现在已经有了类型。

我们的包是用 TypeScript 编写的。要使类型起作用,必须对 JSX 命名空间进行类型化。可以通过安装和使用您的框架的类型,比如 @types/react 来完成这一点。

要为导入的 .mdx.md 等文件启用类型,请安装并使用 @types/mdx。该包还导出了一些有用的类型,例如 MDXComponents,它表示 components 属性。您可以这样导入它们:

example.ts

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

安全性

MDX 是一种编程语言。如果您信任您的作者,那没问题。如果不信任,那就不安全。

不要让互联网上的随机人员编写 MDX。如果您这样做了,您可能会考虑使用带有 sandbox<iframe>,但是安全性很难保证,似乎也不是 100%。对于 Node.js,vm2 听起来很有趣。但是您可能还应该使用 Docker 或类似的东西沙盒整个操作系统,执行速率限制,并确保进程在运行时间过长时可以被终止。