作者:Titus Wormer
按需 MDX
本指南展示了如何使用 @mdx-js/mdx 在服务器上编译 MDX 并在客户端运行 结果。 一些框架,如 Next.js 和 Remix,使得在服务器和客户端之间分割工作变得容易。 使用它,例如可以按需在服务器上完成大部分工作,而不是在构建时, 然后将结果数据传递给客户端,客户端 最终使用它。
这类似于人们有时使用 mdx-bundler 或 next-mdx-remote 的方式,但 MDX 也支持它。
快速示例
在服务器上:
import {compile} from '@mdx-js/mdx'
const code = String(await compile('# hi', {
outputFormat: 'function-body',
/* …otherOptions */
}))
// To do: send `code` to the client somehow.
(alias) function compile(vfileCompatible: Readonly<Compatible>, compileOptions?: Readonly<CompileOptions> | null | undefined): Promise<VFile>
import compileCompile MDX to JS.
- @param vfileCompatible MDX document to parse.
- @param compileOptions Compile configuration (optional).
- @return Promise to compiled file.
const code: stringvar String: StringConstructor
(value?: any) => stringAllows manipulation and formatting of text strings and determination and location of substrings within strings.
(alias) compile(vfileCompatible: Readonly<Compatible>, compileOptions?: Readonly<CompileOptions> | null | undefined): Promise<VFile>
import compileCompile MDX to JS.
- @param vfileCompatible MDX document to parse.
- @param compileOptions Compile configuration (optional).
- @return Promise to compiled file.
(property) outputFormat?: "function-body" | "program" | null | undefinedOutput format to generate (default: 'program'); in most cases 'program' should be used, it results in a whole program; internally evaluate uses 'function-body' to compile to code that can be passed to run; in some cases, you might want what evaluate does in separate steps, such as when compiling on the server and running on the client.
在客户端:
import {run} from '@mdx-js/mdx'
import * as runtime from 'react/jsx-runtime'
const code = '' // To do: get `code` from server somehow.
const {default: Content} = await run(code, {...runtime, baseUrl: import.meta.url})
(alias) function run(code: {
toString(): string;
}, options: RunOptions): Promise<MDXModule>
import runRun code compiled with outputFormat: 'function-body'.
☢️ Danger: this
evals JavaScript.
- @param code JavaScript function body to run.
- @param options Configuration (required).
- @return Promise to a module; the result is an object with a
defaultfield set to the component; anything else that was exported is available too.
import runtimeconst code: ""(property) MDXModule.default: MDXContentA functional JSX component which renders the content of the MDX file.
const Content: MDXContentA functional JSX component which renders the content of the MDX file.
(alias) run(code: {
toString(): string;
}, options: RunOptions): Promise<MDXModule>
import runRun code compiled with outputFormat: 'function-body'.
☢️ Danger: this
evals JavaScript.
- @param code JavaScript function body to run.
- @param options Configuration (required).
- @return Promise to a module; the result is an object with a
defaultfield set to the component; anything else that was exported is available too.
const code: ""import runtime(property) baseUrl?: string | URL | null | undefinedUse this URL as import.meta.url and resolve import and export … from relative to it (optional, example: import.meta.url); this option can also be given at compile time in CompileOptions; you should pass this (likely at runtime), as you might get runtime errors when using import.meta.url / import / export … from otherwise.
The type of import.meta.
If you need to declare that a given property exists on import.meta, this type may be augmented via interface merging.
(property) ImportMeta.url: stringThe absolute file: URL of the module.
This is defined exactly the same as it is in browsers providing the URL of the current module file.
This enables useful patterns such as relative file loading:
import { readFileSync } from 'node:fs';
const buffer = readFileSync(new URL('./data.proto', import.meta.url));
Content 现在是一个 MDXContent 组件,你可以在你的 框架中正常使用(参见§ 使用 MDX)。
更多信息可在 @mdx-js/mdx 的 API 文档中找到,关于 compile 和 run。 对于其他用例,你也可以使用 evaluate,它在一个步骤中 编译并运行。
注意:MDX 不是打包器(esbuild、webpack 和 Rollup 是打包器): 你不能从服务器上的 MDX 字符串中导入其他代码并获得 精简的包。
Next.js 示例
一些框架让你在一个文件中编写服务器和客户端代码,例如 Next。
/**
* @import {MDXModule} from 'mdx/types.js'
* @import {Dispatch, ReactElement, SetStateAction} from 'react'
*/
import {compile, run} from '@mdx-js/mdx'
import {Fragment, useEffect, useState} from 'react'
import * as runtime from 'react/jsx-runtime'
/**
* @param {{code: string}} props
* @returns {ReactElement}
*/
export default function Page({code}) {
/** @type {[MDXModule | undefined, Dispatch<SetStateAction<MDXModule | undefined>>]} */
const [mdxModule, setMdxModule] = useState()
const Content = mdxModule ? mdxModule.default : Fragment
useEffect(
function () {
;(async function () {
setMdxModule(await run(code, {...runtime, baseUrl: import.meta.url}))
})()
},
[code]
)
return <Content />
}
export async function getStaticProps() {
const code = String(
await compile('# hi', {
outputFormat: 'function-body'
/* …otherOptions */
})
)
return {props: {code}}
}
(alias) function compile(vfileCompatible: Readonly<Compatible>, compileOptions?: Readonly<CompileOptions> | null | undefined): Promise<VFile>
import compileCompile MDX to JS.
- @param vfileCompatible MDX document to parse.
- @param compileOptions Compile configuration (optional).
- @return Promise to compiled file.
(alias) function run(code: {
toString(): string;
}, options: RunOptions): Promise<MDXModule>
import runRun code compiled with outputFormat: 'function-body'.
☢️ Danger: this
evals JavaScript.
- @param code JavaScript function body to run.
- @param options Configuration (required).
- @return Promise to a module; the result is an object with a
defaultfield set to the component; anything else that was exported is available too.
(alias) const Fragment: ExoticComponent<FragmentProps>
import FragmentLets you group elements without a wrapper node.
- @see {@link https://react.dev/reference/react/Fragment React Docs}
- @example
import { Fragment } from 'react'; <Fragment> <td>Hello</td> <td>World</td> </Fragment> - @example
// Using the <></> shorthand syntax: <> <td>Hello</td> <td>World</td> </>
(alias) function useEffect(effect: EffectCallback, deps?: DependencyList): void
import useEffectAccepts a function that contains imperative, possibly effectful code.
- @param effect Imperative function that can return a cleanup function
- @param deps If present, effect will only activate if the values in the list change.
- @version 16.8.0
- @see {@link https://react.dev/reference/react/useEffect}
(alias) function useState<S>(initialState: S | (() => S)): [S, Dispatch<SetStateAction<S>>] (+1 overload)
import useStateReturns a stateful value, and a function to update it.
- @version 16.8.0
- @see {@link https://react.dev/reference/react/useState}
import runtimefunction Page({ code }: {
code: string;
}): ReactElement- @param props
- @returns
(parameter) code: stringconst mdxModule: MDXModule | undefinedconst setMdxModule: Dispatch<SetStateAction<MDXModule | undefined>>(alias) useState<MDXModule>(): [MDXModule | undefined, Dispatch<SetStateAction<MDXModule | undefined>>] (+1 overload)
import useStateReturns a stateful value, and a function to update it.
- @version 16.8.0
- @see {@link https://react.dev/reference/react/useState}
const Content: ExoticComponent<FragmentProps> | MDXContentconst mdxModule: MDXModule | undefinedconst mdxModule: MDXModule(property) MDXModule.default: MDXContentA functional JSX component which renders the content of the MDX file.
(alias) const Fragment: ExoticComponent<FragmentProps>
import FragmentLets you group elements without a wrapper node.
- @see {@link https://react.dev/reference/react/Fragment React Docs}
- @example
import { Fragment } from 'react'; <Fragment> <td>Hello</td> <td>World</td> </Fragment> - @example
// Using the <></> shorthand syntax: <> <td>Hello</td> <td>World</td> </>
(alias) useEffect(effect: EffectCallback, deps?: DependencyList): void
import useEffectAccepts a function that contains imperative, possibly effectful code.
- @param effect Imperative function that can return a cleanup function
- @param deps If present, effect will only activate if the values in the list change.
- @version 16.8.0
- @see {@link https://react.dev/reference/react/useEffect}
const setMdxModule: (value: SetStateAction<MDXModule | undefined>) => void(alias) run(code: {
toString(): string;
}, options: RunOptions): Promise<MDXModule>
import runRun code compiled with outputFormat: 'function-body'.
☢️ Danger: this
evals JavaScript.
- @param code JavaScript function body to run.
- @param options Configuration (required).
- @return Promise to a module; the result is an object with a
defaultfield set to the component; anything else that was exported is available too.
(parameter) code: stringimport runtime(property) baseUrl?: string | URL | null | undefinedUse this URL as import.meta.url and resolve import and export … from relative to it (optional, example: import.meta.url); this option can also be given at compile time in CompileOptions; you should pass this (likely at runtime), as you might get runtime errors when using import.meta.url / import / export … from otherwise.
The type of import.meta.
If you need to declare that a given property exists on import.meta, this type may be augmented via interface merging.
(property) ImportMeta.url: stringThe absolute file: URL of the module.
This is defined exactly the same as it is in browsers providing the URL of the current module file.
This enables useful patterns such as relative file loading:
import { readFileSync } from 'node:fs';
const buffer = readFileSync(new URL('./data.proto', import.meta.url));
(parameter) code: stringconst Content: ExoticComponent<FragmentProps> | MDXContentfunction getStaticProps(): Promise<{
props: {
code: string;
};
}>const code: stringvar String: StringConstructor
(value?: any) => stringAllows manipulation and formatting of text strings and determination and location of substrings within strings.
(alias) compile(vfileCompatible: Readonly<Compatible>, compileOptions?: Readonly<CompileOptions> | null | undefined): Promise<VFile>
import compileCompile MDX to JS.
- @param vfileCompatible MDX document to parse.
- @param compileOptions Compile configuration (optional).
- @return Promise to compiled file.
(property) outputFormat?: "function-body" | "program" | null | undefinedOutput format to generate (default: 'program'); in most cases 'program' should be used, it results in a whole program; internally evaluate uses 'function-body' to compile to code that can be passed to run; in some cases, you might want what evaluate does in separate steps, such as when compiling on the server and running on the client.
(property) props: {
code: string;
}(property) code: string