Skip to content
This repository was archived by the owner on Mar 13, 2024. It is now read-only.
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .eslintrc
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ extends:
- formidable/configurations/es6-node
- prettier

env:
jest: true

plugins:
- prettier

Expand Down
4 changes: 4 additions & 0 deletions examples/mdx/slides.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -58,9 +58,13 @@ Standard Text
---

```javascript
import React from 'react';

class SuperCoolComponent extends React.Component {
render() {
return <p>code slide works in markdown too whaaaaat</p>;
}
}

export default SuperCoolComponent;
```
52 changes: 48 additions & 4 deletions lib/helpers.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
'use strict';

const mdx = require('@mdx-js/mdx');
const matter = require('gray-matter');

const EX_REG = /export\sdefault\s/g;
/*
* See this link for an explanation of the regex solution:
* https://stackoverflow.com/questions/6462578/regex-to-match-all-instances-not-inside-quotes/23667311#23667311
* Note that the regex isn't concerned about code blocks (```).
* Tracking pairs of ` should be sufficient to capture code blocks, too.
*/
const EX_REG = /\\`|`(?:\\`|[^`])*`|(^(?:export\sdefault\s)(.*)$)/gm;
const MOD_REG = /\\`|`(?:\\`|[^`])*`|(^(?:import|export).*$)/gm;

const NOTES_MARKER = 'Notes: ';
Expand Down Expand Up @@ -63,8 +64,15 @@ const sync = (content, options) => mdx.sync(content, options);
* slide. However, we have multiple slides and thus multiple generated components.
* We can't export multiple defaults, so we must remove all existing occurences of
* `export default`.
*
* We also take care that `export default` inside code blocks are not removed
* from the output.
*/
const removeDefaultExport = content => content.replace(EX_REG, '');
const removeDefaultExport = content => {
return content.replace(EX_REG, (fullMatch, _, bit) => {
return bit || fullMatch;
});
};

/*
* Remove the inline exports/imports. We don't want to duplicate inline import/export
Expand All @@ -89,15 +97,49 @@ const trim = content => content.trim();
* Thus, the easiest solution is to wrap each mdx.sync-generated component+const
* definitions in another component that is uniquely named.
*/
const wrapComponent = (content, index, type) => {
const wrapComponent = (content, index, type, front) => {
const wrapperName = nameForComponent(index, type);
return `function ${wrapperName}(props) {
${content}
return (<MDXContent {...props} />);
return (<MDXContent {...props} frontmatter={${JSON.stringify(front)}} />);
};
${wrapperName}.isMDXComponent = true;`;
};

/*
* Split the MDX file by occurences of `---`. This is a reserved symbol
* to denote slide boundaries.
*/
const SLIDE_REG = /\n---\n/;
const splitSlides = content => {
return content.split(SLIDE_REG);
};

const SLIDE_TYPE = 'Slide';
const generateSlides = (slides, { inlineModules, options }) => {
const slidesWithFrontmatter = [];

let frontmatter = null;
slides.forEach(slide => {
if (/^\s*layout:/m.test(slide)) {
frontmatter = matter(`---\n${slide}\n---`).data;
return;
}

slidesWithFrontmatter.push([slide, frontmatter]);
frontmatter = null;
});

return slidesWithFrontmatter
.map(([content, f]) => [removeNotes(content), f])
.map(([content, f]) => [addInlineModules(content, inlineModules), f])
.map(([mdxContent, f]) => [sync(mdxContent, options), f])
.map(([content, f]) => [removeDefaultExport(content), f])
.map(([content, f]) => [removeInlineModules(content), f])
.map(([content, f]) => [trim(content), f])
.map(([content, f], index) => wrapComponent(content, index, SLIDE_TYPE, f));
};

module.exports = {
isolateNotes,
removeNotes,
Expand All @@ -108,5 +150,7 @@ module.exports = {
sync,
addInlineModules,
nameForComponent,
splitSlides,
generateSlides,
MOD_REG
};
56 changes: 24 additions & 32 deletions lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,11 @@ const {
addInlineModules,
nameForComponent,
isolateNotes,
removeNotes,
generateSlides,
splitSlides,
MOD_REG
} = helpers;

const SLIDE_REG = /\n---\n/;

const SLIDE_TYPE = 'Slide';
const NOTES_TYPE = 'Notes';

Expand All @@ -34,39 +33,32 @@ module.exports = async function(src) {
filepath: this.resourcePath
});

const separatedContent = normalizeNewline(content)
/*
* Set aside all inline JSX import and export statements from the MDX file.
* When mdx.sync() compiles MDX into JSX, it will stub any component that doesn't
* have a corresponding import. Therefore, we will re-add all of the imports/exports
* to each slide before compiling the MDX via mdx.sync().
*/
.replace(MOD_REG, (value, group1) => {
if (!group1) {
// group1 is empty, so this is not the import/export case we're looking for
return value;
}
// found an inline export or import statement
inlineModules.push(value);
return '';
})
/*
* Split the MDX file by occurences of `---`. This is a reserved symbol
* to denote slide boundaries.
*/
.split(SLIDE_REG);
const separatedContent = splitSlides(
normalizeNewline(content)
/*
* Set aside all inline JSX import and export statements from the MDX file.
* When mdx.sync() compiles MDX into JSX, it will stub any component that doesn't
* have a corresponding import. Therefore, we will re-add all of the imports/exports
* to each slide before compiling the MDX via mdx.sync().
*/
.replace(MOD_REG, (value, group1) => {
if (!group1) {
// group1 is empty, so this is not the import/export case we're looking for
return value;
}
// found an inline export or import statement
inlineModules.push(value);
return '';
})
);

/*
* Process the content and generate an array of slide components
*/
const slides = separatedContent
.map(removeNotes)
.map(mdxContent => addInlineModules(mdxContent, inlineModules))
.map(mdxContent => sync(mdxContent, options))
.map(removeDefaultExport)
.map(removeInlineModules)
.map(trim)
.map((mdxContent, index) => wrapComponent(mdxContent, index, SLIDE_TYPE));
const slides = generateSlides(separatedContent, {
inlineModules,
options
});

/*
* Process the content and generate an array of notes components
Expand Down
48 changes: 48 additions & 0 deletions lib/index.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
'use strict';

const normalizeNewline = require('normalize-newline');
const { splitSlides, generateSlides } = require('./helpers');

const slidesContent = parts => {
const content = parts.join('');
return normalizeNewline(content).replace(/\n[^\n\S]+/g, '\n');
};

describe('Split slides', () => {
it('should split slides', () => {
const content = slidesContent`
# Slide 1

---

## Slide 2

Text
`;
const slides = splitSlides(content);
expect(slides).toHaveLength(2);
expect(slides[0]).toContain('# Slide 1');
expect(slides[1]).toContain('# Slide 2\n\nText');
});
});

describe('generateSlides', () => {
it('should generate slides', () => {
const content = slidesContent`
# Slide 1

---
layout: big-quotes
---

## Slide 2

Text
`;
const slides = generateSlides(splitSlides(content), {
inlineModules: []
});
expect(slides).toHaveLength(2);
expect(slides[1]).toContain('frontmatter={{"layout":"big-quotes"}}');
});
});
5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,15 +23,15 @@
"url": "https://github.com/FormidableLabs/spectacle-mdx-loader.git"
},
"dependencies": {
"@mdx-js/mdx": "^1.5.3",
"@mdx-js/mdx": "^1.6.22",
"gray-matter": "^4.0.2",
"loader-utils": "^1.2.3",
"normalize-newline": "^3.0.0"
},
"devDependencies": {
"@babel/core": "^7.7.7",
"@babel/preset-react": "^7.7.4",
"@mdx-js/react": "^1.5.3",
"@mdx-js/react": "^1.6.22",
"babel-eslint": "^10.0.2",
"babel-loader": "^8.0.6",
"eslint": "^6.7.2",
Expand All @@ -44,6 +44,7 @@
"eslint-plugin-react": "^7.17.0",
"file-loader": "^5.0.2",
"html-webpack-plugin": "^3.2.0",
"jest": "^29.0.2",
"prettier": "^1.19.1",
"prop-types": "^15.7.2",
"raw-loader": "^4.0.0",
Expand Down
Loading