Skip to content

feat: support MDX v2 #211

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 34 commits into from
Feb 15, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
62e5ab5
WIP integrate mdx v2
Nov 1, 2021
69589f8
gets tests working
Nov 2, 2021
61d5025
remove babel deps and upgrade typescript
Nov 2, 2021
087d624
upgrade dependencies
Nov 2, 2021
06928d2
make esbuild opt-in behind the minify flag
Nov 2, 2021
7c6f8d4
Adds well-formatted error message
Nov 4, 2021
66309dd
Working build in another app
Nov 11, 2021
e6c1cc7
3.1.0-alpha.1
Nov 11, 2021
e7795c8
update external for jsx-runtime
Nov 11, 2021
62e4571
3.1.0-alpha.2
Nov 11, 2021
af368d1
refactor test structure
Nov 12, 2021
f3db18b
clean up types and serialize code
Nov 12, 2021
770ad1a
fix integration tests and minor cleanup
Nov 17, 2021
c451c29
improve error formatting
Nov 18, 2021
42d74b1
use GH Actions for tests
Nov 18, 2021
247376c
update branch qualifier for action
Nov 18, 2021
55ed966
temporary: add this branch for test run
Nov 18, 2021
9300c31
undo branch for validation
Nov 18, 2021
bad0abb
remove unnecessary return statement
Nov 18, 2021
43738fd
merge with main
Nov 18, 2021
6579e85
adds support for parsing frontmatter
Nov 19, 2021
910c251
Update README
Nov 19, 2021
03e10bf
adds test for error messaging
Nov 19, 2021
8053662
4.0.0-alpha.1
Nov 19, 2021
108bb8c
don't require node v16
Nov 23, 2021
c459f2b
4.0.0-alpha.2
Nov 23, 2021
29e6ac3
remove esbuild integration completely
Dec 10, 2021
1b2d364
remove minify options from serialize
Dec 10, 2021
c7020ed
4.0.0-rc.1
Dec 10, 2021
f8780fd
updates core dependencies
Feb 11, 2022
a53d177
adds exports field
Feb 11, 2022
32cca8d
Merge branch 'main' into brk.feat/mdx-v2-rc
Feb 11, 2022
1663e99
Merge branch 'main' into brk.feat/mdx-v2-rc
Feb 14, 2022
4c80b60
4.0.0-rc.2
Feb 15, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 0 additions & 24 deletions .circleci/config.yml

This file was deleted.

26 changes: 26 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
name: 'Test'
on:
pull_request:
branches: [main]
push:
branches: [main]

jobs:
test:
name: 'Run Tests 🧪'
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Setup Node
uses: actions/setup-node@v2
with:
node-version: '16.x'
cache: 'npm'
cache-dependency-path: '**/package-lock.json'
- name: Install and build
run: |
npm ci
npm run build
- name: Run Jest
run: npm run test
27 changes: 27 additions & 0 deletions .jest/utils.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import ReactDOMServer from 'react-dom/server'
import React from 'react'

import { MDXRemote, MDXRemoteProps } from '../src/index'
import { serialize } from '../src/serialize'
import { SerializeOptions } from '../src/types'

export async function renderStatic(
mdx: string,
{
components,
scope,
mdxOptions,
minifyOptions,
parseFrontmatter,
}: SerializeOptions & Pick<MDXRemoteProps, 'components'> = {}
): Promise<string> {
const mdxSource = await serialize(mdx, {
mdxOptions,
minifyOptions,
parseFrontmatter,
})

return ReactDOMServer.renderToStaticMarkup(
<MDXRemote {...mdxSource} components={components} scope={scope} />
)
}
1 change: 1 addition & 0 deletions .npmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
engine-strict=true
114 changes: 58 additions & 56 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,48 @@ While it may seem strange to see these two in the same file, this is one of the

### Additional Examples

<details>
<summary>Parsing Frontmatter</summary>

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`.

Here's what that looks like:

```jsx
import { serialize } from 'next-mdx-remote/serialize'
import { MDXRemote } from 'next-mdx-remote'

import Test from '../components/test'

const components = { Test }

export default function TestPage({ mdxSource }) {
return (
<div className="wrapper">
<h1>{mdxSource.frontmatter.title}</h1>
<MDXRemote {...mdxSource} components={components} />
</div>
)
}

