forked from hashicorp/next-mdx-remote
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathrsc.tsx
83 lines (74 loc) · 2.4 KB
/
rsc.tsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: MPL-2.0
*/
import React from 'react'
import { jsxRuntime } from './jsx-runtime.cjs'
import { MDXRemoteSerializeResult, SerializeOptions } from './types'
import { VFileCompatible } from 'vfile'
import { MDXProvider } from '@mdx-js/react'
import { serialize } from './serialize'
export type MDXRemoteProps = {
source: VFileCompatible
options?: SerializeOptions
/**
* An object mapping names to React components.
* The key used will be the name accessible to MDX.
*
* For example: `{ ComponentName: Component }` will be accessible in the MDX as `<ComponentName/>`.
*/
components?: React.ComponentProps<typeof MDXProvider>['components']
}
export { MDXRemoteSerializeResult }
export type CompileMDXResult<TFrontmatter = Record<string, unknown>> = {
content: React.ReactElement
frontmatter: TFrontmatter
}
export async function compileMDX<TFrontmatter = Record<string, unknown>>({
source,
options,
components = {},
}: MDXRemoteProps): Promise<CompileMDXResult<TFrontmatter>> {
const { compiledSource, frontmatter, scope } = await serialize<
Record<string, unknown>,
TFrontmatter
>(
source,
options,
// Enable RSC importSource
true
)
// if we're ready to render, we can assemble the component tree and let React do its thing
// first we set up the scope which has to include the mdx custom
// create element function as well as any components we're using
const fullScope = Object.assign(
{
opts: jsxRuntime,
},
{ frontmatter },
scope
)
const keys = Object.keys(fullScope)
const values = Object.values(fullScope)
// now we eval the source code using a function constructor
// in order for this to work we need to have React, the mdx createElement,
// and all our components in scope for the function, which is the case here
// we pass the names (via keys) in as the function's args, and execute the
// function with the actual values.
const hydrateFn = Reflect.construct(
Function,
keys.concat(`${compiledSource}`)
)
const Content: React.ElementType = hydrateFn.apply(hydrateFn, values).default
return {
content: <Content components={components} />,
frontmatter,
}
}
/**
* Renders compiled source from next-mdx-remote/serialize.
*/
export async function MDXRemote(props: MDXRemoteProps) {
const { content } = await compileMDX(props)
return content
}