@@ -4,75 +4,111 @@ const matter = require('gray-matter');
4
4
const normalizeNewline = require ( 'normalize-newline' ) ;
5
5
6
6
const EXREG = / e x p o r t \s d e f a u l t \s / g;
7
- const MODREG = / ^ ( i m p o r t | e x p o r t ) \s / ;
7
+ const MODREG = / ^ ( i m p o r t | e x p o r t ) \s / g ;
8
8
const SLIDEREG = / \n - - - \n / ;
9
+ const CODEBLOCKREG = / ( ` { 3 } ) (?: (? = ( \\ ? ) ) \2[ \s \S ] ) * ?\1/ g;
10
+ const INLINECODEREG = / ( ` { 1 } ) (?: (? = ( \\ ? ) ) \2.) * ?\1/ g;
11
+ const SNIPPETREG = / S P E C T A C L E - C O D E - S N I P P E T - \d / g;
9
12
10
13
const nameForSlide = index => `MDXContentWrapper${ index } ` ;
14
+ const codeSnippetPlaceholder = index => `SPECTACLE-CODE-SNIPPET-${ index } ` ;
11
15
12
16
module . exports = async function ( src ) {
13
17
const { data, content } = matter ( src ) ;
14
18
15
19
const inlineModules = [ ] ;
20
+ const codeSnippets = { } ;
21
+ let codeSnippetCounter = 0 ;
16
22
17
23
const callback = this . async ( ) ;
18
24
const options = Object . assign ( { } , getOptions ( this ) , {
19
25
filepath : this . resourcePath
20
26
} ) ;
21
27
22
28
/*
23
- Step 1:
24
- * Set aside all inline import and export statements from the mdx file.
25
- * When mdx.sync compiles MDX into JSX, it will stub any component that doesn't have a corresponding
26
- * import. Therefore, we will re-add all of the imports/exports to each
27
- * slide (then remove and add them again!).
28
- */
29
- const slides = normalizeNewline ( content )
29
+ Step 1:
30
+ * Replace all code blocks and inline code with a temporary string.
31
+ * This prevents the next step from accidentally pulling `import` or `export`
32
+ * statements out of the code snippets (which would later break the JSX with
33
+ * duplicate import statements). We look for pairs of (```) before pairs of
34
+ * (`) so that we don't mismatch code block back ticks.
35
+ */
36
+ const contentWithoutCodeSnippets = normalizeNewline ( content )
37
+ . replace ( CODEBLOCKREG , codeBlock => {
38
+ const placeholderString = codeSnippetPlaceholder ( codeSnippetCounter ) ;
39
+ codeSnippets [ placeholderString ] = codeBlock ;
40
+ codeSnippetCounter ++ ;
41
+ return placeholderString ;
42
+ } )
43
+ . replace ( INLINECODEREG , inlineCode => {
44
+ const placeholderString = codeSnippetPlaceholder ( codeSnippetCounter ) ;
45
+ codeSnippets [ placeholderString ] = inlineCode ;
46
+ codeSnippetCounter ++ ;
47
+ return placeholderString ;
48
+ } ) ;
49
+
50
+ const slides = contentWithoutCodeSnippets
30
51
. split ( '\n' )
52
+ /*
53
+ Step 2:
54
+ * Set aside all inline JSX import and export statements from the MDX file.
55
+ * When mdx.sync() compiles MDX into JSX, it will stub any component that doesn't
56
+ * have a corresponding import. Therefore, we will re-add all of the imports/exports
57
+ * to each slide before compiling the MDX via mdx.sync().
58
+ */
31
59
. map ( line => {
32
60
if ( MODREG . test ( line ) ) {
33
61
inlineModules . push ( line ) ;
34
62
}
35
63
return line ;
36
64
} )
37
65
. filter ( line => ! MODREG . test ( line ) )
38
- . filter ( Boolean )
39
66
. join ( '\n' )
67
+ /*
68
+ Step 3:
69
+ * We can now safely put back the code snippets. This is important to do
70
+ * before compiling the MDX.
71
+ */
72
+ . replace ( SNIPPETREG , placeholderString => {
73
+ const codeSnippet = codeSnippets [ placeholderString ] ;
74
+ return codeSnippet ;
75
+ } )
40
76
/*
41
- Step 2 :
42
- * Split the MDX file by occurences of `---`. This is a reserved symbol
43
- * to denote slide boundaries.
44
- */
77
+ Step 4 :
78
+ * Split the MDX file by occurences of `---`. This is a reserved symbol
79
+ * to denote slide boundaries.
80
+ */
45
81
. split ( SLIDEREG )
46
82
/*
47
- Step 3 :
48
- * As referenced before, we need to add the imports and exports to
49
- * every slide again. That way mdx.sync can find the component definitions
50
- * for any custom components used in the MDX file.
51
- */
83
+ Step 5 :
84
+ * As referenced before, we need to add the imports and exports to
85
+ * every slide again. That way mdx.sync can find the component definitions
86
+ * for any custom components used in the MDX file.
87
+ */
52
88
. map (
53
89
slide => `
54
90
${ inlineModules . join ( '\n' ) } \n
55
91
${ slide } `
56
92
)
57
93
/*
58
- Step 4 :
59
- * Use mdx.sync to compile a separate JSX component for each slide
60
- * written in MDX.
61
- */
94
+ Step 6 :
95
+ * Use mdx.sync to compile a separate JSX component for each slide
96
+ * written in MDX.
97
+ */
62
98
. map ( slide => mdx . sync ( slide , options ) )
63
99
/*
64
- Step 5 :
65
- * mdx.sync will attempt to default export the component generated for each
66
- * slide. However, we have multiple slides and thus multiple generated components.
67
- * We can't export multiple defaults, so we must remove all existing occurences of
68
- * `export default`.
69
- */
100
+ Step 7 :
101
+ * mdx.sync will attempt to default export the component generated for each
102
+ * slide. However, we have multiple slides and thus multiple generated components.
103
+ * We can't export multiple defaults, so we must remove all existing occurences of
104
+ * `export default`.
105
+ */
70
106
. map ( slide => slide . replace ( EXREG , '' ) )
71
107
/*
72
- Step 6 :
73
- * Remove the inline exports/imports again. We don't want duplicate import/export
74
- * statements littered throughout the file output.
75
- */
108
+ Step 8 :
109
+ * Remove the inline exports/imports again. We don't want to duplicate import/export
110
+ * statements littered throughout the file output.
111
+ */
76
112
. map ( slide =>
77
113
slide
78
114
. split ( '\n' )
@@ -82,14 +118,14 @@ ${slide}`
82
118
)
83
119
. map ( slide => slide . trim ( ) )
84
120
/*
85
- Step 7 :
86
- * The generate component from mdx.sync assumes it's the only component that
87
- * will inhabit a file. It has const definitions outside of the auto-named MDXContent
88
- * component. This would be fine if we weren't generating a component for each
89
- * slide. However, in our case we would generate a lot of duplicate variable names.
90
- * Thus, the easiest solution is to wrap each mdx.sync-generated component+const
91
- * definitions in another component that is uniquely named (using slide index).
92
- */
121
+ Step 9 :
122
+ * The generated component from mdx.sync assumes it's the only component that
123
+ * will inhabit a file. It has const definitions outside of the auto-named MDXContent
124
+ * component. This would be fine if we weren't generating a component for each
125
+ * slide. However, in our case we would generate a lot of duplicate variable names.
126
+ * Thus, the easiest solution is to wrap each mdx.sync-generated component+const
127
+ * definitions in another component that is uniquely named (using slide index).
128
+ */
93
129
. map ( ( slide , i ) => {
94
130
const wrapperName = nameForSlide ( i ) ;
95
131
return `function ${ wrapperName } (props) {
@@ -102,10 +138,10 @@ ${wrapperName}.isMDXComponent = true;`;
102
138
const { modules = [ ] } = data ;
103
139
let wrapperNames = [ ] ;
104
140
/*
105
- Step 8 :
106
- * Begin composing the final output. Include React, mdx, modules, and the inline
107
- * export/import statements that we removed in Step 6.
108
- */
141
+ Step 10 :
142
+ * Begin composing the final output. Include React, mdx, modules, and the inline
143
+ * export/import statements that we removed in Step 6.
144
+ */
109
145
let allCode = `/* @jsx mdx */
110
146
import React from 'react'
111
147
import { mdx } from '@mdx-js/react'
@@ -116,19 +152,19 @@ ${inlineModules
116
152
} )
117
153
. join ( '\n' ) } \n\n`;
118
154
/*
119
- Step 9 :
120
- * Add in the slide component definitions. Keep track of the component names.
121
- */
155
+ Step 11 :
156
+ * Add in the slide component definitions. Keep track of the component names.
157
+ */
122
158
slides . forEach ( ( s , i ) => {
123
159
allCode += s + '\n\n' ;
124
160
wrapperNames . push ( nameForSlide ( i ) ) ;
125
161
} ) ;
126
162
/*
127
- Step 10 :
128
- * Finally, declare the default export as an array of the slide components.
129
- * See /examples/mdx/test-mdx.js for how to import and use the generated slide
130
- * components.
131
- */
163
+ Step 12 :
164
+ * Finally, declare the default export as an array of the slide components.
165
+ * See /examples/mdx/test-mdx.js for how to import and use the generated slide
166
+ * components.
167
+ */
132
168
const footer = `export const slideCount = ${ slides . length } ;\n\n
133
169
export default [${ wrapperNames } ]` ;
134
170
allCode += footer ;
0 commit comments