By Titus Wormer
Migrating from v1 to v2
Contents
ESM
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.
Update @mdx-js/*
packages
You 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/loader
To 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
):Expand example of a
webpack.config.js
in ESMimport {URL, fileURLToPath} from 'node:url'
import webpack from 'webpack'
const config = {
mode: 'none',
context: fileURLToPath(new URL('src/', import.meta.url)),
entry: ['./index.js'],
output: {
path: fileURLToPath(new URL('dest/', import.meta.url)),
filename: 'bundle.js'
},
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: {}
}
]
}
]
}
};
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:
Before:
// …
{
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: {}
}
]
}
// …
After:
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/loader
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 fromoptions.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 removeoptions.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
.
Changes
The options accepted by the loader changed:
renderer
is replaced byoptions.jsxImportSource
,options.providerImportSource
, and others options. More info in § API in@mdx-js/mdx
- Other options were undocumented but passed to
@mdx-js/mdx
. See@mdx-js/mdx
below if needed
For more information, please see § API in @mdx-js/loader
.
@mdx-js/react
, @mdx-js/preact
, @mdx-js/vue
To 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.
Changes
The interface of changed slightly:
- The named export
mdx
, which was acreateElement
function, is removed. You can use your framework’s functions instead:React.createElement
,h
frompreact
, etc.
@mdx-js/mdx
To 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:
Before:
import mdx from '@mdx-js/mdx'
const result = await mdx('# hi')
console.log(result)
After:
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 fromoptions.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 removeoptions.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
Changes
The interface of @mdx-js/mdx
changed:
- The default export is replaced by the named export
compile
- The named export
sync
is replaced bycompileSync
- The named export
createCompiler
is replaced bycreateProcessor
- The named export
createMdxAstCompiler
is removed, useremark
withremark-mdx
instead - The return value of
compile
,compileSync
are now VFiles instead of strings - There are new exports
evaluate
,evaluateSync
to eval (compile and run) MDX
Options in version 1 were undocumented. If you used them:
filepath
is replaced by accepting a VFile or compatible object as the first argumentfile
compilers
is replaced byoptions.recmaPlugins
hastPlugins
is replaced byoptions.rehypePlugins
mdPlugins
is replaced byoptions.remarkPlugins
skipExport
is removed,evaluate
can do this automaticallywrapExport
is removed, use a custom recma plugin instead- There are many new options to support different JSX runtimes, non-React JSX, compile JSX away, source maps, normal markdown, and otherwise change how MDX is compiled
For more information, please see § API in @mdx-js/mdx
.
@mdx-js/runtime
We’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:
Before:
import MDX from '@mdx-js/runtime'
const components = {/* … */}
const value = '# hi'
export default () => (
<MDX components={components}>
{value}
</MDX>
)
After:
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 () => (
<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 removeoptions.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
.
Changes
- The package
@mdx-js/runtime
is replaced byevaluate
from@mdx-js/mdx
evaluate
supports any JSX runtime and providers are optionalevaluate
yields anMDXContent
component just like how importing and MDX file worksevaluate
supports imports and exports inside evaluated MDX
For more information, please see evaluate
in @mdx-js/mdx
.
remark-mdx
To 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-loader
We’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-mdx
We’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.
Other packages
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 byhast-util-to-estree
babel-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 it
Update MDX files
Now 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.
JSX
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?
Expressions
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?
ESM
You can more easily embed components in MDX because blank lines are allowed:
export const 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?
Markdown
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?
GFM
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. How to do so is described in our guide on GFM.
Update MDX content
The interface of the generated JavaScript from MDX changed:
- Missing components now throw an error rather than render their children and emit a warning to the console, which is closer to how frameworks like React handle missing undefined JSX components
parent.child
combos such asol.li
, to pass components, were removed, we recommend to style yourol
s,ul
s, andli
s separately- The special component name
inlineCode
was removed, we recommend to usepre
for the block version of code, andcode
for both the block and inline versions MDXContent.isMDXContent
was removed, we recommend treatingMDXContent
like any other component- Locally defined or imported components precede over passed components
- We now “sandbox” components, for lack of a better name. It means that when you pass a component for
h1
, it does get used for# hi
but not for<h1>hi</h1>
- You can now pass and use objects of components: if you pass
components={{theme}}
, wheretheme
is an object with aBox
component, it can be used with:<theme.Box>stuff</theme.Box>
- the values exports from an MDX file are no longer passed to layouts, you can achieve the same with:
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