SkillAgentSearch skills...

Xdm

Just a *really* good MDX compiler. No runtime. With esbuild, Rollup, and webpack plugins

Install / Use

/learn @wooorm/Xdm
About this skill

Quality Score

0/100

Supported Platforms

Universal

README

xdm

[![Build][build-badge]][build] [![Coverage][coverage-badge]][coverage] [![Downloads][downloads-badge]][downloads] [![Size][size-badge]][size]

xdm was a fork (complete rewrite?) that envisioned how mdx-js/mdx could work. All of xdm has now been ported into mdx-js/mdx making xdm no longer needed. Use [mdx-js/mdx][mdxjs] instead.

<details><summary>Read the original readme</summary>

xdm is an MDX compiler that focusses on two things:

  1. Compiling the MDX syntax (markdown + JSX) to JavaScript
  2. Making it easier to use the MDX syntax in different places

This is mostly things I wrote for @mdx-js/mdx which are not slated to be released (soon?) plus some further changes that I think are good ideas (source maps, ESM only, defaulting to an automatic JSX runtime, no Babel, smallish browser size, more docs, import/exports in evaluate, esbuild and Rollup plugins).

There are also some cool experimental features in [👩‍🔬 Lab][lab]!

Install

Use Node 12 or later. Then install xdm with either npm or yarn.

[npm][]:

npm install xdm

[yarn][]:

yarn add xdm

This package is ESM only: Node 12+ is needed to use it and it must be imported instead of required.

Contents

What is MDX?

MDX is different things. The term is sometimes used for a compiler, typically implying @mdx-js/mdx, but there are more. First there was [mdxc][mdxc]. Then came [@mdx-js/mdx][mdxjs]. There’s also [mdsvex][mdsvex]. And now there’s xdm too.

Sometimes the term is used for a runtime/helper library. xdm has no runtime: it’s not needed!

Most often the term is used for the format: markdown + JS(X) (there are some [caveats][]):

## Hello, world!

<div className="note">
  > Some notable things in a block quote!
</div>

See? Most of markdown works! Those XML-like things are not HTML though: they’re JSX. Note that there are some differences in how JSX should be authored: for example, React expects className, whereas Vue expects class. See [§ MDX syntax][mdx-syntax] below for more on how the format works.

Use

This section describes how to use the API. See [§ MDX syntax][mdx-syntax] on how the format works. See [§ Integrations][integrations] on how to use xdm with Babel, esbuild, Rollup, webpack, etc.

Say we have an MDX document, example.mdx:

export const Thing = () => <>World!</>

# Hello, <Thing />

First, a rough idea of what the result will be. The below is not the actual output, but it might help to form a mental model:

/* @jsxRuntime automatic @jsxImportSource react */

export const Thing = () => <>World!</>

export default function MDXContent() {
  return <h1>Hello, <Thing /></h1>
}

Some observations:

  • The output is serialized JavaScript that still needs to be evaluated
  • A comment is injected to configure how JSX is handled
  • It’s a complete file with import/exports
  • A component (MDXContent) is exported

Now for how to get the actual output. Add some code in example.js to compile example.mdx to JavaScript:

import {promises as fs} from 'node:fs'
import {compile} from 'xdm'

main()

async function main() {
  const compiled = await compile(await fs.readFile('example.mdx'))
  console.log(String(compiled))
}

The actual output of running node example.js is:

/* @jsxRuntime automatic @jsxImportSource react */
import {Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs} from 'react/jsx-runtime'

export const Thing = () => _jsx(_Fragment, {children: 'World!'})

function MDXContent(props = {}) {
  let {wrapper: MDXLayout} = props.components || ({})
  return MDXLayout
    ? _jsx(MDXLayout, Object.assign({}, props, {children: _jsx(_createMdxContent, {})}))
    : _createMdxContent()
  function _createMdxContent() {
    let _components = Object.assign({h1: 'h1'}, props.components)
    return _jsxs(_components.h1, {children: ['Hello, ', _jsx(Thing, {})]})
  }
}

