Skip to content

Commit 550aff9

Browse files
committed
properly inject compiled MDX as children
1 parent fe487cd commit 550aff9

File tree

8 files changed

+156
-21
lines changed

8 files changed

+156
-21
lines changed

benchmarks/gabe-fs-mdx/src/templates/blog-post.js

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,13 @@ import { graphql, Link } from "gatsby"
33
import Layout from "../components/layout_1"
44

55
const Article = ({ children, pageContext, data }) => {
6-
console.log({ data })
76
return (
87
<Layout>
98
<Link to="/">Go back to index page</Link>
109
<div>
1110
<h1>{pageContext.frontmatter.title}</h1>
1211
{children}
1312
</div>
14-
<pre>
15-
<code>{JSON.stringify({ children, pageContext, data }, null, 2)}</code>
16-
</pre>
1713
</Layout>
1814
)
1915
}

packages/gatsby-plugin-mdx/package.json

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,11 @@
5959
"mdast-util-to-markdown": "^1.3.0",
6060
"unified": "^10.1.2",
6161
"unist-util-visit": "^4.1.0",
62-
"vfile": "^5.3.2"
62+
"vfile": "^5.3.2",
63+
"acorn": "^8.7.1",
64+
"acorn-jsx": "^5.3.2",
65+
"astring": "^1.8.3",
66+
"estree-util-build-jsx": "^2.1.0"
6367
},
6468
"devDependencies": {
6569
"@babel/cli": "^7.17.10",
@@ -70,6 +74,7 @@
7074
},
7175
"peerDependencies": {
7276
"@mdx-js/react": "^2.0.0",
77+
"@types/estree": "^0.0.50",
7378
"gatsby": "^4.0.0-next",
7479
"gatsby-source-filesystem": "^4.0.0-next",
7580
"loader-utils": "^3.2.0",

packages/gatsby-plugin-mdx/src/gatsby-layout-loader.ts

Lines changed: 123 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
/* eslint-disable @babel/no-invalid-this */
22
import type { LoaderDefinition } from "webpack"
3+
import type { Program, Identifier, ExportDefaultDeclaration } from "estree"
34
import type { NodeMap } from "./types"
45
import type { IMdxPluginOptions } from "./plugin-options"
56

@@ -11,7 +12,7 @@ export interface IGatsbyLayoutLoaderOptions {
1112
}
1213

1314
// Wrap MDX content with Gatsby Layout component
14-
const gatsbyLayoutLoader: LoaderDefinition = async function (source, ...rest) {
15+
const gatsbyLayoutLoader: LoaderDefinition = async function (source) {
1516
const { nodeMap }: IGatsbyLayoutLoaderOptions = getOptions(this)
1617

1718
const mdxPath = this.resourceQuery.split(`__mdxPath=`)[1]
@@ -24,8 +25,127 @@ const gatsbyLayoutLoader: LoaderDefinition = async function (source, ...rest) {
2425
)
2526
}
2627

27-
// @todo this should be an AST transformation and not string manipulation
28-
return `import RENDERED_MDX from "${mdxPath}"\n${source}\nconsole.log(RENDERED_MDX)`
28+
const acorn = await import(`acorn`)
29+
const { default: jsx } = await import(`acorn-jsx`)
30+
const { generate } = await import(`astring`)
31+
const { buildJsx } = await import(`estree-util-build-jsx`)
32+
33+
try {
34+
const tree = acorn.Parser.extend(jsx()).parse(source, {
35+
ecmaVersion: 2020,
36+
sourceType: `module`,
37+
locations: true,
38+
})
39+
40+
const AST = tree as unknown as Program
41+
42+
// Throw when tree is not a Program
43+
if (!AST.body && !AST.sourceType) {
44+
throw new Error(
45+
`Invalid AST. Parsed source code did not return a Program`
46+
)
47+
}
48+
49+
// Inject import to actual MDX file at the top of the file
50+
AST.body.unshift({
51+
type: `ImportDeclaration`,
52+
specifiers: [
53+
{
54+
type: `ImportDefaultSpecifier`,
55+
local: {
56+
type: `Identifier`,
57+
name: `GATSBY_COMPILED_MDX`,
58+
},
59+
},
60+
],
61+
source: {
62+
type: `Literal`,
63+
value: mdxPath,
64+
},
65+
})
66+
67+
// Replace default export with wrapper function that injects compiled MDX as children
68+
AST.body = AST.body.map(child => {
69+
if (child.type !== `ExportDefaultDeclaration`) {
70+
return child
71+
}
72+
const declaration = child.declaration as unknown as Identifier
73+
if (!declaration.name) {
74+
throw new Error(`Unable to determine default export name`)
75+
}
76+
77+
const pageComponentName = declaration.name
78+
79+
return {
80+
type: `ExportDefaultDeclaration`,
81+
declaration: {
82+
type: `ArrowFunctionExpression`,
83+
id: null,
84+
expression: true,
85+
generator: false,
86+
async: false,
87+
params: [
88+
{
89+
type: `Identifier`,
90+
name: `props`,
91+
},
92+
],
93+
body: {
94+
type: `JSXElement`,
95+
openingElement: {
96+
type: `JSXOpeningElement`,
97+
attributes: [
98+
{
99+
type: `JSXSpreadAttribute`,
100+
argument: {
101+
type: `Identifier`,
102+
name: `props`,
103+
},
104+
},
105+
],
106+
name: {
107+
type: `JSXIdentifier`,
108+
name: pageComponentName,
109+
},
110+
selfClosing: false,
111+
},
112+
closingElement: {
113+
type: `JSXClosingElement`,
114+
name: {
115+
type: `JSXIdentifier`,
116+
name: pageComponentName,
117+
},
118+
},
119+
children: [
120+
{
121+
type: `JSXExpressionContainer`,
122+
expression: {
123+
type: `CallExpression`,
124+
callee: {
125+
type: `Identifier`,
126+
name: `GATSBY_COMPILED_MDX`,
127+
},
128+
arguments: [],
129+
optional: false,
130+
},
131+
},
132+
],
133+
},
134+
},
135+
} as unknown as ExportDefaultDeclaration
136+
})
137+
138+
// @todo what do we do with runtime, pragma and pragmaFrag options? We should still be able to support preact.
139+
buildJsx(AST)
140+
141+
const transformedSource = generate(AST)
142+
143+
return transformedSource
144+
} catch (e) {
145+
throw new Error(
146+
`Unable to inject MDX into JS template:\n${this.resourcePath}\n${e}`
147+
)
148+
}
29149
}
30150

31151
export default gatsbyLayoutLoader

packages/gatsby-plugin-mdx/src/gatsby-mdx-loader.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import type { LoaderDefinition } from "webpack"
33
import type { ProcessorOptions } from "@mdx-js/mdx"
44
import type { NodeMap } from "./types"
55

6+
import grayMatter from "gray-matter"
67
import { getOptions } from "loader-utils"
78

89
import compileMDX from "./compile-mdx"
@@ -25,7 +26,10 @@ const gatsbyMDXLoader: LoaderDefinition = async function (source) {
2526

2627
const { mdxNode, fileNode } = res
2728

28-
return compileMDX(source, mdxNode, fileNode, options)
29+
// Remove frontmatter
30+
const { content } = grayMatter(source)
31+
32+
return compileMDX(content, mdxNode, fileNode, options)
2933
}
3034

3135
export default gatsbyMDXLoader

packages/gatsby-plugin-mdx/src/get-source-plugins-as-remark-plugins.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,9 @@ export async function getSourcePluginsAsRemarkPlugins({
2525
}: IGetSourcePluginsAsRemarkPlugins): Promise<
2626
ProcessorOptions["remarkPlugins"]
2727
> {
28-
const userPluginsFiltered = gatsbyRemarkPlugins.filter(
29-
plugin => typeof plugin.module === `function`
30-
)
28+
const userPluginsFiltered = gatsbyRemarkPlugins
29+
? gatsbyRemarkPlugins.filter(plugin => typeof plugin.module === `function`)
30+
: []
3131

3232
if (!userPluginsFiltered.length) {
3333
return pathPrefix ? [[pathPlugin, { pathPrefix }]] : []

packages/gatsby-plugin-mdx/src/plugin-options.ts

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,10 @@ import deepmerge from "deepmerge"
99
import { IPluginRefObject } from "gatsby-plugin-utils/types"
1010
import { getSourcePluginsAsRemarkPlugins } from "./get-source-plugins-as-remark-plugins"
1111

12-
export interface IMdxPluginOptions extends PluginOptions {
12+
export interface IMdxPluginOptions extends Partial<PluginOptions> {
1313
extensions: [string]
14-
defaultLayouts: { [key: string]: string }
1514
mdxOptions: ProcessorOptions
16-
gatsbyRemarkPlugins: [IPluginRefObject]
15+
gatsbyRemarkPlugins?: [IPluginRefObject]
1716
}
1817
interface IHelpers {
1918
getNode: NodePluginArgs["getNode"]
@@ -33,7 +32,6 @@ export const defaultOptions: MdxDefaultOptions = pluginOptions => {
3332
const options: IMdxPluginOptions = deepmerge(
3433
{
3534
extensions: [`.mdx`],
36-
defaultLayouts: {},
3735
mdxOptions,
3836
},
3937
pluginOptions
@@ -54,7 +52,10 @@ export const enhanceMdxOptions: EnhanceMdxOptions = async (
5452
const options = defaultOptions(pluginOptions)
5553

5654
// Support gatsby-remark-* plugins
57-
if (Object.keys(options.gatsbyRemarkPlugins).length) {
55+
if (
56+
options.gatsbyRemarkPlugins &&
57+
Object.keys(options.gatsbyRemarkPlugins).length
58+
) {
5859
if (!options.mdxOptions.remarkPlugins) {
5960
options.mdxOptions.remarkPlugins = []
6061
}

packages/gatsby/index.d.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -791,7 +791,7 @@ export interface GatsbySSR<
791791
}
792792

793793
export interface PluginOptions {
794-
plugins: unknown[]
794+
// plugins: unknown[]
795795
[key: string]: unknown
796796
}
797797

yarn.lock

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5808,7 +5808,7 @@ acorn-import-assertions@^1.7.6:
58085808
resolved "https://registry.yarnpkg.com/acorn-import-assertions/-/acorn-import-assertions-1.7.6.tgz#580e3ffcae6770eebeec76c3b9723201e9d01f78"
58095809
integrity sha512-FlVvVFA1TX6l3lp8VjDnYYq7R1nyW6x3svAt4nDgrWQ9SBaSh9CnbwgSUTasgfNfOG5HlM1ehugCvM+hjo56LA==
58105810

5811-
acorn-jsx@^5.0.0:
5811+
acorn-jsx@^5.0.0, acorn-jsx@^5.3.2:
58125812
version "5.3.2"
58135813
resolved "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937"
58145814
integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==
@@ -5851,7 +5851,7 @@ acorn@^7.0.0, acorn@^7.1.0, acorn@^7.1.1, acorn@^7.4.0:
58515851
resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.4.1.tgz#feaed255973d2e77555b83dbc08851a6c63520fa"
58525852
integrity sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==
58535853

5854-
acorn@^8.0.0:
5854+
acorn@^8.0.0, acorn@^8.7.1:
58555855
version "8.7.1"
58565856
resolved "https://registry.npmjs.org/acorn/-/acorn-8.7.1.tgz#0197122c843d1bf6d0a5e83220a788f278f63c30"
58575857
integrity sha512-Xx54uLJQZ19lKygFXOWsscKUbsBZW0CPykPhVQdhIeIwrbPmJzqeASDInc8nKBnp/JT6igTs82qPXz069H8I/A==
@@ -6409,7 +6409,7 @@ astral-regex@^2.0.0:
64096409
resolved "https://registry.yarnpkg.com/astral-regex/-/astral-regex-2.0.0.tgz#483143c567aeed4785759c0865786dc77d7d2e31"
64106410
integrity sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==
64116411

6412-
astring@^1.6.0:
6412+
astring@^1.6.0, astring@^1.8.3:
64136413
version "1.8.3"
64146414
resolved "https://registry.npmjs.org/astring/-/astring-1.8.3.tgz#1a0ae738c7cc558f8e5ddc8e3120636f5cebcb85"
64156415
integrity sha512-sRpyiNrx2dEYIMmUXprS8nlpRg2Drs8m9ElX9vVEXaCB4XEAJhKfs7IcX0IwShjuOAjLR6wzIrgoptz1n19i1A==
@@ -10846,6 +10846,15 @@ estree-util-build-jsx@^2.0.0:
1084610846
estree-util-is-identifier-name "^2.0.0"
1084710847
estree-walker "^3.0.0"
1084810848

10849+
estree-util-build-jsx@^2.1.0:
10850+
version "2.1.0"
10851+
resolved "https://registry.yarnpkg.com/estree-util-build-jsx/-/estree-util-build-jsx-2.1.0.tgz#629aa81fbb1b16ed628c7a9334d37bc8a2a3726f"
10852+
integrity sha512-gsBGfsY6LOJUIDwmMkTOcgCX+3r/LUjRBccgHMSW55PHjhZsV13RmPl/iwpAvW8KcQqoN9P0FEFWTSS2Zc5bGA==
10853+
dependencies:
10854+
"@types/estree-jsx" "^0.0.1"
10855+
estree-util-is-identifier-name "^2.0.0"
10856+
estree-walker "^3.0.0"
10857+
1084910858
estree-util-is-identifier-name@^2.0.0:
1085010859
version "2.0.0"
1085110860
resolved "https://registry.npmjs.org/estree-util-is-identifier-name/-/estree-util-is-identifier-name-2.0.0.tgz#e2d3d2ae3032c017b2112832bfc5d8ba938c8010"

0 commit comments

Comments
 (0)