wooorm/xdm(opens in a new tab)
Language: JavaScript
Just a *really* good MDX compiler. No runtime. With esbuild, Rollup, and webpack plugins
- stars
- 595
- watchers
- 6
- forks
- 18
- open issues
- 0
- Last updated
- 03/09/2022
Language: JavaScript
Just a *really* good MDX compiler. No runtime. With esbuild, Rollup, and webpack plugins
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 instead.
xdm is an MDX compiler that focusses on two things:
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!
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.
@mdx-js/mdxMDX is different things.
The term is sometimes used for a compiler, typically implying @mdx-js/mdx, but
there are more.
First there was mdxc.
Then came @mdx-js/mdx.
There’s also 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 below for more on how the format works.
This section describes how to use the API. See § MDX syntax on how the format works. See § 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:
MDXContent) is exportedNow 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:
{components: {wrapper: MyLayout}} to
wrap the whole content{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 below on how to use the result.
xdm exports the following identifiers:
compile,
compileSync,
evaluate,
evaluateSync,
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
for more info.
Compile MDX to JS.
MDX document to parse (string, Buffer in UTF-8, vfile,
or anything that can be given to vfile).
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: '🤭'}))
List of remark plugins, presets, and pairs.
Exampleimport 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.
List of rehype plugins, presets, and pairs.
Exampleimport 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}]] })
List of recma plugins. This is a new ecosystem, currently in beta, to transform esast trees (JavaScript).
Options to pass through to 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.
await compile({value: '…'}, {remarkRehypeOptions: {clobberPrefix: 'comment-1'}})
List of markdown extensions, with dot (string[], default: ['.md', '.markdown', '.mdown', '.mkdn', '.mkd', '.mdwn', '.mkdown', '.ron']).
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).
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'md' — treat file as plain vanilla markdownThe format cannot be detected if a file is passed without a path or extension:
mdx will be assumed.
So pass a full vfile (with path) or an object with a path.
compile({value: '…'}) // Seen as MDX compile({value: '…'}, {format: 'md'}) // Seen as markdown compile({value: '…', path: 'readme.md'}) // Seen as markdown // Please do not use `.md` for MDX as other tools won’t know how to handle it. compile({value: '…', path: 'readme.md'}, {format: 'mdx'}) // Seen as MDX compile({value: '…', path: 'readme.md'}, {mdExtensions: []}) // Seen as MDX
This option mostly affects esbuild and Rollup plugins, and the experimental ESM loader + register hook (see 👩🔬 Lab), because in those it affects which files are “registered”:
format: 'mdx' registers the extensions in options.mdxExtensionsformat: 'md' registers the extensions in options.mdExtensionsformat: 'detect' registers both lists of extensionsOutput format to generate ('program' | 'function-body', default: 'program').
In most cases 'program' should be used, as it results in a whole program.
Internally, evaluate uses outputFormat: 'function-body' to compile
to code that can be evaled with 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.
The 'program' format will use import statements to import the runtime (and
optionally provider) and use an export statement to yield the MDXContent
component.
The 'function-body' format will get the runtime (and optionally provider) from
arguments[0], rewrite export statements, and use a return statement to yield
what was exported.
Normally, this output format will throw on import (and export … from)
statements, but you can support them by setting
options.useDynamicImport.
A module example.js:
import {compile} from 'xdm' main('export const no = 3.14\n\n# hi {no}') async function main(code) { console.log(String(await compile(code, {outputFormat: 'program'}))) // Default console.log(String(await compile(code, {outputFormat: 'function-body'}))) }
…yields:
import {Fragment as _Fragment, jsx as _jsx} from 'react/jsx-runtime' export const no = 3.14 function MDXContent(props = {}) { /* … */ } export default MDXContent
const {Fragment: _Fragment, jsx: _jsx} = arguments[0] const no = 3.14 function MDXContent(props = {}) { /* … */ } return {no, default: MDXContent}
Whether to compile to dynamic import expressions (boolean, default: false).
This option applies when options.outputFormat is
'function-body'.
xdm can turn import statements (import x from 'y') into dynamic imports
(const {x} = await import('y')).
This is useful because import statements only work at the top level of
JavaScript modules, whereas import() is available inside function bodies.
When you turn useDynamicImport on, you should probably set options.baseUrl too.
Say we have a couple modules:
// meta.js: export const title = 'World' // numbers.js: export const no = 3.14 // example.js: import {compileSync} from 'xdm' const code = `import {name} from './meta.js' export {no} from './numbers.js' # hi {name}!` console.log(String(compileSync(code, {outputFormat: 'function-body', useDynamicImport: true})))
…now running node example.js yields:
const {Fragment: _Fragment, jsx: _jsx, jsxs: _jsxs} = arguments[0] const {name} = await import('./meta.js') const {no} = await import('./numbers.js') function MDXContent(props = {}) { /* … */ } return {no, default: MDXContent}
Resolve relative import (and export … from) from this URL (string?,
example: import.meta.url).
Relative specifiers are non-absolute URLs that start with /, ./, or ../.
For example: /index.js, ./folder/file.js, or ../main.js.
This option is useful when code will run in a different place.
One example is when .mdx files are in path a but compiled to path b and
imports should run relative the path b.
Another example is when evaluating code, whether in Node or a browser.
Say we have a module example.js:
import {compile} from 'xdm' main() async function main() { const code = 'export {number} from "./data.js"\n\n# hi' const baseUrl = 'https://a.full/url' // Typically `import.meta.url` console.log(String(await compile(code, {baseUrl}))) }
…now running node example.js yields:
import {Fragment as _Fragment, jsx as _jsx} from 'react/jsx-runtime' export {number} from 'https://a.full/data.js' function MDXContent(props = {}) { /* … */ } export default MDXContent
Whether to add extra information to error messages in generated code
(boolean?, default: false).
The default can be set to true in Node.js through environment variables: set
NODE_ENV=development.
Say we had some MDX that references a component that can be passed or provided at runtime:
**Note**<NoteIcon />: some stuff.
And a module to evaluate that:
import {promises as fs} from 'node:fs' import * as runtime from 'react/jsx-runtime' import {evaluate} from 'xdm' main() async function main() { const path = 'example.mdx' const value = await fs.readFile(path) const MDXContent = (await evaluate({path, value}, runtime)).default console.log(MDXContent()) }
Running that would normally (production) yield:
Error: Expected component `NoteIcon` to be defined: you likely forgot to import, pass, or provide it.
at _missingMdxReference (eval at run (…/xdm/lib/run.js:18:10), <anonymous>:27:9)
at _createMdxContent (eval at run (…/xdm/lib/run.js:18:10), <anonymous>:15:20)
at MDXContent (eval at run (…/xdm/lib/run.js:18:10), <anonymous>:9:9)
at main (…/example.js:11:15)But if we change add development: true to our example:
@@ -7,6 +7,6 @@ main() async function main() { const path = 'example.mdx' const value = await fs.readFile(path) - const MDXContent = (await evaluate({path, value}, runtime)).default + const MDXContent = (await evaluate({path, value}, {development: true, ...runtime})).default console.log(MDXContent({})) }
And we’d run it again, we’d get:
Error: Expected component `NoteIcon` to be defined: you likely forgot to import, pass, or provide it.
It’s referenced in your code at `1:9-1:21` in `example.mdx`
provide it.
at _missingMdxReference (eval at run (…/xdm/lib/run.js:18:10), <anonymous>:27:9)
at _createMdxContent (eval at run (…/xdm/lib/run.js:18:10), <anonymous>:15:20)
at MDXContent (eval at run (…/xdm/lib/run.js:18:10), <anonymous>:9:9)
at main (…/example.js:11:15)The SourceMapGenerator class from source-map (optional).
When given, the resulting file will have a map field set to a source map (in
object form).
Assuming example.mdx from § Use exists, then:
import {promises as fs} from 'node:fs' import {SourceMapGenerator} from 'source-map' import {compile} from 'xdm' main() async function main() { const file = await compile( {path: 'example.mdx', value: await fs.readFile('example.mdx')}, {SourceMapGenerator} ) console.log(file.map) }
…yields:
{ version: 3, sources: ['example.mdx'], names: ['Thing'], mappings: ';;aAAaA,QAAQ;YAAQ;;;;;;;;iBAE3B', file: 'example.mdx' }
Place to import a provider from (string?, example: '@mdx-js/react').
Useful for runtimes that support context (React, Preact).
The provider must export a useMDXComponents, which is called to access an
object of components.
If file is the contents of example.mdx from § Use, then:
compile(file, {providerImportSource: '@mdx-js/react'})
…yields this difference:
/* @jsxRuntime automatic @jsxImportSource react */
import {Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs} from 'react/jsx-runtime'
+import {useMDXComponents as _provideComponents} from '@mdx-js/react'
export const Thing = () => _jsx(_Fragment, {children: 'World!'})
function MDXContent(props = {}) {
- let {wrapper: MDXLayout} = props.components || ({})
+ let {wrapper: MDXLayout} = Object.assign({}, _provideComponents(), props.components)
return MDXLayout
? _jsx(MDXLayout, Object.assign({}, props, {children: _jsx(_createMdxContent, {})}))
: _createMdxContent()
function _createMdxContent() {
- let _components = Object.assign({h1: 'h1'}, props.components)
+ let _components = Object.assign({h1: 'h1'}, _provideComponents(), props.components)
return _jsxs(_components.h1, {children: ['Hello, ', _jsx(Thing, {})]})
}
}
export default MDXContentWhether to keep JSX (boolean?, default: false).
The default is to compile JSX away so that the resulting file is immediately
runnable.
If file is the contents of example.mdx from § Use, then:
compile(file, {jsx: true})
…yields this difference:
/* @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!'}) +export const Thing = () => <>World!</> function MDXContent(props = {}) { let {wrapper: MDXLayout} = props.components || ({}) return MDXLayout - ? _jsx(MDXLayout, Object.assign({}, props, {children: _jsx(_createMdxContent, {})})) + ? <MDXLayout {...props}><_createMdxContent /></MDXLayout> : _createMdxContent() function _createMdxContent() { let _components = Object.assign({h1: 'h1'}, props.components) - return _jsxs(_components.h1, {children: ['Hello, ', _jsx(Thing, {})]}) + return <_components.h1>{"Hello, "}<Thing /></_components.h1> } } export default MDXContent
JSX runtime to use ('automatic' | 'classic', default: 'automatic').
The classic runtime compiles to calls such as h('p'), the automatic runtime
compiles to import _jsx from '$importSource/jsx-runtime'\n_jsx('p').
If file is the contents of example.mdx from § Use, then:
compile(file, {jsxRuntime: 'classic'})
…yields this difference:
-/* @jsxRuntime automatic @jsxImportSource react */ -import {Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs} from 'react/jsx-runtime' +/* @jsxRuntime classic @jsx React.createElement @jsxFrag React.Fragment */ +import React from 'react' -export const Thing = () => _jsx(_Fragment, {children: 'World!'}) +export const Thing = () => React.createElement(React.Fragment, null, 'World!') …
Place to import automatic JSX runtimes from (string?, default: 'react').
When in the automatic runtime, this is used to define an import for
_Fragment, _jsx, and _jsxs.
If file is the contents of example.mdx from § Use, then:
compile(file, {jsxImportSource: 'preact'})
…yields this difference:
-/* @jsxRuntime automatic @jsxImportSource react */ -import {Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs} from 'react/jsx-runtime' +/* @jsxRuntime automatic @jsxImportSource preact */ +import {Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from 'preact/jsx-runtime'
Pragma for JSX (string?, default: 'React.createElement').
When in the classic runtime, this is used as an identifier for function calls:
<x /> to React.createElement('x').
You should most probably define pragmaFrag and pragmaImportSource too when
changing this.
If file is the contents of example.mdx from § Use, then:
compile(file, { jsxRuntime: 'classic', pragma: 'preact.createElement', pragmaFrag: 'preact.Fragment', pragmaImportSource: 'preact/compat' })
…yields this difference:
-/* @jsxRuntime classic @jsx React.createElement @jsxFrag React.Fragment */ -import React from 'react' +/* @jsxRuntime classic @jsx preact.createElement @jsxFrag preact.Fragment */ +import preact from 'preact/compat' -export const Thing = () => React.createElement(React.Fragment, null, 'World!') +export const Thing = () => preact.createElement(preact.Fragment, null, 'World!') …
Pragma for JSX fragments (string?, default: 'React.Fragment').
When in the classic runtime, this is used as an identifier for fragments: <>
to React.createElement(React.Fragment).
See options.pragma for an example.
Where to import the identifier of pragma from (string?, default: 'react').
When in the classic runtime, this is used to import the pragma function.
To illustrate with an example: when pragma is 'a.b' and pragmaImportSource
is 'c' this following will be generated: import a from 'c'.
See options.pragma for an example.
Promise<VFile> — Promise that resolves to the compiled JS as a vfile.
import remarkPresetLintConsistent from 'remark-preset-lint-consistent' // Lint rules to check for consistent markdown. import {reporter} from 'vfile-reporter' import {compile} from 'xdm' main() async function main() { 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 warningCompile MDX to JS.
Synchronous version of compile.
When possible please use the async compile.
☢️ Danger: It’s called evaluate because it
evals JavaScript.
Compile and run MDX.
When possible, please use compile, write to a file, and then run with Node or
bundle with esbuild/Rollup/webpack.
But if you trust your content, evaluate can work.
Typically, import (or export … from) do not work here.
They can be compiled to dynamic import() by passing
options.useDynamicImport.
See compile.
Most options are the same as compile, with the following
exceptions:
providerImportSource is replaced by useMDXComponentsjsx* and pragma* options are replaced by jsx, jsxs, and FragmentoutputFormat is set to function-bodyThese three options are required. They come from an automatic JSX runtime that you must import yourself.
Exampleimport * as runtime from 'react/jsx-runtime' const {default: Content} = await evaluate('# hi', {...runtime, ...otherOptions})
Needed if you want to support a provider.
Exampleimport * as provider from '@mdx-js/react' import * as runtime from 'react/jsx-runtime' const {default: Content} = await evaluate('# hi', {...provider, ...runtime, ...otherOptions})
Promise<Module> — Promise that resolves to something that looks a bit like a
module: an object with a default field set to the component and anything else
that was exported from the MDX file available too.
Assuming the contents of example.mdx from § Use was in file, then:
import * as runtime from 'react/jsx-runtime' import {evaluate} from 'xdm' console.log(await evaluate(file, {...runtime}))
…yields:
{Thing: [Function: Thing], default: [Function: MDXContent]}
☢️ Danger: It’s called evaluate because it
evals JavaScript.
Compile and run MDX.
Synchronous version of evaluate.
When possible please use the async evaluate.
☢️ Danger: This
evals JavaScript.
Run MDX compiled as options.outputFormat: 'function-body'.
You can pass jsx, jsxs, and Fragment from an automatic JSX runtime as
options.
You can pass useMDXComponents from a provider in options as well if the MDX
is compiled with options.providerImportSource: '#' (the exact value of this
option doesn’t matter).
All other options have to be passed to compile instead.
Promise<Module> — See evaluate
On the server:
import {compile} from 'xdm' const code = String(await compile('# hi', {outputFormat: 'function-body'}))
On the client:
import {run} from 'xdm' import * as runtime from 'react/jsx-runtime' const code = '' // To do: get `code` from server somehow. const {default: Content} = await run(code, runtime)
…yields:
[Function: MDXContent]
☢️ Danger: This
evals JavaScript.
Run MDX.
Synchronous version of run.
When possible please use the async run.
Create a unified processor to compile MDX to JS.
Has the same options as compile, but returns a configured
processor.
Note that format: 'detect' does not work here: only 'md' or 'mdx' are
allowed (and 'mdx' is the default).
This section describes experimental features! These do not adhere to semver and could break at any time!
ESM loaders are an experimental
feature in Node, slated to change.
Still, they let projects “hijack” imports, to do all sorts of fancy things!
xdm comes with experimental support for importing .mdx files with
on-the-fly compilation, using xdm/esm-loader.js:
Assuming example.mdx from § Use exists, and our module example.js
looks as follows:
import {renderToStaticMarkup} from 'react-dom/server' import React from 'react' import Content from './example.mdx' console.log(renderToStaticMarkup(React.createElement(Content)))
Running that with:
node --experimental-loader=xdm/esm-loader.js example.js
…yields:
<h1>Hello, World!</h1>
To pass options, you can make your own loader, such as this my-loader.js:
import {createLoader} from 'xdm/esm-loader.js' // Load is for Node 17+, the rest for 12-16. const {load, getFormat, transformSource} = createLoader(/* Options… */) export {load, getFormat, transformSource}
Which can then be used with node --experimental-loader=./my-loader.js.
Node itself does not yet support multiple loaders, but it is possible to combine
multiple loaders with
@node-loader/core.
require.extensions
is a deprecated feature in Node.
Still, it lets projects “hijack” require calls to do fancy things.
xdm comes with support for requiring .mdx files with on-the-fly
evaluation, using xdm/register.cjs:
Assuming example.mdx from § Use exists, and our script example.cjs
looks as follows:
const React = require('react') const {renderToStaticMarkup} = require('react-dom/server') const Content = require('./example.mdx') console.log(renderToStaticMarkup(React.createElement(Content)))
Running that with:
node -r xdm/register.cjs example.cjs
…yields:
<h1>Hello, World!</h1>
To pass options, you can make your own hook, such as this my-hook.cjs:
'use strict' const register = require('xdm/lib/integration/require.cjs') register({/* Options… */})
Which can then be used with node -r ./my-hook.cjs.
The register hook uses evaluateSync.
That means import (and export … from) are not supported when requiring
.mdx files.
⚠️ Note that this includes remote code in your bundle. Make sure you trust it! See § Security for more info.
When passing allowDangerousRemoteMdx to the esbuild loader, MD(X) and JS files
can be imported from http: and https: urls.
Take this index.mdx file:
import Readme from 'https://raw.githubusercontent.com/wooorm/xdm/main/readme.md' Embed the xdm readme like so: <Readme />
And a module build.js:
import xdm from 'xdm/esbuild.js' import esbuild from 'esbuild' await esbuild.build({ entryPoints: ['index.mdx'], outfile: 'output.js', format: 'esm', plugins: [xdm({allowDangerousRemoteMdx: true, /* Other options… */})] })
Running that (node build.js) and evaluating output.js (depends on how you
evaluate React stuff) would give:
<p>Embed the xdm readme like so:</p> <h1>xdm</h1> {/* … */} <p><a href="https://github.com/wooorm/xdm/blob/main/license">MIT</a> © …</p>
Note! You don’t have to use this syntax. Or use it always. With
format, you can opt-in gradually or not at all.
The MDX syntax is a mix between markdown and JSX. Markdown often feels more natural to type than HTML (or JSX) for the common things (like emphasis, headings). JSX is an extension to JavaScript that looks like HTML but makes it convenient to use components (reusable things). See this description for a more formal description of the syntax.
This gives us something along the lines of literate programming.
MDX also gives us an odd mix of two languages: markdown is whitespace sensitive and forgiving (what you type may not “work”, but it won’t crash) whereas JavaScript is whitespace insensitive and does crash on typos. Weirdly enough they combine pretty well!
It’s important to know markdown (see this cheatsheet and tutorial for help) and have experience with JavaScript (specifically JSX) to write (and enjoy writing) MDX.
Some common gotchas with writing MDX are documented here.
Most of markdown (CommonMark) works:
# Heading (rank 1) ## Heading 2 ### 3 #### 4 ##### 5 ###### 6 > Block quote * Unordered * List 1. Ordered 2. List A paragraph, introducing a thematic break: *** ```js some.code() ``` a [link](https://example.com), an , some *emphasis*, something **strong**, and finally a little `code()`.
Some other features often used with markdown are:
There are many more things possible by configuring remark plugins and rehype plugins.
There are also a couple specific remark/rehype/recma plugins that work with xdm: see plugins.
Some markdown features don’t work in MDX:
Indented code works in markdown, but not in MDX:
console.log(1) // this is a paragraph in MDX!
The reason for that is so that you can nicely indent your components.
A different one is “autolinks”:
<svg:rect> and <admin@example.com> are links in markdown, but they crash xdm.
The reason is that they look a lot like JSX components, and we prefer being unambiguous.
If you want links, use [descriptive text](https://and-the-link-here.com).
HTML doesn’t work, because MDX has JSX instead (see next section).
And you must escape less than (`<`) and opening braces (`{`) like so: \< or \{.More on this is documented here.
Most of JSX works. Here’s some that looks a lot like HTML (but is JSX):
<h1>Heading!</h1> <abbr title="HyperText Markup Language">HTML</abbr> is a lovely language. <section> And here is *markdown* in **JSX**! </