Sanitary HTML can be turned into React Components, which can be run both on the server and on the client, by analyzing the html string and converting the resulting nodes into React elements.
const React = require('react'); const ReactDOMServer = require('react-dom/server'); const str = `<div>divContent<p> para 1</p><p> para 2</p><gallery image-ids="" /><player video-id="" /><p> para 3</p><gallery image-ids="[1, 3]"/></div>`; var parse = require('xml-parser'); const Gallery = () => React.createElement('div', null, 'Gallery comp'); const Player = () => React.createElement('div', null, 'Player comp'); const componentMap = { gallery: Gallery, player: Player }; const traverse = (cur, props) => { return React.createElement( componentMap[cur.name] || cur.name, props, cur.children.length === 0 ? cur.content: Array.prototype.map.call(cur.children, (c, i) => traverse(c, { key: i })) ); }; const domTree = parse(str).root; const App = traverse( domTree ); console.log( ReactDOMServer.renderToString( App ) );
Please note that you do not need, in your opinion, not JSX / TSX, but the React Nodes tree for the React renderer (ReactDOM in this case). JSX is just syntactic sugar, and converting it back and forth is not required unless you want to support React output in your code base.
Forgive the html simplified syntax markup. Its for illustrative purposes only. You might want to use a specification-compatible library to parse the input html or whatever suits your use case.
Make sure that the client-side package receives the same App component, otherwise you can respond to the client-side script to recreate the DOM tree and you will lose all the benefits of server-side rendering.
You can also take advantage of React 16 thread with the above approach.
Solving the problem of props
Details will be available to you from the tree as attributes and can be transferred as details (upon careful consideration of your use case).
const React = require('react'); const ReactDOMServer = require('react-dom/server'); const str = `<div>divContent<p> para 1</p><p> para 2</p><gallery image-ids="" /><player video-id="" /><p> para 3</p><gallery image-ids="[1, 3]"/></div>`; var parse = require('xml-parser'); const Gallery = props => React.createElement('div', null, `Gallery comp: Props ${JSON.stringify(props)}`); const Player = () => React.createElement('div', null, 'Player comp'); const componentMap = { gallery: Gallery, player: Player }; const attrsToProps = attributes => { return Object.keys(attributes).reduce((acc, k) => { let val; try { val = JSON.parse(attributes[k]) } catch(e) { val = null; } return Object.assign( {}, acc, { [ k.replace(/\-/g, '') ]: val } ); }, {}); }; const traverse = (cur, props) => { const propsFromAttrs = attrsToProps(cur.attributes); const childrenNodes = Array.prototype.map.call(cur.children, (c, i) => { return traverse( c, Object.assign( {}, { key: i } ) ); }); return React.createElement( componentMap[cur.name] || cur.name, Object.assign( {}, props, propsFromAttrs ), cur.children.length === 0 ? cur.content: childrenNodes ); }; const domTree = parse(str).root; const App = traverse( domTree ); console.log( ReactDOMServer.renderToString( App ) );
Be careful with custom attributes, although you might want to follow this rfc . Stick with camelCase if possible.