Skip to content
Merged
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
95 changes: 73 additions & 22 deletions lib/markdown-to-react-loader.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,24 +3,51 @@ const marked = require('marked');
const Prism = require('prismjs');
const renderer = new marked.Renderer();

const singleReplaceChars = {
'\(': '(',
'\)': ')',
'\{': '{',
'\}': '}',
}
const regexString = `[${Object.keys(singleReplaceChars).join('')}]`;
const regex = new RegExp(regexString, 'gm');

function replaceReact(string) {
return string
.replace(regex, m => singleReplaceChars[m])
.replace(/class="/g, 'className="')
}

const reactSafe = (fn) => {
return function () {
let original = fn.apply(renderer, [...arguments]);
let replaced = replaceReact(original);
return replaced;
}
}

renderer.code = function(code, lang) {
function processCodeBlock (code, lang) {
let className, highlighter, replaced;

// users can easily type in any language they want,
// we don't want the app to blow up in case of bad input.
try {
require(`prismjs/components/prism-${lang}`);
className = `language-${lang}`;
highlighter = Prism.languages[lang];
if (typeof lang === 'undefined') {
className = '';
highlighter = '';
} else {
require(`prismjs/components/prism-${lang}`);
className = `language-${lang}`;
highlighter = Prism.languages[lang];
}
} catch (e) {
console.warn(`Could not find PrismJS language ${lang}`);
console.warn(`Could not find PrismJS language ${lang}`, e);
className = '';
highlighter = '';
}

try {
const wrapped = Prism.highlight(code, highlighter);
const wrapped = Prism.highlight(code, highlighter);
replaced = wrapped.replace(/\n/g, '<br />');
} catch (e) {
console.error(`Failed to highlight syntax for language ${lang}`);
Expand All @@ -31,15 +58,46 @@ renderer.code = function(code, lang) {
}

return `
<pre class="${className}">
<code class="${className}">
${replaced}
</code>
</pre>
`;
};
<pre class="${className}">
<code class="${className}">
${replaced}
</code>
</pre>
`;
}

/**
* Block level renderer methods
* https://marked.js.org/#/USING_PRO.md#block-level-renderer-methods
*/
renderer.code = reactSafe(processCodeBlock);
renderer.blockquote = reactSafe(renderer.blockquote);
renderer.paragraph = reactSafe(renderer.paragraph);
renderer.heading = reactSafe(renderer.heading);
renderer.html = renderer.html;
renderer.hr = reactSafe(renderer.hr);
renderer.list = reactSafe(renderer.list);
renderer.listitem = reactSafe(renderer.listitem);
renderer.checkbox = reactSafe(renderer.checkbox);
renderer.paragraph = reactSafe(renderer.paragraph);
renderer.table = reactSafe(renderer.table);
renderer.tablerow = reactSafe(renderer.tablerow);
renderer.tablecell = reactSafe(renderer.tablecell);

const extraExports = extra => {
/**
* Inline level renderer methods
* https://marked.js.org/#/USING_PRO.md#inline-level-renderer-methods
*/
renderer.strong = reactSafe(renderer.strong);
renderer.em = reactSafe(renderer.em);
renderer.codespan = reactSafe(renderer.codespan);
renderer.br = reactSafe(renderer.br);
renderer.del = reactSafe(renderer.del);
renderer.link = reactSafe(renderer.link);
renderer.image = reactSafe(renderer.image);
renderer.text = reactSafe(renderer.text);

function extraExports(extra) {
let ret = '';
for (var key in extra) {
ret += `
Expand All @@ -66,18 +124,11 @@ module.exports = function (source, map) {
return acc;
}, {});

const replaced = html
.replace(/class="/g, 'className="')
.replace(/\(/g, '&#40;')
.replace(/\)/g, '&#41;')
.replace(/\{/g, '&#123;')
.replace(/\}/g, '&#125;');

const processed = `
import React, { Fragment } from 'react';
${parsed.attributes.imports ? parsed.attributes.imports : ''}
${extraExports(extra)}
const Markdown = () => (<Fragment>${replaced}</Fragment>);
const Markdown = () => (<Fragment>${html}</Fragment>);
export default Markdown;
`;

Expand Down
26 changes: 21 additions & 5 deletions test/all.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ const markdownToReact = require('../lib/markdown-to-react-loader');
const pretter = require('prettier');


const testIO = (inFile, outFile) => {
const expectIO = (inFile, outFile) => {
let input = getFileContents(inFile);
let output = getFileContents(outFile);

Expand All @@ -23,17 +23,33 @@ const getFileContents = file => {
}

test('Compiles hello, world', () => {
testIO('io/simple.md', 'io/simple.js');
expectIO('io/simple.md', 'io/simple.js');
});

test('Compiles file with imports', () => {
testIO('io/imports.md', 'io/imports.js');
expectIO('io/imports.md', 'io/imports.js');
});

test('Compiles file with code block', () => {
testIO('io/codeblock.md', 'io/codeblock.js');
expectIO('io/codeblock.md', 'io/codeblock.js');
});

test('Exports extra front matter as named exports', () => {
testIO('io/data.md', 'io/data.js');
expectIO('io/data.md', 'io/data.js');
});

test('Can work with an async component', () => {
expectIO('io/javascript.md', 'io/javascript.js');
});

test('Properly converts parens & curly brackets', () => {
expectIO('io/replace-chars.md', 'io/replace-chars.js');
});

test('Converts tables and table cells', () => {
expectIO('io/table.md', 'io/table.js');
});

test('Can render everything', () => {
expectIO('io/everything.md', 'io/everything.js');
});
11 changes: 11 additions & 0 deletions test/io/data-function.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import React, { Fragment } from "react";

const module = () => import('my-module');
export { module };

const Markdown = () => (
<Fragment>
<p>Something async</p>
</Fragment>
);
export default Markdown;
5 changes: 5 additions & 0 deletions test/io/data-function.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
module: () => import('my-module')
---

Something async
Loading