unified

Project: mdx-js/mdx

Package: @mdx-js/mdx@2.2.1

  1. Dependents: 0
  2. MDX compiler
  1. remark 214
  2. markdown 154
  3. mdx 35
  4. jsx 18
  5. mdxast 5

@mdx-js/mdx

Build Coverage Downloads Size Sponsors Backers Chat

MDX compiler.

Contents

What is this?

This package is a compiler that turns MDX into JavaScript. It can also evaluate MDX code.

When should I use this?

This is the core compiler for turning MDX into JavaScript which gives you the most control. If you’re using a bundler (Rollup, esbuild, webpack), a site builder (Next.js), or build system (Vite) which comes with a bundler, you’re better off using an integration: see § Integrations.

Install

This package is ESM only. In Node.js (version 16+), install with npm:

npm install @mdx-js/mdx

In Deno with esm.sh:

import {compile} from 'https://esm.sh/@mdx-js/mdx@3'

In browsers with esm.sh:

<script type="module">
  import {compile} from 'https://esm.sh/@mdx-js/mdx@3?bundle'
</script>

Use

Say we have an MDX document, example.mdx:

export function Thing() {
  return <>World!</>
}

# Hello, <Thing />

…and some code in example.js to compile example.mdx to JavaScript:

import fs from 'node:fs/promises'
import {compile} from '@mdx-js/mdx'

const compiled = await compile(await fs.readFile('example.mdx'))

console.log(String(compiled))

Yields roughly:

import {Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs} from 'react/jsx-runtime'

export function Thing() {
  return _jsx(_Fragment, {children: 'World!'})
}

function _createMdxContent(props) {
  const _components = {h1: 'h1', ...props.components}
  return _jsxs(_components.h1, {children: ['Hello, ', _jsx(Thing, {})]})
}

export default function MDXContent(props = {}) {
  const {wrapper: MDXLayout} = props.components || {}
  return MDXLayout
    ? _jsx(MDXLayout, {...props, children: _jsx(_createMdxContent, {...props})})
    : _createMdxContent(props)
}

See § Using MDX for more on how MDX work and how to use the result.

API

This package exports the following identifiers: compile, compileSync, createProcessor, evaluate, evaluateSync, nodeTypes, run, and runSync. There is no default export.

compile(file, options?)

Compile MDX to JS.

Parameters
Returns

Promise to compiled file (Promise<VFile>).

Examples

The input value for file can be many different things. You can pass a string, Uint8Array in UTF-8, VFile, or anything that can be given to new VFile.

import {compile} from '@mdx-js/mdx'
import {VFile} from 'vfile'

await compile(':)')
await compile(Buffer.from(':-)'))
await compile({path: 'path/to/file.mdx', value: '🥳'})
await compile(new VFile({path: 'path/to/file.mdx', value: '🤭'}))

The output VFile can be used to access more than the generated code:

import {compile} from '@mdx-js/mdx'
import remarkPresetLintConsistent from 'remark-preset-lint-consistent' // Lint rules to check for consistent markdown.
import {reporter} from 'vfile-reporter'

const file = await compile('*like this* or _like this_?', {remarkPlugins: [remarkPresetLintConsistent]})

console.error(reporter(file))

Yields:

  1:16-1:27  warning  Emphasis should use `*` as a marker  emphasis-marker  remark-lint

⚠ 1 warning

compileSync(file, options?)

Synchronously compile MDX to JS.

When possible please use the async compile.

Parameters
Returns

Compiled file (VFile).

createProcessor(options?)

Create a processor to compile markdown or MDX to JavaScript.

Note: format: 'detect' is not allowed in ProcessorOptions.

Parameters
Returns

Processor (Processor from unified).

evaluate(file, options)

Compile and run MDX.

When you trust your content, evaluate can work. When possible, use compile, write to a file, and then run with Node or use one of the § Integrations.

☢️ Danger: it’s called evaluate because it evals JavaScript.

Parameters
Returns

Promise to a module (Promise<MDXModule> from mdx/types.js).

