这篇文章解释了如何将 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。)如果您有一个打包器,最简单的开始方法就是使用适用于您的打包器的集成:
- 如果您使用 esbuild,请安装并配置
@mdx-js/esbuild - 如果您使用 Rollup(或 Vite),请安装并配置
@mdx-js/rollup - 如果您使用 webpack(或 Next.js),请安装并配置
@mdx-js/loader
您也可以在没有打包器的情况下使用 MDX:
- 您可以使用
@mdx-js/node-loader在 Node.js 中导入 MDX 文件 - 您可以使用我们的核心编译器
@mdx-js/mdx编译 MDX 文件 - 您可以使用我们的核心编译器
@mdx-js/mdx进行评估(编译 并运行)MDX 文件
有关这些工具的更多信息,请参阅它们各自的专用部分:¶ Next.js、¶ Node.js、¶ Rollup、¶ Vite、¶ esbuild 和 ¶ webpack。
JSX
现在您已经设置了集成或 @mdx-js/mdx 本身,是时候配置您的 JSX 运行时了。
- 如果您使用 React,那就是默认的;可以选择安装并配置
@mdx-js/react - 如果您使用 Preact,请将
ProcessorOptions中的jsxImportSource设置为'preact';可以选择安装并配置@mdx-js/preact - 如果您使用 Svelte,请安装
svelte-jsx,并将ProcessorOptions中的jsxImportSource设置为'svelte-jsx' - 如果您使用 Vue,请将
ProcessorOptions中的jsxImportSource设置为'vue';可以选择安装并配置@mdx-js/vue - 如果您使用 Solid,请将
ProcessorOptions中的jsxImportSource设置为'solid-js/h' - 如果您使用 Emotion,请将
ProcessorOptions中的jsxImportSource设置为'@emotion/react' - 如果您使用 Theme UI,请先安装并配置
@mdx-js/react,然后将您的 MDX 内容包裹在<ThemeProvider />中
其他 JSX 运行时可通过设置 ProcessorOptions 中的 jsxImportSource 来支持。
有关这些工具的更多信息,请参阅它们各自的专用部分:¶ Emotion、¶ Preact、¶ React、¶ Solid、¶ Svelte、¶ Theme UI 和 ¶ Vue。
编辑器
您可以通过为编辑器添加 MDX 支持来增强使用 MDX 的体验:
- 如果您使用 VS Code,请尝试
mdx-js/vscode-mdx - 如果您使用 Vim,请尝试
jxnblk/vim-mdx-js - 如果您使用 Sublime Text,请尝试
jonsuh/mdx-sublime - 如果您使用 **JetBrains IntelliJ/WebStorm
**,请尝试 JetBrains/mdx-intellij-plugin
驱动我们 VS Code 扩展的语法高亮显示也用于 GitHub 上的代码块高亮显示,这些语法高亮显示的维护工作在 wooorm/markdown-tm-language 中进行。
类型
扩展类型导入的示例
首先安装该包:
Shell
npm install @types/mdx
……TypeScript 应该会自动识别到:
example.js
import Post from './post.mdx' // `Post` 现在已经有了类型。
我们的包是用 TypeScript 编写的。要使类型起作用,必须对 JSX 命名空间进行类型化。可以通过安装和使用您的框架的类型,比如 @types/react 来完成这一点。
要为导入的 .mdx、.md 等文件启用类型,请安装并使用 @types/mdx。该包还导出了一些有用的类型,例如 MDXComponents,它表示 components 属性。您可以这样导入它们:
example.ts
import type {MDXComponents} from 'mdx/types.js'
安全性
MDX 是一种编程语言。如果您信任您的作者,那没问题。如果不信任,那就不安全。
不要让互联网上的随机人员编写 MDX。如果您这样做了,您可能会考虑使用带有 sandbox 的 <iframe>,但是安全性很难保证,似乎也不是 100%。对于 Node.js,vm2 听起来很有趣。但是您可能还应该使用 Docker 或类似的东西沙盒整个操作系统,执行速率限制,并确保进程在运行时间过长时可以被终止。
集成
打包器
esbuild
扩展示例
example.js
import mdx from '@mdx-js/esbuild'import esbuild from 'esbuild'await esbuild.build({entryPoints: ['index.mdx'],format: 'esm',outfile: 'output.js',plugins: [mdx({/* jsxImportSource: …, otherOptions… */})]})
我们支持 esbuild。安装并配置 esbuild 插件 @mdx-js/esbuild。根据您使用的 JSX 运行时(React、Preact、Vue 等),配置您的 JSX 运行时。
要使用比您的用户支持的更现代的 JavaScript 特性,配置 esbuild 的 target。
Rollup
扩展示例
rollup.config.js
import mdx from '@mdx-js/rollup'import {babel} from '@rollup/plugin-babel'/** @type {import('rollup').RollupOptions} */const config = {// …plugins: [// …mdx({/* jsxImportSource: …, otherOptions… */})// Babel 是可选的:babel({// 也在之前所用的 `.mdx` 上运行(但现在是 JS):extensions: ['.js', '.jsx', '.cjs', '.mjs', '.md', '.mdx'],// 其他选项…})]}export default config
我们支持 Rollup。安装并配置 Rollup 插件 @mdx-js/rollup。根据您使用的 JSX 运行时(React、Preact、Vue 等),配置您的 JSX 运行时。
要使用比您的用户支持的更现代的 JavaScript 特性,安装并配置 @rollup/plugin-babel。
如果您通过 Vite 使用 Rollup,请参阅 ¶ Vite 获取更多信息。
webpack
扩展示例
webpack.config.js
/** @type {import('webpack').Configuration} */const webpackConfig = {module: {// …rules: [// …{test: /\.mdx?$/,use: [// Babel 是可选的:{loader: 'babel-loader', options: {}},{loader: '@mdx-js/loader',/** @type {import('@mdx-js/loader').Options} */options: {/* jsxImportSource: …, otherOptions… */}}]}]}}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
import mdx from '@mdx-js/rollup'import {defineConfig} from 'vite'const viteConfig = defineConfig({plugins: [mdx(/* jsxImportSource: …, otherOptions… */)]})export default viteConfig
我们支持 Vite。安装并配置 Rollup 插件 @mdx-js/rollup。根据您使用的 JSX 运行时(React、Preact、Vue 等),配置您的 JSX 运行时。
要使用
比您的用户支持的更现代的 JavaScript 特性,配置 Vite 的 build.target。
注意:如果您还使用了 vitejs/vite-plugin-react,您必须在它之前强制运行 @mdx-js/rollup 的 pre 阶段:
vite.config.js
// …const viteConfig = defineConfig({plugins: [{enforce: 'pre', ...mdx(/* jsxImportSource: …, otherOptions… */)},react({include: /\.(jsx|js|mdx|md|tsx|ts)$/})]})// …
另请参阅 ¶ Rollup,它在 Vite 中使用,有关更多信息,请参见 ¶ Vue。使用的更多信息。
编译器
Babel
扩展插件和示例用法
这个插件:
plugin.js
import path from 'node:path'import parser from '@babel/parser'import {compileSync} from '@mdx-js/mdx'import estreeToBabel from 'estree-to-babel'export function babelPluginSyntaxMdx() {// 告诉 Babel 使用不同的解析器。return {parserOverride: babelParserWithMdx}}// 一个 Babel 解析器,它使用 `@mdx-js/mdx` 解析 MDX 文件,并将任何其他内容传递给正常的 Babel 解析器。function babelParserWithMdx(value, options) {if (options.sourceFileName && /\.mdx?$/.test(options.sourceFileName)) {// 不幸的是,Babel 不支持异步解析器。return compileSync({value, path: options.sourceFileName},// 告诉 `@mdx-js/mdx` 返回 Babel 树而不是序列化的 JS。{recmaPlugins: [recmaBabel], /* jsxImportSource: …, otherOptions… */}).result}return parser.parse(value, options)}// “recma” 插件是在 estree 上运行的统一插件(由 `@mdx-js/mdx` 和许多 JS 生态系统的其他部分使用,但不是 Babel)。// 该插件将 `'estree-to-babel'` 定义为编译器,这意味着由 `compileSync` 返回 Babel 树。function recmaBabel() {this.compiler = estreeToBabel}
……可以像这样使用 Babel API:
example.js
import babel from '@babel/core'import {babelPluginSyntaxMdx} from './plugin.js'// 请注意,必须为我们的插件设置文件名,以便它知道这是 MDX 而不是 JS。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
import nextMdx from '@next/mdx'const withMdx = nextMdx({// 默认情况下只支持 `.mdx` 扩展名。extension: /\.mdx?$/,options: {/* otherOptions… */}})const nextConfig = withMdx({// 将 MDX 文件作为页面支持:pageExtensions: ['md', 'mdx', 'tsx', 'ts', 'jsx', 'js'],})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
import {CacheProvider} from '@emotion/react'import {mdx} from '@mdx-js/react'import {cache} from 'emotion'/** @type {import('@mdx-js/react').MDXProviderProps['components']} */const components = {}const content = mdx('Hello, world!', {components})// 在组件中:ReactDOM.render(<CacheProvider value={cache}>{content}</CacheProvider>,document.body)
Emotion 是使用我们的 MDX 插件时的默认支持的 JSX 运行时。安装并配置 @mdx-js/react。有关如何在 Emotion 中使用 MDX 的信息,请参阅 Emotion 的 MDX 文档。
Preact
扩展示例
example.js
import {h, render} from 'preact'import {mdx} from '@mdx-js/preact'/** @type {import('@mdx-js/preact').MDXProviderComponentsProp} */const components = {}const content = mdx('Hello, world!', {components})// 在组件中:render(content, document.body)
Preact 是使用我们的 MDX 插件时的默认支持的 JSX 运行时。安装并配置 @mdx-js/preact。有关如何在 Preact 中使用 MDX 的信息,请参阅 Preact 的 MDX 文档。
React
扩展示例
example.js
import ReactDOM from 'react-dom'import {mdx} from '@mdx-js/react'/** @type {import('@mdx-js/react').MDXProviderComponentsProp} */const components = {}const content = mdx('Hello, world!', {components})// 在组件中:ReactDOM.render(content, document.body)
React 是使用我们的 MDX 插件时的默认支持的 JSX 运行时。安装并配置 @mdx-js/react。有关如何在 React 中使用 MDX 的信息,请参阅 React 的 MDX 文档。
Solid
扩展示例
example.js
import {createSignal, JSX} from 'solid-js'import {mdx} from '@mdx-js/solid'const components = {} as Record<string, (props: any) => JSX.Element>const content = mdx('Hello, world!', {components})// 在组件中:const [content, setContent] = createSignal<JSX.Element>()setContent(content)
Solid 是使用我们的 MDX 插件时的默认支持的 JSX 运行时。安装并配置 @mdx-js/solid。有关如何在 Solid 中使用 MDX 的信息,请参阅 Solid 的 MDX 文档。
Svelte
扩展示例
example.js
import {render} from '@testing-library/svelte'import {mdx} from '@mdx-js/svelte'/** @type {import('@mdx-js/svelte').ComponentMap} */const components = {}const content = mdx('Hello, world!', {components})// 在组件中:const {container} = render(content)
Svelte 是使用我们的 MDX 插件时的默认支持的 JSX 运行时。安装并配置 @mdx-js/svelte。有关如何在 Svelte 中使用 MDX 的信息,请参阅 Svelte 的 MDX 文档。
Theme UI
扩展示例
example.js
import {ThemeProvider} from '@theme-ui/react'import {mdx} from '@mdx-js/react'import {theme} from './theme'/** @type {import('@mdx-js/react').MDXProviderComponentsProp} */const components = {}const content = mdx('Hello, world!', {components})// 在组件中:ReactDOM.render(<ThemeProvider theme={theme}>{content}</ThemeProvider>,document.body)
Theme UI 是使用我们的 MDX 插件时的默认支持的 JSX 运行时。安装并配置 @mdx-js/react。有关如何在 Theme UI 中使用 MDX 的信息,请参阅 Theme UI 的 MDX 文档。
Vue
扩展示例
example.js
import {createApp} from 'vue'import {mdx} from '@mdx-js/vue'const app = createApp({/* … */})/** @type {import('@mdx-js/vue').MDXProviderComponentsProp} */const components = {}const content = mdx('Hello, world!', {components})// 在组件中:app.mount(content)
Vue 是使用我们的 MDX 插件时的默认支持的 JSX 运行时。安装并配置 @mdx-js/vue。有关如何在 Vue 中使用 MDX 的信息,请参阅 Vue 的 MDX 文档。
TypeScript
扩展类型导入的示例
首先安装该包:
Shell
npm install @types/mdx
……TypeScript 应该会自动识别到:
example.js
import Post from './post.mdx' // `Post` 现在已经有了类型。
我们的包是用 TypeScript 编写的。要使类型起作用,必须对 JSX 命名空间进行类型化。可以通过安装和使用您的框架的类型,比如 @types/react 来完成这一点。
要为导入的 .mdx、.md 等文件启用类型,请安装并使用 @types/mdx。该包还导出了一些有用的类型,例如 MDXComponents,它表示 components 属性。您可以这样导入它们:
example.ts
import type {MDXComponents} from 'mdx/types.js'
安全性
MDX 是一种编程语言。如果您信任您的作者,那没问题。如果不信任,那就不安全。
不要让互联网上的随机人员编写 MDX。如果您这样做了,您可能会考虑使用带有 sandbox 的 <iframe>,但是安全性很难保证,似乎也不是 100%。对于 Node.js,vm2 听起来很有趣。但是您可能还应该使用 Docker 或类似的东西沙盒整个操作系统,执行速率限制,并确保进程在运行时间过长时可以被终止。
