Skip to content

Commit 7762b13

Browse files
use a better regex to support code snippets
1 parent 8c11803 commit 7762b13

File tree

1 file changed

+42
-67
lines changed

1 file changed

+42
-67
lines changed

mdx-slide-loader.js

+42-67
Original file line numberDiff line numberDiff line change
@@ -4,83 +4,53 @@ const matter = require('gray-matter');
44
const normalizeNewline = require('normalize-newline');
55

66
const EXREG = /export\sdefault\s/g;
7-
const MODREG = /^(import|export)\s/g;
7+
/*
8+
* See this link for an explanation of the regex solution:
9+
* https://stackoverflow.com/questions/6462578/regex-to-match-all-instances-not-inside-quotes/23667311#23667311
10+
* Note that the regex isn't concerned about code blocks (```).
11+
* Tracking pairs of ` should be sufficient to caputre code blocks, too.
12+
*/
13+
const MODREG = /\\`|`(?:\\`|[^`])*`|(^(?:import|export).*$)/gm;
814
const SLIDEREG = /\n---\n/;
9-
const CODEBLOCKREG = /(`{3})(?:(?=(\\?))\2[\s\S])*?\1/g;
10-
const INLINECODEREG = /(`{1})(?:(?=(\\?))\2.)*?\1/g;
11-
const SNIPPETREG = /SPECTACLE-CODE-SNIPPET-\d/g;
1215

1316
const nameForSlide = index => `MDXContentWrapper${index}`;
14-
const codeSnippetPlaceholder = index => `SPECTACLE-CODE-SNIPPET-${index}`;
1517

1618
module.exports = async function(src) {
1719
const { data, content } = matter(src);
1820

1921
const inlineModules = [];
20-
const codeSnippets = {};
21-
let codeSnippetCounter = 0;
2222

2323
const callback = this.async();
2424
const options = Object.assign({}, getOptions(this), {
2525
filepath: this.resourcePath
2626
});
2727

2828
/*
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.
29+
Step 1:
30+
* Set aside all inline JSX import and export statements from the MDX file.
31+
* When mdx.sync() compiles MDX into JSX, it will stub any component that doesn't
32+
* have a corresponding import. Therefore, we will re-add all of the imports/exports
33+
* to each slide before compiling the MDX via mdx.sync().
3534
*/
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
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-
*/
59-
.map(line => {
60-
if (MODREG.test(line)) {
61-
inlineModules.push(line);
35+
const slides = normalizeNewline(content)
36+
.replace(MODREG, (value, group1) => {
37+
if (!group1) {
38+
// group1 is empty, so this is not the import/export case we're looking for
39+
return value;
40+
} else {
41+
// found an inline export or import statement
42+
inlineModules.push(value);
43+
return '';
6244
}
63-
return line;
64-
})
65-
.filter(line => !MODREG.test(line))
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;
7545
})
7646
/*
77-
Step 4:
47+
Step 2:
7848
* Split the MDX file by occurences of `---`. This is a reserved symbol
7949
* to denote slide boundaries.
8050
*/
8151
.split(SLIDEREG)
8252
/*
83-
Step 5:
53+
Step 3:
8454
* As referenced before, we need to add the imports and exports to
8555
* every slide again. That way mdx.sync can find the component definitions
8656
* for any custom components used in the MDX file.
@@ -91,34 +61,39 @@ ${inlineModules.join('\n')}\n
9161
${slide}`
9262
)
9363
/*
94-
Step 6:
64+
Step 4:
9565
* Use mdx.sync to compile a separate JSX component for each slide
9666
* written in MDX.
9767
*/
9868
.map(slide => mdx.sync(slide, options))
9969
/*
100-
Step 7:
70+
Step 5:
10171
* mdx.sync will attempt to default export the component generated for each
10272
* slide. However, we have multiple slides and thus multiple generated components.
10373
* We can't export multiple defaults, so we must remove all existing occurences of
10474
* `export default`.
10575
*/
10676
.map(slide => slide.replace(EXREG, ''))
10777
/*
108-
Step 8:
109-
* Remove the inline exports/imports again. We don't want to duplicate import/export
78+
Step 6:
79+
* Remove the inline exports/imports again. We don't want to duplicate inline import/export
11080
* statements littered throughout the file output.
11181
*/
11282
.map(slide =>
113-
slide
114-
.split('\n')
115-
.filter(line => !MODREG.test(line))
116-
.filter(Boolean)
117-
.join('\n')
83+
slide.replace(MODREG, (value, group1) => {
84+
if (!group1) {
85+
// group1 is empty, so this is not the import/export case we're looking for
86+
return value;
87+
} else {
88+
// found an inline export or import statement
89+
inlineModules.push(value);
90+
return '';
91+
}
92+
})
11893
)
11994
.map(slide => slide.trim())
12095
/*
121-
Step 9:
96+
Step 7:
12297
* The generated component from mdx.sync assumes it's the only component that
12398
* will inhabit a file. It has const definitions outside of the auto-named MDXContent
12499
* component. This would be fine if we weren't generating a component for each
@@ -138,9 +113,9 @@ ${wrapperName}.isMDXComponent = true;`;
138113
const { modules = [] } = data;
139114
let wrapperNames = [];
140115
/*
141-
Step 10:
116+
Step 8:
142117
* Begin composing the final output. Include React, mdx, modules, and the inline
143-
* export/import statements that we removed in Step 8.
118+
* export/import statements that we removed in Step 6.
144119
*/
145120
let allCode = `/* @jsx mdx */
146121
import React from 'react'
@@ -152,15 +127,15 @@ ${inlineModules
152127
})
153128
.join('\n')}\n\n`;
154129
/*
155-
Step 11:
130+
Step 9:
156131
* Add in the slide component definitions. Keep track of the component names.
157132
*/
158133
slides.forEach((s, i) => {
159134
allCode += s + '\n\n';
160135
wrapperNames.push(nameForSlide(i));
161136
});
162137
/*
163-
Step 12:
138+
Step 10:
164139
* Finally, declare the default export as an array of the slide components.
165140
* See /examples/mdx/test-mdx.js for how to import and use the generated slide
166141
* components.

0 commit comments

Comments
 (0)