docs/migrating/v2.mdx
import {Note} from '../_component/note.jsx'
export const info = { author: [ {github: 'wooorm', name: 'Titus Wormer'} ], modified: new Date('2025-01-27'), published: new Date('2022-02-01') } export const navExclude = true
<Note type="legacy"> **Note**: This is an old migration guide. See [§ Migrating from v2 to v3](/migrating/v3/). The below is kept as is for historical purposes. </Note>When upgrading to version 2, you might run into import problems.
Our packages are now ESM only.
You have to load them with import foo from 'foo' instead of
const foo = require('foo').
Please see ¶ ESM in § Troubleshooting MDX.
@mdx-js/* packagesYou need to make changes when upgrading some mdx-js/* packages.
In this section we will discuss the needed changes for each package.
If you’re experiencing problems, please see § Troubleshooting MDX
for common errors and how to solve them.
@mdx-js/loaderTo update our webpack loader @mdx-js/loader, please first update to recent
versions of webpack and React.
Then make sure ESM is supported.
ESM is explained above.
If you’re having trouble using ESM in your webpack config, here’s an example
that works with our previous @mdx-js/loader (1.6.22):
import {fileURLToPath} from 'node:url'
import webpack from 'webpack'
const config = {
context: fileURLToPath(new URL('src/', import.meta.url)),
entry: ['./index.js'],
mode: 'none',
module: {
rules: [
{
test: /\.mdx?$/,
use: [
{
loader: 'babel-loader',
options: {presets: ['@babel/preset-env', '@babel/preset-react']}
},
{
loader: '@mdx-js/loader',
/** @type {import('@mdx-js/loader').Options} */
options: {}
}
]
}
]
},
output: {
filename: 'bundle.js',
path: fileURLToPath(new URL('dest/', import.meta.url))
}
};
export default config
Then install MDX version 2:
npm install @mdx-js/loader @mdx-js/react remark-gfm
You can update your code as follows:
<div className="big-columns"> <div> Before:```tsx path="webpack.config.js"
// …
{
test: /\.mdx?$/,
use: [
{
loader: 'babel-loader',
options: {
presets: [
'@babel/preset-env',
'@babel/preset-react'
]
}
},
{
loader: '@mdx-js/loader',
/** @type {import('@mdx-js/loader').Options} */
options: {}
}
]
}
// …
```
```tsx path="webpack.config.js"
import remarkGfm from 'remark-gfm'
// …
{
test: /\.mdx?$/,
use: [
{
loader: 'babel-loader',
options: {
presets: [
'@babel/preset-env'
]
}
},
{
loader: '@mdx-js/loader',
/** @type {import('@mdx-js/loader').Options} */
options: {
providerImportSource: '@mdx-js/react',
remarkPlugins: [remarkGfm]
}
}
]
}
// …
```
The above changes get MDX 2 close to how MDX 1 worked. You can make it simpler:
babel-loader is optional.
It compiles modern JavaScript to JavaScript your users support.
If you don’t need that you can remove it before @mdx-js/loaderremark-gfm is optional.
It adds support for autolink literals, footnotes, strikethrough, tables, and
task lists.
If you don’t want them, you can uninstall it, remove the import, and remove
it from options.remarkPlugins.
More info in our guide on GFM@mdx-js/react is optional.
It adds support for context based components passing.
If you don’t need that, you can uninstall it and remove
options.providerImportSource.
More info in ¶ MDX provider in § Using MDX@mdx-js/loader now supports Preact and other JSX runtimes through
configuration.
Using Preact as an example:
// …
{
loader: '@mdx-js/loader',
/** @type {import('@mdx-js/loader').Options} */
options: {
jsxImportSource: 'preact',
// Optional: either remove the following line or install `@mdx-js/preact`.
providerImportSource: '@mdx-js/preact'
}
}
// …
For more information, please see § API in @mdx-js/loader.
The options accepted by the loader changed:
renderer is replaced by jsxImportSource, providerImportSource, and
others options.
More info in § API in @mdx-js/mdx@mdx-js/mdx.
See @mdx-js/mdx below if neededFor more information, please see § API in @mdx-js/loader.
@mdx-js/react, @mdx-js/preact, @mdx-js/vueTo update our framework integrations, please first update to recent versions of React, Preact, or Vue 3. Then make sure ESM is supported. ESM is explained above. Then install version 2:
npm install @mdx-js/react # Change `react` to `preact` or `vue` if needed
Note that these packages now only add support for context based components passing. If you don’t need that, you can uninstall them. More info in ¶ MDX provider in § Using MDX.
The interface of changed slightly:
mdx, which was a createElement function, is removed.
You can use your framework’s functions instead: React.createElement,
h from preact, etc.@mdx-js/mdxTo update our core compiler @mdx-js/mdx, first make sure ESM is supported.
ESM is explained above.
Then install version 2:
npm install @mdx-js/mdx @mdx-js/react remark-gfm
You can update your code as follows:
<div className="big-columns"> <div> Before:```tsx path="old.js"
import mdx from '@mdx-js/mdx'
const result = await mdx('# hi')
console.log(result)
```
```tsx path="new.js"
import {compile} from '@mdx-js/mdx'
import remarkGfm from 'remark-gfm'
const result = await compile('# hi', {
providerImportSource: '@mdx-js/react',
remarkPlugins: [remarkGfm]
})
console.log(String(result))
```
The above changes get MDX 2 close to how MDX 1 worked. You can make it simpler:
remark-gfm is optional.
It adds support for autolink literals, footnotes, strikethrough, tables, and
task lists.
If you don’t want them, you can uninstall it, remove the import, and remove
it from options.remarkPlugins.
More info in our guide on GFM@mdx-js/react is optional.
It adds support for context based components passing.
If you don’t need that, you can uninstall it and remove
options.providerImportSource.
More info in ¶ MDX provider in § Using MDX@mdx-js/mdx now supports Preact and other JSX runtimes through configuration.
Using Preact as an example:
import {compile} from '@mdx-js/mdx'
const result = await compile('# hi', {jsxImportSource: 'preact'})
For more information, please see § API in @mdx-js/mdx
The interface of @mdx-js/mdx changed:
compilesync is replaced by compileSynccreateCompiler is replaced by
createProcessorcreateMdxAstCompiler is removed, use remark with
remark-mdx insteadcompile, compileSync are now VFiles instead of
stringsevaluate,
evaluateSync to eval (compile and run) MDXOptions in version 1 were undocumented. If you used them:
filepath is replaced by accepting a VFile or compatible object as the
first argument filecompilers is replaced by recmaPluginshastPlugins is replaced by rehypePluginsmdPlugins is replaced by options.remarkPluginsskipExport is removed, evaluate can do this
automaticallywrapExport is removed, use a custom recma plugin insteadFor more information, please see § API in @mdx-js/mdx.
@mdx-js/runtimeWe’ve deprecated @mdx-js/runtime.
Our core compiler @mdx-js/mdx can now do the same and more.
To update, first make sure ESM is supported.
ESM is explained above.
Please also make sure you are using a recent version of React.
Then uninstall @mdx-js/runtime and install @mdx-js/mdx and @mdx-js/react:
npm uninstall @mdx-js/runtime
npm install @mdx-js/mdx @mdx-js/react
You can update your code as follows:
<div className="big-columns"> <div> Before:```tsx path="old.js"
import MDX from '@mdx-js/runtime'
const components =
const value = '# hi'
export default function () {
return <MDX components={components}>
{value}
</MDX>
}
```
```tsx path="new.js"
import * as runtime from 'react/jsx-runtime'
import * as provider from '@mdx-js/react'
import {evaluate} from '@mdx-js/mdx'
const components =
const value = '# hi'
const {default: Content} = await evaluate(value, {...provider, ...runtime})
export default function () {
return <Content components={components} />
}
```
The above changes get MDX 2 close to how MDX 1 worked. You can make it simpler:
@mdx-js/react is optional.
It adds support for context based components passing.
If you don’t need that, you can uninstall it and remove
options.providerImportSource.
More info in ¶ MDX provider in § Using MDX@mdx-js/mdx now supports Preact and other JSX runtimes through configuration.
Using Emotion as an example:
// …
import * as runtime from '@emotion/react/jsx-runtime'
// …
For more information, please see evaluate in @mdx-js/mdx.
@mdx-js/runtime is replaced by evaluate
from @mdx-js/mdxevaluate supports any JSX runtime and providers are optionalevaluate yields an MDXContent component just like how importing
and MDX file worksevaluate supports imports and exports inside evaluated MDXFor more information, please see evaluate in @mdx-js/mdx.
remark-mdxTo update our remark plugin remark-mdx, first make sure ESM is supported.
ESM is explained above.
Then install version 2:
npm install remark-mdx
For more information, please see § Use in remark-mdx.
@mdx-js/vue-loaderWe’ve deprecated @mdx-js/vue-loader.
Our main loader @mdx-js/loader can now do the same and more.
To update, first remove @mdx-js/vue-loader and upgrade to Vue 3.
Then, see ¶ Vue in § Getting started on how to use
Vue with MDX.
@mdx-js/parcel-plugin-mdxWe’ve deprecated @mdx-js/parcel-plugin-mdx.
Parcel 2 has their own plugin.
To update, first remove @mdx-js/parcel-plugin-mdx and upgrade to Parcel 2.
Then, see ¶ Parcel in § Getting started on how
to use Parcel with MDX.
We removed several packages doing internal work for us. These packages are:
@mdx-js/util
— no longer needed internal helpers@mdx-js/test-util
— no longer needed test helpers, evaluate can do themgatsby-theme-mdx
— no longer needed website templateremark-mdx-remove-imports, babel-plugin-extract-import-names
— no longer needed transforms, our compiler handles importsremark-mdx-remove-exports, babel-plugin-remove-export-keywords
— no longer needed transforms, our compiler handles exportsbabel-plugin-html-attributes-to-jsx
— no longer needed transform, something similar is done by
hast-util-to-estreebabel-plugin-apply-mdx-type-props
— no longer needed transform due to the new architecturecreate-mdx
— no longer needed, we recommend to start your project with whatever other
integrations you prefer and then add MDX to itNow you’ve updated our packages, you can update your MDX files. In version 2, we improved the syntax of the MDX format. When upgrading, you’ll likely get errors. Read those error messages carefully, as they typically explain what happened where and how to fix it. See § Troubleshooting MDX for common errors and how to solve them.
Let’s kick off with a couple of things that are now possible with JSX in MDX. You don’t need blank lines between JSX and markdown anymore:
<hgroup>
# This is a heading now
</hgroup>
You can now indent your JSX and markdown:
<article>
<hgroup>
# This is a heading now, not code or plain text
</hgroup>
<section>
```js
// if you do want code blocks, use fenced code
```
</section>
</article>
You can use markdown “inlines” but not “blocks” inside JSX if the text and tags are on the same line:
<div># this is not a heading but *this* is emphasis</div>
Text and tags on one line don’t produce blocks so they don’t produce <p>s
either.
On separate lines, they do:
<div>
This is a `p`.
</div>
We differentiate using this rule (same line or not). Not based on semantics of elements in HTML. So you can build incorrect HTML (which you shouldn’t):
<h1 className="main">
Don’t do this: it’s a `p` in an `h1`
</h1>
<h1 className="main">Do this: an `h1` with `code`</h1>
It’s not possible to wrap “blocks” if text and tags are on the same line but the corresponding tags are on different lines:
Welcome! <a href="about.html">
This is home of...
# The Falcons!</a>
That’s because to parse markdown, we first have to divide it into “blocks”.
So in this case two paragraphs and a heading.
Leaving an opening a tag in the first paragraph and a stray closing a tag in
the heading.
We also fixed several cases where JSX was not parsed or handled correctly or even crashed. More on JSX in MDX is in ¶ JSX in § What is MDX?
You can now use expressions in MDX to include JavaScript. You can also use them as an escape hatch if you ever just want strings or JSX rather than markdown:
{
<h1>
This just JSX, these *asterisks* have no meaning.
</h1>
}
This is just {'`text`'}, not code.
More on expressions in MDX is in ¶ Expressions in § What is MDX?
You can more easily embed components in MDX because blank lines are allowed:
export function Button(props) {
const style = {color: 'red'}
return <button style={style} {...props} />
}
We also fixed several cases where import and export were not parsed or
handled correctly or even crashed.
More on ESM in MDX is in ¶ ESM in § What is MDX?
We turned off several things that are allowed in regular markdown to make MDX less ambiguous. More on markdown in MDX and where it differs is in ¶ Markdown in § What is MDX?
We turned off GFM features in MDX by default. GFM extends CommonMark to add autolink literals, footnotes, strikethrough, tables, and task lists. If you do want these features, you can use a plugin. See our guide on GFM.
The interface of the generated JavaScript from MDX changed:
parent.child combos such as ol.li, to pass components, were removed, we
recommend to style your ols, uls, and lis separatelyinlineCode was removed, we recommend to use
pre for the block version of code, and code for both the block and
inline versionsMDXContent.isMDXContent was removed, we recommend treating MDXContent
like any other componenth1, it does get used for
# hi but not for <h1>hi</h1>components={{theme}}, where theme is an object with a Box component,
it can be used with: <theme.Box>stuff</theme.Box>import * as everything from './example.mdx'
const {default: Content, ...exported} = everything
<Content {...exported} />
We also fixed several cases where defined, imported, or passed components weren’t handled correctly or even crashed.
Phew. You made it! Welcome on the MDX 2 train, it’s been a journey. We know it wasn’t the easiest to migrate, we’re happy you’re still here, we’ll try to make migration easier next time. With our new AST, we’re able to create codemods from now on. <3