Skip to content

Commit

Permalink
Add docs on run and compiling MDX on demand
Browse files Browse the repository at this point in the history
Related-to: GH-1792.
  • Loading branch information
wooorm committed Nov 13, 2021
1 parent cd95df6 commit 61091c3
Show file tree
Hide file tree
Showing 2 changed files with 171 additions and 12 deletions.
101 changes: 101 additions & 0 deletions docs/guides/mdx-on-demand.server.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import {Note} from '../_component/note.server.js'
export const navSortSelf = 6
export const info = {
author: [
{name: 'Titus Wormer', github: 'wooorm', twitter: 'wooorm'}
],
published: new Date('2021-11-13'),
modified: new Date('2021-11-13')
}

# MDX on demand

This guide shows how to use `@mdx-js/mdx` to compile MDX on the server and run
the result on clients. {/* more */}
Some frameworks, such as Next.js and Remix, make it easy to split work between
servers and clients.
Using that it’s possible to for example do most of the work on demand on the
server instead of at build time, then pass the resulting data to clients, where
they finally use it.

This is similar to what [`mdx-bundler`][mdx-bundler] and
[`next-mdx-remote`][next-mdx-remote] also do, but they add more features.

## Quick example

On the server:

```js path="server.js"
import {compile} from '@mdx-js/mdx'

const code = String(await compile('# hi', {outputFormat: 'function-body' /* …otherOptions */ }))
// To do: send `code` to the client somehow.
```

On the client:

```js path="client.js"
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)
```

`Content` is now an `MDXContent` component that you can use like normal in your
framework (see [§ Using MDX][use]).

More information is available in the API docs of `@mdx-js/mdx` for
[`compile`][compile] and [`run`][run].
For other use cases, you can also use [`evaluate`][eval], which both compiles
and runs in one.

<Note type="info">
**Note**: MDX is not a bundler (esbuild, webpack, and Rollup are bundlers):
you can’t import other code from the server within the string of MDX and get a
nicely minified bundle out or so.
</Note>

## Next.js example

Some frameworks let you write the server and client code in one file, such as
Next.

```js path="pages/hello.js"
import {useState, useEffect, Fragment} from 'react'
import * as runtime from 'react/jsx-runtime'
import {compile, run} from '@mdx-js/mdx'

export default function Page({code}) {
const [mdxModule, setMdxModule] = useState()
const Content = mdxModule ? mdxModule.default : Fragment

useEffect(() => {
;(async () => {
setMdxModule(await run(code, runtime))
})()
}, [code])

return <Content />
}

export async function getStaticProps() {
const code = String(
await compile('# hi', {outputFormat: 'function-body' /* …otherOptions */})
)
return {props: {code}}
}
```

[mdx-bundler]: https://github.com/kentcdodds/mdx-bundler

[next-mdx-remote]: https://github.com/hashicorp/next-mdx-remote

[use]: /docs/using-mdx/

[compile]: /packages/mdx/#compilefile-options

[run]: /packages/mdx/#runfunctionbody-options

[eval]: /packages/mdx/#evaluatefile-options
82 changes: 70 additions & 12 deletions packages/mdx/readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ MDX compiler.
* [`compileSync(file, options?)`](#compilesyncfile-options)
* [`evaluate(file, options)`](#evaluatefile-options)
* [`evaluateSync(file, options)`](#evaluatesyncfile-options)
* [`run(functionBody, options)`](#runfunctionbody-options)
* [`runSync(functionBody, options)`](#runsyncfunctionbody-options)
* [`createProcessor(options)`](#createprocessoroptions)
* [Types](#types)
* [Architecture](#architecture)
Expand Down Expand Up @@ -114,7 +116,9 @@ This package exports the following identifiers:
[`compile`][compile],
[`compileSync`][compile-sync],
[`evaluate`][eval],
[`evaluateSync`][eval-sync], and
[`evaluateSync`](#evaluatesyncfile-options),
[`run`][run],
[`runSync`](#runsyncfunctionbody-options), and
[`createProcessor`][create-processor].
There is no default export.

Expand Down Expand Up @@ -240,7 +244,7 @@ because in those it affects *which* files are “registered”:
Output format to generate (`'program' | 'function-body'`, default: `'program'`).
In most cases `'program'` should be used, as it results in a whole program.
Internally, [`evaluate`][eval] uses `outputFormat: 'function-body'` to compile
to code that can be `eval`d.
to code that can be `eval`ed with [`run`][run].
In some cases, you might want to do what `evaluate` does in separate steps
yourself, such as when compiling on the server and running on the client.

Expand Down Expand Up @@ -725,16 +729,14 @@ When possible please use the async `compile`.

### `evaluate(file, options)`

Compile and run MDX.
☢️ It’s called **evaluate** because it `eval`s JavaScript.
> ☢️ **Danger**: It’s called **evaluate** because it `eval`s JavaScript.
[Compile][] and [run][] MDX.
When possible, please use `compile`, write to a file, and then run with Node,
or use one of the
[§ Integrations][integrations].
But if you trust your content, `evaluate` can work.

`evaluate` wraps code in an [`AsyncFunction`][async-function], `evaluateSync`
uses a normal [`Function`][function].

Typically, `import` (or `export … from`) do not work here.
They can be compiled to dynamic `import()` by passing
[`options.useDynamicImport`][usedynamicimport].
Expand Down Expand Up @@ -832,10 +834,70 @@ be diffed:

### `evaluateSync(file, options)`

> ☢️ **Danger**: It’s called **evaluate** because it `eval`s JavaScript.
Compile and run MDX.
Synchronous version of [`evaluate`][eval].
When possible please use the async `evaluate`.

### `run(functionBody, options)`

> ☢️ **Danger**: This `eval`s JavaScript.
Run MDX compiled as [`options.outputFormat: 'function-body'`][outputformat].

###### `options`

You can pass `jsx`, `jsxs`, and `Fragment` from an automatic JSX runtime as
`options`.
You can also pass `useMDXComponents` from a provider in options if the MDX is
compiled with `options.providerImportSource: '#'` (the exact value of this
compile option doesn’t matter).
All other options have to be passed to `compile` instead.

###### Returns

`Promise<MDXModule>` — See `evaluate`

<details>
<summary>Example</summary>

On the server:

```js
import {compile} from '@mdx-js/mdx'

const code = String(await compile('# hi', {outputFormat: 'function-body'}))
// To do: send `code` to the client somehow.
```

On the client:

```js
import * as runtime from 'react/jsx-runtime'
import {run} from '@mdx-js/mdx'

const code = '' // To do: get `code` from server somehow.

const {default: Content} = await run(code, runtime)
```

…yields:

```js
[Function: MDXContent]
```

</details>

### `runSync(functionBody, options)`

> ☢️ **Danger**: This `eval`s JavaScript.
Run MDX.
Synchronous version of [`run`][run].
When possible please use the async `run`.

### `createProcessor(options)`

Create a unified processor to compile MDX to JS.
Expand Down Expand Up @@ -985,7 +1047,7 @@ abide by its terms.

[eval]: #evaluatefile-options

[eval-sync]: #evaluatesyncfile-options
[run]: #runfunctionbody-options

[create-processor]: #createprocessoroptions

Expand All @@ -1009,10 +1071,6 @@ abide by its terms.

[usedynamicimport]: #optionsusedynamicimport

[async-function]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/AsyncFunction

[function]: https://developer.mozilla.org/docs/JavaScript/Reference/Global_Objects/Function

[unified]: https://github.com/unifiedjs/unified

[processor]: https://github.com/unifiedjs/unified#processor
Expand Down

0 comments on commit 61091c3

Please sign in to comment.