11/* eslint-disable @babel/no-invalid-this */
22import type { LoaderDefinition } from "webpack"
3+ import type { Program , Identifier , ExportDefaultDeclaration } from "estree"
34import type { NodeMap } from "./types"
45import 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
31151export default gatsbyLayoutLoader
0 commit comments