The result is an object with a default field set to the component; anything else that was exported is available too. For example, assuming the contents of example.mdx from § Use was in file, then:

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

console.log(await evaluate(file, runtime))

…yields:

{Thing: [Function: Thing], default: [Function: MDXContent]}
Notes

Compiling (and running) MDX takes time.

If you are live-rendering a string of MDX that often changes using a virtual DOM based framework (such as React), one performance improvement is to call the MDXContent component yourself. The reason is that the evaluate creates a new function each time, which cannot be diffed:

 const {default: MDXContent} = await evaluate('…')

-<MDXContent {...props} />
+MDXContent(props)

evaluateSync(file, options)

Compile and run MDX, synchronously.

When possible please use the async evaluate.

☢️ Danger: it’s called evaluate because it evals JavaScript.

Parameters
Returns

Module (MDXModule from mdx/types.js).

nodeTypes

List of node types made by mdast-util-mdx, which have to be passed through untouched from the mdast tree to the hast tree (Array<string>).

run(code, options)

Run code compiled with outputFormat: 'function-body'.

☢️ Danger: this evals JavaScript.

Parameters
Returns

Promise to a module (Promise<MDXModule> from mdx/types.js); the result is an object with a default field set to the component; anything else that was exported is available too.

Example

On the server:

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:

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})

console.log(Content)

…yields:

[Function: MDXContent]

runSync(code, options)

Run code, synchronously.

When possible please use the async run.

☢️ Danger: this evals JavaScript.

Parameters
Returns

Module (MDXModule from mdx/types.js).

CompileOptions

Configuration for compile (TypeScript type).

CompileOptions is the same as ProcessorOptions with the exception that the format option supports a 'detect' value, which is the default. The 'detect' format means to use 'md' for files with an extension in mdExtensions and 'mdx' otherwise.

Type
/**
 * Configuration for `compile`
 */
type CompileOptions = Omit<ProcessorOptions, 'format'> & {
  /**
   * Format of `file` (default: `'detect'`).
   */
  format?: 'detect' | 'md' | 'mdx' | null | undefined
}

EvaluateOptions

Configuration for evaluate (TypeScript type).

EvaluateOptions is the same as CompileOptions, except that the options baseUrl, jsx, jsxImportSource, jsxRuntime, outputFormat, pragma, pragmaFrag, pragmaImportSource, and providerImportSource are not allowed, and that RunOptions are also used.

Type
/**
 * Configuration for `evaluate`.
 */
type EvaluateOptions = Omit<
  CompileOptions,
  | 'baseUrl' // Note that this is also in `RunOptions`.
  | 'jsx'
  | 'jsxImportSource'
  | 'jsxRuntime'
  | 'outputFormat'
  | 'pragma'
  | 'pragmaFrag'
  | 'pragmaImportSource'
  | 'providerImportSource'
> &
  RunOptions

Fragment

Represent the children, typically a symbol (TypeScript type).

Type
type Fragment = unknown

Jsx

Create a production element (TypeScript type).

Parameters
Returns

Element from your framework (JSX.Element).

JsxDev

Create a development element (TypeScript type).

Parameters

ProcessorOptions

Configuration for createProcessor (TypeScript type).

Fields

RunOptions

Configuration to run compiled code (TypeScript type).

Fragment, jsx, and jsxs are used when the code is compiled in production mode (development: false). Fragment and jsxDEV are used when compiled in development mode (development: true). useMDXComponents is used when the code is compiled with providerImportSource: '#' (the exact value of this compile option doesn’t matter).

Fields
Examples

A /jsx-runtime module will expose Fragment, jsx, and jsxs:

import * as runtime from 'react/jsx-runtime'

const {default: Content} = await evaluate('# hi', {...runtime, baseUrl: import.meta.url, ...otherOptions})

A /jsx-dev-runtime module will expose Fragment and jsxDEV:

import * as runtime from 'react/jsx-dev-runtime'

