Skip to content

Commit 34420b5

Browse files
support code blocks and inline code in WP loader
1 parent 644ffa5 commit 34420b5

File tree

2 files changed

+89
-53
lines changed

2 files changed

+89
-53
lines changed

mdx-slide-loader.js

+88-52
Original file line numberDiff line numberDiff line change
@@ -4,75 +4,111 @@ 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/;
7+
const MODREG = /^(import|export)\s/g;
88
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;
912

1013
const nameForSlide = index => `MDXContentWrapper${index}`;
14+
const codeSnippetPlaceholder = index => `SPECTACLE-CODE-SNIPPET-${index}`;
1115

1216
module.exports = async function(src) {
1317
const { data, content } = matter(src);
1418

1519
const inlineModules = [];
20+
const codeSnippets = {};
21+
let codeSnippetCounter = 0;
1622

1723
const callback = this.async();
1824
const options = Object.assign({}, getOptions(this), {
1925
filepath: this.resourcePath
2026
});
2127

2228
/*
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
3051
.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+
*/
3159
.map(line => {
3260
if (MODREG.test(line)) {
3361
inlineModules.push(line);
3462
}
3563
return line;
3664
})
3765
.filter(line => !MODREG.test(line))
38-
.filter(Boolean)
3966
.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+
})
4076
/*
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+
*/
4581
.split(SLIDEREG)
4682
/*
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+
*/
5288
.map(
5389
slide => `
5490
${inlineModules.join('\n')}\n
5591
${slide}`
5692
)
5793
/*
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+
*/
6298
.map(slide => mdx.sync(slide, options))
6399
/*
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+
*/
70106
.map(slide => slide.replace(EXREG, ''))
71107
/*
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+
*/
76112
.map(slide =>
77113
slide
78114
.split('\n')
@@ -82,14 +118,14 @@ ${slide}`
82118
)
83119
.map(slide => slide.trim())
84120
/*
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+
*/
93129
.map((slide, i) => {
94130
const wrapperName = nameForSlide(i);
95131
return `function ${wrapperName}(props) {
@@ -102,10 +138,10 @@ ${wrapperName}.isMDXComponent = true;`;
102138
const { modules = [] } = data;
103139
let wrapperNames = [];
104140
/*
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+
*/
109145
let allCode = `/* @jsx mdx */
110146
import React from 'react'
111147
import { mdx } from '@mdx-js/react'
@@ -116,19 +152,19 @@ ${inlineModules
116152
})
117153
.join('\n')}\n\n`;
118154
/*
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+
*/
122158
slides.forEach((s, i) => {
123159
allCode += s + '\n\n';
124160
wrapperNames.push(nameForSlide(i));
125161
});
126162
/*
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+
*/
132168
const footer = `export const slideCount = ${slides.length};\n\n
133169
export default [${wrapperNames}]`;
134170
allCode += footer;

webpack.config.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ module.exports = {
5353
}
5454
},
5555
{
56-
test: /\.mdx$/,
56+
test: /\.mdx?$/,
5757
use: [
5858
{
5959
loader: require.resolve(babelLoader),

0 commit comments

Comments
 (0)