export async function getStaticProps() {
// MDX text - can be from a local file, database, anywhere
const source = `---
title: Test
---

Some **mdx** text, with a component <Test name={title}/>
`

const mdxSource = await serialize(source, { parseFrontmatter: true })
return { props: { mdxSource } }
}
```

_[`vfile-matter`](https://github.com/vfile/vfile-matter) is used to parse the frontmatter._

</details>

<details>
<summary>Passing custom data to a component with `scope`</summary>

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

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`.
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`.

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

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:
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.

```js
import { motion } from 'framer-motion'
Expand All @@ -192,7 +234,7 @@ import { MDXRemote } from 'next-mdx-remote'
export default function TestPage({ source }) {
return (
<div className="wrapper">
<MDXRemote {...source} scope={{ motion }} />
<MDXRemote {...source} components={{ motion }} />
</div>
)
}
Expand Down Expand Up @@ -246,9 +288,9 @@ export async function getStaticProps() {

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.

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

**`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.
**`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.

```ts
serialize(
Expand All @@ -267,9 +309,8 @@ This library exposes a function and a component, `serialize` and `<MDXRemote />`
compilers: [],
filepath: '/some/file/path',
},
// Specify the target environment for the generated code. See esbuild docs:
// https://esbuild.github.io/api/#target
target: ['esnext'],
// Indicates whether or not to parse the frontmatter from the mdx source
parseFrontmatter: false,
}
)
```
Expand All @@ -284,51 +325,9 @@ This library exposes a function and a component, `serialize` and `<MDXRemote />`
<MDXRemote {...source} components={components} />
```

## Frontmatter & Custom Processing

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.

Let's walk through an example of how we could process frontmatter out of our MDX source:

```jsx
import { serialize } from 'next-mdx-remote/serialize'
import { MDXRemote } from 'next-mdx-remote'

import matter from 'gray-matter'

import Test from '../components/test'

const components = { Test }

export default function TestPage({ source, frontMatter }) {
return (
<div className="wrapper">
<h1>{frontMatter.title}</h1>
<MDXRemote {...source} components={components} />
</div>
)
}

export async function getStaticProps() {
// MDX text - can be from a local file, database, anywhere
const source = `---
title: Test
---

Some **mdx** text, with a component <Test name={title}/>
`

const { content, data } = matter(source)
const mdxSource = await serialize(content, { scope: data })
return { props: { source: mdxSource, frontMatter: data } }
}
```

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.

### Replacing default components

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).
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/).

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

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

### Environment Targets

The code generated by `next-mdx-remote`, which is used to actually render the MDX, is transformed to support: `>= node 12, es2020`.
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`.

### `import` / `export`

Expand Down Expand Up @@ -410,10 +409,13 @@ export default function ExamplePage({ mdxSource }: Props) {
)
}

export const getStaticProps: GetStaticProps<{mdxSource: MDXRemoteSerializeResult}> = async () => {
const mdxSource = await serialize('some *mdx* content: <ExampleComponent />')
return { props: { mdxSource } }
}
export const getStaticProps: GetStaticProps<{mdxSource: MDXRemoteSerializeResult}> =
async () => {
const mdxSource = await serialize(
'some *mdx* content: <ExampleComponent />'
)
return { props: { mdxSource } }
}
```

## Migrating from v2 to v3
Expand Down
20 changes: 19 additions & 1 deletion __tests__/fixtures/basic/mdx/test.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import Bar from 'bar'

# Headline

<Test name={name} />
<Test name={frontmatter.name} />

<ContextConsumer />

Expand All @@ -16,3 +16,21 @@ Some **markdown** content
~> Alert

<Dynamic />

```shell-session
curl localhost
```

This is more text.

~> < client node IP >:9999

"Authorize \<GITHUB_USER\>"

(support for version \<230)

### Some version \<= 1.3.x

#### metric.name.\<operation>.\<mount>

< 8ms
3 changes: 3 additions & 0 deletions __tests__/fixtures/basic/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"private": true
}
16 changes: 9 additions & 7 deletions __tests__/fixtures/basic/pages/index.jsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import fs from 'fs'
import path from 'path'
import matter from 'gray-matter'
import { createContext, useEffect, useState } from 'react'
import dynamic from 'next/dynamic'
import { serialize } from '../../../../serialize'
Expand Down Expand Up @@ -34,7 +33,7 @@ const MDX_COMPONENTS = {
Dynamic: dynamic(() => import('../components/dynamic')),
}

export default function TestPage({ data, mdxSource }) {
export default function TestPage({ mdxSource }) {
const [providerOptions, setProviderOptions] = useState(PROVIDER)

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

return (
<>
<h1>{data.title}</h1>
<h1>{mdxSource.frontmatter.title}</h1>
<TestContext.Provider {...providerOptions.props}>
<MDXRemote {...mdxSource} components={MDX_COMPONENTS} scope={data} />
<MDXRemote {...mdxSource} components={MDX_COMPONENTS} />
</TestContext.Provider>
</>
)
}

export async function getStaticProps() {
const fixturePath = path.join(process.cwd(), 'mdx/test.mdx')
const { data, content } = matter(fs.readFileSync(fixturePath, 'utf8'))
const mdxSource = await serialize(content, {
const source = await fs.promises.readFile(fixturePath, 'utf8')

const mdxSource = await serialize(source, {
mdxOptions: { remarkPlugins: [paragraphCustomAlerts] },
parseFrontmatter: true,
})
return { props: { mdxSource, data } }

return { props: { mdxSource } }
}
Loading