export default MDXContent

Some more observations:

  • JSX is compiled away to function calls and an import of React†
  • The content component can be given {components: {wrapper: MyLayout}} to wrap the whole content
  • The content component can be given {components: {h1: MyComponent}} to use something else for the heading

xdm is not coupled to React. You can also use it with Preact, Vue, Emotion, Theme UI, etc.

See [§ MDX content][mdx-content] below on how to use the result.

API

xdm exports the following identifiers: [compile][compile], compileSync, [evaluate][eval], evaluateSync, [run][run], runSync, and createProcessor. There is no default export.

xdm/esbuild.js exports a function as the default export that returns an [esbuild][] plugin.

xdm/rollup.js exports a function as the default export that returns a [Rollup][] plugin.

xdm/webpack.cjs exports a [webpack][] loader as the default export.

There is also xdm/esm-loader.js and xdm/register.cjs, see [👩‍🔬 Lab][lab] for more info.

compile(file, options?)

Compile MDX to JS.

file

MDX document to parse (string, [Buffer][buffer] in UTF-8, [vfile][vfile], or anything that can be given to vfile).

<details> <summary>Example</summary>
import {VFile} from 'vfile'
import {compile} from 'xdm'

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

List of [remark plugins][remark-plugins], presets, and pairs.

<details> <summary>Example</summary>
import remarkFrontmatter from 'remark-frontmatter' // YAML and such.
import remarkGfm from 'remark-gfm' // Tables, strikethrough, tasklists, literal URLs.

await compile(file, {remarkPlugins: [remarkGfm]}) // One plugin.
await compile(file, {remarkPlugins: [[remarkFrontmatter, 'toml']]}) // A plugin with options.
await compile(file, {remarkPlugins: [remarkGfm, remarkFrontmatter]}) // Two plugins.
await compile(file, {remarkPlugins: [[remarkGfm, {singleTilde: false}], remarkFrontmatter]}) // Two plugins, first w/ options.
</details>
options.rehypePlugins

List of [rehype plugins][rehype-plugins], presets, and pairs.

<details> <summary>Example</summary>
import rehypeKatex from 'rehype-katex' // Render math with KaTeX.
import remarkMath from 'remark-math' // Support math like `$so$`.

await compile(file, {remarkPlugins: [remarkMath], rehypePlugins: [rehypeKatex]})

await compile(file, {
  remarkPlugins: [remarkMath],
  // A plugin with options:
  rehypePlugins: [[rehypeKatex, {throwOnError: true, strict: true}]]
})
</details>
options.recmaPlugins

List of recma plugins. This is a new ecosystem, currently in beta, to transform esast trees (JavaScript).

options.remarkRehypeOptions

Options to pass through to [remark-rehype][remark-rehype]. The option allowDangerousHtml will always be set to true and the MDX nodes are passed through. In particular, you might want to pass clobberPrefix, footnoteLabel, and footnoteBackLabel.

<details> <summary>Example</summary>
await compile({value: '…'}, {remarkRehypeOptions: {clobberPrefix: 'comment-1'}})
</details>
options.mdExtensions

List of markdown extensions, with dot (string[], default: ['.md', '.markdown', '.mdown', '.mkdn', '.mkd', '.mdwn', '.mkdown', '.ron']).

options.mdxExtensions

List of MDX extensions, with dot (string[], default: ['.mdx']). Has no effect in compile or evaluate, but does affect [esbuild][], [Rollup][], and the experimental ESM loader + register hook (see [👩‍🔬 Lab][lab]).

options.format

Format the file is in ('detect' | 'mdx' | 'md', default: 'detect').

  • 'detect' — use 'markdown' for files with an extension in mdExtensions and 'mdx' otherwise
  • 'mdx' — treat file as [MDX][mdx-syntax]
View on GitHub
GitHub Stars595
CategoryDevelopment
Updated11d ago
Forks18

Languages

JavaScript

Security Score

100/100

Audited on Mar 18, 2026

No findings