const {default: Content} = await evaluate('# hi', {development: true, baseUrl: import.meta.url, ...runtime, ...otherOptions})

Our providers will expose useMDXComponents:

import * as provider from '@mdx-js/react'
import * as runtime from 'react/jsx-runtime'

const {default: Content} = await evaluate('# hi', {...provider, ...runtime, baseUrl: import.meta.url, ...otherOptions})

UseMdxComponents

Get components (TypeScript type).

Parameters

There are no parameters.

Returns

Components (MDXComponents from mdx/types.js).

Types

This package is fully typed with TypeScript. It exports the additional types CompileOptions, EvaluateOptions, Fragment, Jsx, JsxDev, ProcessorOptions, RunOptions, and UseMdxComponents.

For types of evaluated MDX to work, make sure the TypeScript JSX namespace is typed. This is done by installing and using the types of your framework, such as @types/react. See § Types on our website for information.

Architecture

To understand what this project does, it’s very important to first understand what unified does: please read through the unifiedjs/unified readme (the part until you hit the API section is required reading).

@mdx-js/mdx is a unified pipeline — wrapped so that most folks don’t need to know about unified: core.js#L65. The processor goes through these steps:

  1. parse MDX (serialized markdown with embedded JSX, ESM, and expressions) to mdast (markdown syntax tree)
  2. transform through remark (markdown ecosystem)
  3. transform mdast to hast (HTML syntax tree)
  4. transform through rehype (HTML ecosystem)
  5. transform hast to esast (JS syntax tree)
  6. do the work needed to get a component
  7. transform through recma (JS ecosystem)
  8. serialize esast as JavaScript

The input is MDX (serialized markdown with embedded JSX, ESM, and expressions). The markdown is parsed with micromark/micromark and the embedded JS with one of its extensions micromark/micromark-extension-mdxjs (which in turn uses acorn). Then syntax-tree/mdast-util-from-markdown and its extension syntax-tree/mdast-util-mdx are used to turn the results from the parser into a syntax tree: mdast.

Markdown is closest to the source format. This is where remark plugins come in. Typically, there shouldn’t be much going on here. But perhaps you want to support GFM (tables and such) or frontmatter? Then you can add a plugin here: remark-gfm or remark-frontmatter, respectively.

After markdown, we go to hast (HTML). This transformation is done by syntax-tree/mdast-util-to-hast. Wait, why, what is HTML needed? Part of the reason is that we care about HTML semantics: we want to know that something is an <a>, not whether it’s a link with a resource ([text](url)) or a reference to a defined link definition ([text][id]\n\n[id]: url). So an HTML AST is closer to where we want to go. Another reason is that there are many things folks need when they go MDX -> JS, markdown -> HTML, or even folks who only process their HTML -> HTML: use cases other than MDX. By having a single AST in these cases and writing a plugin that works on that AST, that plugin can supports all these use cases (for example, rehypejs/rehype-highlight for syntax highlighting or rehypejs/rehype-katex for math). So, this is where rehype plugins come in: most of the plugins, probably.

Then we go to JavaScript: esast (JS; an AST which is compatible with estree but looks a bit more like other unist ASTs). This transformation is done by syntax-tree/hast-util-to-estree. This is a new ecosystem that does not have utilities or plugins yet. But it’s where @mdx-js/mdx does its thing: where it adds imports/exports, where it compiles JSX away into _jsx() calls, and where it does the other cool things that it provides.

Finally, The output is serialized JavaScript. That final step is done by astring, a small and fast JS generator.

Compatibility

Projects maintained by the unified collective are compatible with maintained versions of Node.js.

When we cut a new major release, we drop support for unmaintained versions of Node. This means we try to keep the current release line, @mdx-js/mdx@^3, compatible with Node.js 16.

Security

See § Security on our website for information.

Contribute

See § Contribute on our website for ways to get started. See § Support for ways to get help.

This project has a code of conduct. By interacting with this repository, organization, or community you agree to abide by its terms.

License

MIT © Compositor and Vercel