As others mentioned, this has code smell. JSX is an intermediary language intended to be compiled into JavaScript, and it's inefficient to compile it at run time. Also it's generally a bad idea to download and run dynamic executable code. Most would say the better way would be to have components that are driven, not defined, by dynamic data. It should be possible to do this with your use case (even if the logic might be unwieldy), though I'll leave the pros/cons and how to do this to other answers.
Using Babel
But if you trust your dynamically generated code, don't mind the slower user experience, and don't have time/access to rewrite the backend/frontend for a more efficient solution, you can do this with Babel, as mentioned by BENARD Patrick and the linked answer. For this example, we'll use a version of Babel that runs in the client browser called Babel Standalone.
Using Dynamic Modules
There needs to be some way to run the compiled JavaScript. For this example, I'll import it dynamically as a module, but there are other ways. Using eval
can be briefer and runs synchronously, but as most know, is generally considered bad practice.
function SomeComponent(props) {
// Simple component as a placeholder; but it can be something more complicated
return React.createElement('div', null, props.name);
}
async function importJSX(input) {
// Since we'll be dynamically importing this; we'll export it as `default`
var moduleInput = 'export default ' + input;
var output = Babel.transform(
moduleInput,
{
presets: [
// `modules: false` creates a module that can be imported
["env", { modules: false }],
"react"
]
}
).code;
// now we'll create a data url of the compiled code to import it
var dataUrl = 'data:text/javascript;base64,' + btoa(output);
return (await import(dataUrl)).default;
}
(async function () {
var element = await importJSX('<SomeComponent name="Hello World"></SomeComponent>');
var root = ReactDOM.createRoot(document.getElementById('root'));
root.render(element);
})();
<script src="https://unpkg.com/@babel/standalone@7/babel.min.js"></script>
<script src="https://unpkg.com/react@18/umd/react.development.js" crossorigin></script>
<script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js" crossorigin></script>
<div id="root"></div>
Adding a Web Worker
For a better user experience, you'd probably want to move the compilation to a web worker. For example:
worker.js
importScripts('https://unpkg.com/@babel/standalone@7/babel.min.js');
self.addEventListener('message', function ({ data: { input, port }}) {
var moduleInput = 'export default ' + input;
var output = Babel.transform(
moduleInput,
{
presets: [
// `modules: false` creates a module that can be imported
["env", { modules: false }],
"react"
]
}
).code;
port.postMessage({ output });
});
And in your main script:
var worker = new Worker('./worker.js');
async function importJSX(input) {
var compiled = await new Promise((resolve) => {
var channel = new MessageChannel();
channel.port1.onmessage = (e) => resolve(e.data);
worker.postMessage({ input, port: channel.port2 }, [ channel.port2 ]);
});
var dataUrl = 'data:text/javascript;base64,' + btoa(compiled.output);
return (await import(dataUrl)).default;
}
(async function () {
var root = ReactDOM.createRoot(document.getElementById('root'));
var element = await importJSX('<div>Hello World</div>');
root.render(element);
})();
This assumes React
and ReactDOM
are imported already, and there's a HTML element with id root
.
Skipping JSX and Compilation
It's worth mentioning that if you're generating JSX dynamically, it's usually only slightly more complex to instead generate what that JSX would compile to.
For instance:
<SomeComponent onClick={foo}>
<div id="container">Hello World</div>
</SomeComponent>
When compiled for React is something like:
React.createElement(SomeComponent,
{
onClick: foo
},
React.createElement('div',
{
id: 'container'
},
'Hello World'
)
);
(See https://reactjs.org/docs/jsx-in-depth.html for more information on how JSX gets compiled and https://babeljs.io/repl for an interactive website to compile JSX to JS using Babel)
While you could run Babel server-side to do this (which would add additional overhead) or you could find/write a pared-down JSX compiler, you also probably can also just rework the functions that return JSX code to ones that return regular JavaScript code.
This offers significant performance improvements client-side since they wouldn't be downloading and running babel.
To actually then use this native JavaScript code, you can export it by prepending export default
to it, e.g.:
export default React.createElement(SomeComponent,
...
);
And then dynamically importing it in your app, with something like:
async function displayInfo() {
let component = (await import(endpointURL)).default;
ReactDom.render(component, '#someDiv');
}