Skip to content

Commit f08d885

Browse files
author
Bryce Kalow
authored
feat: support MDX v2 (#211)
1 parent d2ad91e commit f08d885

23 files changed

+20602
-6862
lines changed

.circleci/config.yml

-24
This file was deleted.

.github/workflows/test.yml

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
name: 'Test'
2+
on:
3+
pull_request:
4+
branches: [main]
5+
push:
6+
branches: [main]
7+
8+
jobs:
9+
test:
10+
name: 'Run Tests 🧪'
11+
runs-on: ubuntu-latest
12+
steps:
13+
- name: Checkout
14+
uses: actions/checkout@v2
15+
- name: Setup Node
16+
uses: actions/setup-node@v2
17+
with:
18+
node-version: '16.x'
19+
cache: 'npm'
20+
cache-dependency-path: '**/package-lock.json'
21+
- name: Install and build
22+
run: |
23+
npm ci
24+
npm run build
25+
- name: Run Jest
26+
run: npm run test

.jest/utils.tsx

+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import ReactDOMServer from 'react-dom/server'
2+
import React from 'react'
3+
4+
import { MDXRemote, MDXRemoteProps } from '../src/index'
5+
import { serialize } from '../src/serialize'
6+
import { SerializeOptions } from '../src/types'
7+
8+
export async function renderStatic(
9+
mdx: string,
10+
{
11+
components,
12+
scope,
13+
mdxOptions,
14+
minifyOptions,
15+
parseFrontmatter,
16+
}: SerializeOptions & Pick<MDXRemoteProps, 'components'> = {}
17+
): Promise<string> {
18+
const mdxSource = await serialize(mdx, {
19+
mdxOptions,
20+
minifyOptions,
21+
parseFrontmatter,
22+
})
23+
24+
return ReactDOMServer.renderToStaticMarkup(
25+
<MDXRemote {...mdxSource} components={components} scope={scope} />
26+
)
27+
}

.npmrc

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
engine-strict=true

README.md

+58-56
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,48 @@ While it may seem strange to see these two in the same file, this is one of the
5454
5555
### Additional Examples
5656

57+
<details>
58+
<summary>Parsing Frontmatter</summary>
59+
60+
Markdown in general is often paired with frontmatter, and normally this means adding some extra custom processing to the way markdown is handled. To address this, `next-mdx-remote` comes with optional parsing of frontmatter, which can be enabled by passing `parseFrontmatter: true` to `serialize`.
61+
62+
Here's what that looks like:
63+
64+
```jsx
65+
import { serialize } from 'next-mdx-remote/serialize'
66+
import { MDXRemote } from 'next-mdx-remote'
67+
68+
import Test from '../components/test'
69+
70+
const components = { Test }
71+
72+
export default function TestPage({ mdxSource }) {
73+
return (
74+
<div className="wrapper">
75+
<h1>{mdxSource.frontmatter.title}</h1>
76+
<MDXRemote {...mdxSource} components={components} />
77+
</div>
78+
)
79+
}
80+
81+
export async function getStaticProps() {
82+
// MDX text - can be from a local file, database, anywhere
83+
const source = `---
84+
title: Test
85+
---
86+
87+
Some **mdx** text, with a component <Test name={title}/>
88+
`
89+
90+
const mdxSource = await serialize(source, { parseFrontmatter: true })
91+
return { props: { mdxSource } }
92+
}
93+
```
94+
95+
_[`vfile-matter`](https://github.com/vfile/vfile-matter) is used to parse the frontmatter._
96+
97+
</details>
98+
5799
<details>
58100
<summary>Passing custom data to a component with `scope`</summary>
59101

@@ -133,7 +175,7 @@ export async function getStaticProps() {
133175
Custom components from <code>MDXProvider</code><a id="mdx-provider"></a>
134176
</summary>
135177

136-
If you want to make components available to any `<MDXRemote />` being rendered in your application, you can use [`<MDXProvider />`](https://mdxjs.com/advanced/components#mdxprovider) from `@mdx-js/react`.
178+
If you want to make components available to any `<MDXRemote />` being rendered in your application, you can use [`<MDXProvider />`](https://mdxjs.com/docs/using-mdx/#mdx-provider) from `@mdx-js/react`.
137179

138180
```jsx
139181
// pages/_app.jsx
@@ -180,7 +222,7 @@ export async function getStaticProps() {
180222
Component names with dot (e.g. <code>motion.div</code>)
181223
</summary>
182224

183-
Component names that contain a dot (`.`), such as those from `framer-motion`, can be rendered as long as the top-level namespace is declared in the MDX scope:
225+
Component names that contain a dot (`.`), such as those from `framer-motion`, can be rendered the same way as other custom components, just pass `motion` in your components object.
184226

185227
```js
186228
import { motion } from 'framer-motion'
@@ -192,7 +234,7 @@ import { MDXRemote } from 'next-mdx-remote'
192234
export default function TestPage({ source }) {
193235
return (
194236
<div className="wrapper">
195-
<MDXRemote {...source} scope={{ motion }} />
237+
<MDXRemote {...source} components={{ motion }} />
196238
</div>
197239
)
198240
}
@@ -246,9 +288,9 @@ export async function getStaticProps() {
246288

247289
This library exposes a function and a component, `serialize` and `<MDXRemote />`. These two are purposefully isolated into their own files -- `serialize` is intended to be run **server-side**, so within `getStaticProps`, which runs on the server/at build time. `<MDXRemote />` on the other hand is intended to be run on the client side, in the browser.
248290

249-
- **`serialize(source: string, { mdxOptions?: object, scope?: object, target?: string | string[] })`**
291+
- **`serialize(source: string, { mdxOptions?: object, scope?: object, parseFrontmatter?: boolean })`**
250292

251-
**`serialize`** consumes a string of MDX. It also can optionally be passed options which are [passed directly to MDX](https://mdxjs.com/advanced/plugins), and a scope object that can be included in the mdx scope. The function returns an object that is intended to be passed into `<MDXRemote />` directly.
293+
**`serialize`** consumes a string of MDX. It can also optionally be passed options which are [passed directly to MDX](https://mdxjs.com/docs/extending-mdx/), and a scope object that can be included in the mdx scope. The function returns an object that is intended to be passed into `<MDXRemote />` directly.
252294

253295
```ts
254296
serialize(
@@ -267,9 +309,8 @@ This library exposes a function and a component, `serialize` and `<MDXRemote />`
267309
compilers: [],
268310
filepath: '/some/file/path',
269311
},
270-
// Specify the target environment for the generated code. See esbuild docs:
271-
// https://esbuild.github.io/api/#target
272-
target: ['esnext'],
312+
// Indicates whether or not to parse the frontmatter from the mdx source
313+
parseFrontmatter: false,
273314
}
274315
)
275316
```
@@ -284,51 +325,9 @@ This library exposes a function and a component, `serialize` and `<MDXRemote />`
284325
<MDXRemote {...source} components={components} />
285326
```
286327

287-
## Frontmatter & Custom Processing
288-
289-
Markdown in general is often paired with frontmatter, and normally this means adding some extra custom processing to the way markdown is handled. Luckily, this can be done entirely independently of `next-mdx-remote`, along with any extra custom processing necessary.
290-
291-
Let's walk through an example of how we could process frontmatter out of our MDX source:
292-
293-
```jsx
294-
import { serialize } from 'next-mdx-remote/serialize'
295-
import { MDXRemote } from 'next-mdx-remote'
296-
297-
import matter from 'gray-matter'
298-
299-
import Test from '../components/test'
300-
301-
const components = { Test }
302-
303-
export default function TestPage({ source, frontMatter }) {
304-
return (
305-
<div className="wrapper">
306-
<h1>{frontMatter.title}</h1>
307-
<MDXRemote {...source} components={components} />
308-
</div>
309-
)
310-
}
311-
312-
export async function getStaticProps() {
313-
// MDX text - can be from a local file, database, anywhere
314-
const source = `---
315-
title: Test
316-
---
317-
318-
Some **mdx** text, with a component <Test name={title}/>
319-
`
320-
321-
const { content, data } = matter(source)
322-
const mdxSource = await serialize(content, { scope: data })
323-
return { props: { source: mdxSource, frontMatter: data } }
324-
}
325-
```
326-
327-
Nice and easy - since we get the content as a string originally and have full control, we can run any extra custom processing needed before passing it into `serialize`, and easily append extra data to the return value from `getStaticProps` without issue.
328-
329328
### Replacing default components
330329

331-
Rendering will use [`MDXProvider`](https://mdxjs.com/getting-started#mdxprovider) under the hood. This means you can replace HTML tags by custom components. Those components are listed in MDXJS [Table of components](https://mdxjs.com/table-of-components).
330+
Rendering will use [`MDXProvider`](https://mdxjs.com/docs/using-mdx/#mdx-provider) under the hood. This means you can replace HTML tags by custom components. Those components are listed in MDXJS [Table of components](https://mdxjs.com/table-of-components/).
332331

333332
An example use case is rendering the content with your preferred styling library.
334333

@@ -368,7 +367,7 @@ If you really insist though, check out [our official nextjs example implementati
368367

369368
### Environment Targets
370369

371-
The code generated by `next-mdx-remote`, which is used to actually render the MDX, is transformed to support: `>= node 12, es2020`.
370+
The code generated by `next-mdx-remote`, which is used to actually render the MDX targets browsers with module support. If you need to support older browsers, consider transpiling the `compiledSource` output from `serialize`.
372371

373372
### `import` / `export`
374373

@@ -410,10 +409,13 @@ export default function ExamplePage({ mdxSource }: Props) {
410409
)
411410
}
412411

413-
export const getStaticProps: GetStaticProps<{mdxSource: MDXRemoteSerializeResult}> = async () => {
414-
const mdxSource = await serialize('some *mdx* content: <ExampleComponent />')
415-
return { props: { mdxSource } }
416-
}
412+
export const getStaticProps: GetStaticProps<{mdxSource: MDXRemoteSerializeResult}> =
413+
async () => {
414+
const mdxSource = await serialize(
415+
'some *mdx* content: <ExampleComponent />'
416+
)
417+
return { props: { mdxSource } }
418+
}
417419
```
418420

419421
## Migrating from v2 to v3

__tests__/fixtures/basic/mdx/test.mdx

+19-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import Bar from 'bar'
77

88
# Headline
99

10-
<Test name={name} />
10+
<Test name={frontmatter.name} />
1111

1212
<ContextConsumer />
1313

@@ -16,3 +16,21 @@ Some **markdown** content
1616
~> Alert
1717

1818
<Dynamic />
19+
20+
```shell-session
21+
curl localhost
22+
```
23+
24+
This is more text.
25+
26+
~> < client node IP >:9999
27+
28+
"Authorize \<GITHUB_USER\>"
29+
30+
(support for version \<230)
31+
32+
### Some version \<= 1.3.x
33+
34+
#### metric.name.\<operation>.\<mount>
35+
36+
< 8ms

__tests__/fixtures/basic/package.json

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"private": true
3+
}

__tests__/fixtures/basic/pages/index.jsx

+9-7
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import fs from 'fs'
22
import path from 'path'
3-
import matter from 'gray-matter'
43
import { createContext, useEffect, useState } from 'react'
54
import dynamic from 'next/dynamic'
65
import { serialize } from '../../../../serialize'
@@ -34,7 +33,7 @@ const MDX_COMPONENTS = {
3433
Dynamic: dynamic(() => import('../components/dynamic')),
3534
}
3635

37-
export default function TestPage({ data, mdxSource }) {
36+
export default function TestPage({ mdxSource }) {
3837
const [providerOptions, setProviderOptions] = useState(PROVIDER)
3938

4039
useEffect(() => {
@@ -48,19 +47,22 @@ export default function TestPage({ data, mdxSource }) {
4847

4948
return (
5049
<>
51-
<h1>{data.title}</h1>
50+
<h1>{mdxSource.frontmatter.title}</h1>
5251
<TestContext.Provider {...providerOptions.props}>
53-
<MDXRemote {...mdxSource} components={MDX_COMPONENTS} scope={data} />
52+
<MDXRemote {...mdxSource} components={MDX_COMPONENTS} />
5453
</TestContext.Provider>
5554
</>
5655
)
5756
}
5857

5958
export async function getStaticProps() {
6059
const fixturePath = path.join(process.cwd(), 'mdx/test.mdx')
61-
const { data, content } = matter(fs.readFileSync(fixturePath, 'utf8'))
62-
const mdxSource = await serialize(content, {
60+
const source = await fs.promises.readFile(fixturePath, 'utf8')
61+
62+
const mdxSource = await serialize(source, {
6363
mdxOptions: { remarkPlugins: [paragraphCustomAlerts] },
64+
parseFrontmatter: true,
6465
})
65-
return { props: { mdxSource, data } }
66+
67+
return { props: { mdxSource } }
6668
}

0 commit comments

Comments
 (0)