Skip to content

Instantly share code, notes, and snippets.

@jamesseanwright
Last active October 11, 2019 08:56
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save jamesseanwright/6b76b5d26776f7796f5fbc479d22a868 to your computer and use it in GitHub Desktop.
Save jamesseanwright/6b76b5d26776f7796f5fbc479d22a868 to your computer and use it in GitHub Desktop.
Toy, stack-based HTML renderer with React.createElement-compliant element creator
'use strict';
const indentWidth = 2;
const selfClosingElements = [
'meta',
'link',
];
const indent = (string, depth) =>
`${' '.repeat(depth * indentWidth)}${string}`;
const serialiseAttrs = attrs => {
const entries = Object.entries(attrs);
return entries.length
? ` ${entries.map(([name, value]) => `${name}="${value}"`)}`
: '';
};
const isFunctionalComponent = element =>
typeof element.type === 'function';
const renderWithChildren = (element, stack, depth) => {
stack.push(indent(`<${element.type}${serialiseAttrs(element.props)}>`, depth));
element.children.forEach(child =>
typeof child === 'string'
? stack.push(indent(child, depth + 1))
: render(child, stack, depth + 1)
);
stack.push(indent(`</${element.type}>`, depth));
};
const render = (element, stack, depth) => {
if (isFunctionalComponent(element)) {
render(element.type(element.props), stack, depth);
} else if (selfClosingElements.includes(element.type)) {
stack.push(indent(`<${element.type}${serialiseAttrs(element.props)} />`, depth));
} else {
renderWithChildren(element, stack, depth);
}
return stack.join('\n');
};
const renderToHtml = element => render(element, [], 0);
/* React.createElement's props argument is mandatory,
* transpiled to `null` by @babel/plugin-transform-react-jsx
* whenever an element in JSX has no props. Since default
* values only apply for `undefined` values, we explicitly
* check for its existence with the ternary operator */
const createElement = (type, props, ...children) => ({
type,
props: props ? props : {}, // nullish coalescing would be sick here
children,
});
const App = ({ greeting }) => (
<main data-foo="bar">
<h1>{greeting}</h1>
</main>
);
/* To transpile our JSX to createElement,
* we can use @babel/plugin-react-transform-jsx
* i.e:
*
* .babelrc:
* {
* "plugins": [
* ["@babel/transform-react-jsx", {
* "pragma": "createElement"
* }]
* ]
* }*/
const tree = (
<html lang="en">
<head>
<meta charset="utf-8" /> {/* TODO: propname mappings i.e. charSet */}
<title>Hello world!</title>
</head>
<body>
<App greeting="Hello world!" />
</body>
</html>
);
console.log(renderToHtml(tree));
/* =>
<html lang="en">
<head>
<title>
Hello world!
</title>
</head>
<body>
<main data-foo="bar">
<h1>
Hello world!
</h1>
</main>
</body>
</html>
*/
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment