Skip to content

Commit

Permalink
chore: add v2 version for renderToJSON method (wix-incubator#65)
Browse files Browse the repository at this point in the history
* chore: add v2 version for renderToJSON method

* chore: bump version
  • Loading branch information
mastertheblaster committed Oct 11, 2021
1 parent 7894e20 commit 338135b
Show file tree
Hide file tree
Showing 6 changed files with 155 additions and 69 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "mjml-react",
"version": "2.0.0",
"version": "2.0.1",
"license": "MIT",
"author": {
"name": "Mantas Miliukas",
Expand Down
3 changes: 2 additions & 1 deletion src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@ import ReactDOMServer from 'react-dom/server';
import mjml2html from 'mjml';

import { renderToJSON } from './utils/render-to-json';
import { renderToJSON2 } from './utils/render-to-json2';

export { render, renderToMjml, renderToJSON };
export { render, renderToMjml, renderToJSON, renderToJSON2 };

export { Mjml } from './mjml';
export { MjmlAccordion } from './mjml-accordion';
Expand Down
67 changes: 1 addition & 66 deletions src/utils/render-to-json.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import React from 'react';
import ReactReconciler from 'react-reconciler';
import ReactDOMServer from 'react-dom/server';

const matchHtmlRegExp = /["'&<>$]/;
import { noop, escapeTextForBrowser, trimContent } from './render-utils';

const reconciler = ReactReconciler({
supportsMutation: true,
Expand Down Expand Up @@ -100,68 +100,3 @@ function toReactElement(element) {
),
);
}

function escapeHtml(string) {
const str = '' + string;
const match = matchHtmlRegExp.exec(str);

if (!match) {
return str;
}

let escape;
let html = '';
let index;
let lastIndex = 0;

for (index = match.index; index < str.length; index++) {
switch (str.charCodeAt(index)) {
case 34: // "
escape = '&quot;';
break;
case 36: // $
escape = '&#36;';
break;
case 38: // &
escape = '&amp;';
break;
case 39: // '
escape = '&#x27;'; // modified from escape-html; used to be '&#39'
break;
case 60: // <
escape = '&lt;';
break;
case 62: // >
escape = '&gt;';
break;
default:
continue;
}

if (lastIndex !== index) {
html += str.substring(lastIndex, index);
}

lastIndex = index + 1;
html += escape;
}

return lastIndex !== index ? html + str.substring(lastIndex, index) : html;
}

function escapeTextForBrowser(text) {
if (typeof text === 'boolean' || typeof text === 'number') {
return '' + text;
}
return escapeHtml(text);
}

function noop() {}

function trimContent(child) {
if (child.content) {
child.content = child.content.trim();
} else if (child.children) {
child.children.forEach(trimContent);
}
}
75 changes: 75 additions & 0 deletions src/utils/render-to-json2.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import React from 'react';
import ReactReconciler from 'react-reconciler';
import ReactDOMServer from 'react-dom/server';

import { noop, escapeTextForBrowser, trimContent } from './render-utils';

const reconciler = ReactReconciler({
supportsMutation: true,
createTextInstance(text) {
return escapeTextForBrowser(text);
},
createInstance(type, props) {
if (!type.startsWith('mj')) {
return { isReact: true, type, props };
}

const { children, dangerouslySetInnerHTML, ...rest } = props;

const res = {
tagName: type,
attributes: rest,
};

Object.keys(res.attributes).forEach((key) => {
const attrKey = res.attributes[key];
if (attrKey === undefined) {
delete res.attributes[key];
}
if (typeof attrKey === 'string') {
res.attributes[key] = escapeTextForBrowser(attrKey);
}
});

if (props.dangerouslySetInnerHTML && props.dangerouslySetInnerHTML.__html) {
// using replace to prevent issue with $ sign in MJML
// https://github.com/mjmlio/mjml2json#L145
res.content = props.dangerouslySetInnerHTML.__html.replace('$', '&#36;');
}

return res;
},
appendChildToContainer(container, child) {
trimContent(child);
container.result = child;
},
appendInitialChild(parent, child) {
if (typeof parent === 'string' || parent.isReact) {
return;
}
if (typeof child === 'string') {
parent.content = (parent.content || '') + child;
} else if (child.isReact) {
const content = ReactDOMServer.renderToStaticMarkup(
React.createElement(child.type, child.props),
);
parent.content = (parent.content || '') + content;
} else {
parent.children = (parent.children || []).concat(child);
}
},
prepareForCommit: noop,
resetAfterCommit: noop,
clearContainer: noop,
appendChild: noop,
finalizeInitialChildren: noop,
getChildHostContext: noop,
getRootHostContext: noop,
shouldSetTextContent: noop,
});

export function renderToJSON2(whatToRender) {
const container = reconciler.createContainer({}, false, false);
reconciler.updateContainer(whatToRender, container, null, null);
return container.containerInfo.result;
}
66 changes: 66 additions & 0 deletions src/utils/render-utils.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
const matchHtmlRegExp = /["'&<>$]/;

export function escapeHtml(string) {
const str = '' + string;
const match = matchHtmlRegExp.exec(str);

if (!match) {
return str;
}

let escape;
let html = '';
let index;
let lastIndex = 0;

for (index = match.index; index < str.length; index++) {
switch (str.charCodeAt(index)) {
case 34: // "
escape = '&quot;';
break;
case 36: // $
escape = '&#36;';
break;
case 38: // &
escape = '&amp;';
break;
case 39: // '
escape = '&#x27;'; // modified from escape-html; used to be '&#39'
break;
case 60: // <
escape = '&lt;';
break;
case 62: // >
escape = '&gt;';
break;
default:
continue;
}

if (lastIndex !== index) {
html += str.substring(lastIndex, index);
}

lastIndex = index + 1;
html += escape;
}

return lastIndex !== index ? html + str.substring(lastIndex, index) : html;
}

export function escapeTextForBrowser(text) {
if (typeof text === 'boolean' || typeof text === 'number') {
return '' + text;
}
return escapeHtml(text);
}

export function noop() {}

export function trimContent(child) {
if (child.content) {
child.content = child.content.trim();
} else if (child.children) {
child.children.forEach(trimContent);
}
}
11 changes: 10 additions & 1 deletion test/render-to-json.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import mjml2json from 'mjml2json';

import {
renderToJSON,
renderToJSON2,
Mjml,
MjmlStyle,
MjmlBody,
Expand Down Expand Up @@ -107,12 +108,20 @@ const useCases = [
>
{' '}Hello World ! <span> Hello World ! </span>{' '}
</MjmlButton>,

<MjmlRaw>
<div>
<div>Nested Element</div>
</div>
</MjmlRaw>,
];

useCases.forEach((tree, i) => {
it(`should render usecase ${i}`, () => {
const mjml = renderToMjml(tree);
expect(mjml2json(mjml)).to.eql(renderToJSON(tree));
const base = mjml2json(mjml);
expect(base).to.eql(renderToJSON(tree));
expect(base).to.eql(renderToJSON2(tree));
});
});

Expand Down

0 comments on commit 338135b

Please sign in to comment.