JSX to JS to HTML Converter

1. JSX / TSX — you write this
2. Compiled JS — Babel outputs this
3. Rendered HTML — the browser renders this

`; // https://stackoverflow.com/a/44211814/865719 const blob = new Blob([iframeHtml], { type: "text/html" }); const url = URL.createObjectURL(blob); const i = document.createElement("iframe"); i.src = url; i.sandbox = "allow-scripts"; i.id = "rendering-sandbox"; i.style = "display:none;"; document.body.appendChild(i); i.addEventListener("load", () => { URL.revokeObjectURL(url); renderingSandboxIFrame = i; console.log("Rendering sandbox loaded"); }); console.log("Rendering sandbox configured", url); } setupRenderingSandbox(); // Set up the conversion system ////////////////////////////////////////////////////////////// // Wait for all libraries to load function checkReady() { if (Babel && renderingSandboxIFrame) { console.log("All libraries loaded"); setupConverter(); } else { setTimeout(checkReady, 10); } } async function asyncWriteToEditor(monacoEditor, prettierParser, content) { // https://prettier.io/docs/api if (prettierParser) { const prettyContent = await prettier.format(content, { parser: prettierParser, plugins: prettierPlugins, htmlWhitespaceSensitivity: "ignore", }); monacoEditor.setValue(prettyContent); } else { monacoEditor.setValue(content); } } async function convertInputEditorContent() { console.log("New conversion"); try { const inputEditorContent = monacoInputEditor.getValue(); // const transformedToAutomaticRuntime = Babel.transform(inputEditorContent, { // // presets: ['react'] // // presets: [ ["react", { runtime: "classic" }] ] // presets: [ ["react", { runtime: "automatic" }], ["typescript", { allExtensions: true, isTSX: true }] ] // }).code; // await asyncWriteToEditor(monacoOutputEditorJs, "babel", transformedToAutomaticRuntime); const transformedToClassicRuntime = Babel.transform(inputEditorContent, { presets: [ ["react", { runtime: "classic" }], ["typescript", { allExtensions: true, isTSX: true }], ], }).code; await asyncWriteToEditor(monacoOutputEditorJs, "babel", transformedToClassicRuntime); function wrapTransformedCode(code) { const ast = Babel.packages.parser.parse(code, { sourceType: "module", // plugins: ["javascript"] }); const t = Babel.packages.types; const body = ast.program.body; if (body.length < 1) { body.push( t.returnStatement(t.stringLiteral('Nothing to render. Did you write an "expression" to render?')), ); } const last = body[body.length - 1]; if (t.isExpressionStatement(last)) { // Replace last statement with a return body[body.length - 1] = t.returnStatement(last.expression); } else { body.push( t.returnStatement(t.stringLiteral('Nothing to render. Did you write an "expression" to render?')), ); } // We could wrap everything in a function // const func = t.functionExpression( // t.identifier("renderMe"), // [], // t.blockStatement(body) // ); // // const call = t.callExpression(func, []); // // return Babel.packages.generator.generate(call).code; // For now, simply return the code block. // It will be wrapped in a "new Function()" by the rendering sandbox return Babel.packages.generator.generate(t.blockStatement(body)).code; } const transformedToClassicRuntimeWithReturn = wrapTransformedCode(transformedToClassicRuntime); // Using appOrigin as the target origin doesn't seem to work. // Perhaps due to the iframe's use of sandbox="allow-scripts" without "allow-same-origin" ? I'm not sure. renderingSandboxIFrame.contentWindow.postMessage({ code: transformedToClassicRuntimeWithReturn }, "*"); } catch (e) { monacoOutputEditorJs.setValue("Error: " + e.message); monacoOutputEditorHtml.setValue("Error: " + e.message); } } function setupConverter() { window.addEventListener("message", async (event) => { // The iframe uses sandbox="allow-scripts" without "allow-same-origin". // Therefore: // * It has an opaque origin and event.origin will be "null" // * For now, we'll check event.source only // Ignore unrelated messages if (event.source !== renderingSandboxIFrame.contentWindow) { return; } console.log("Received html result:", event); console.log("event.data:", event.data); if (event.data.error) { monacoOutputEditorHtml.setValue("Error: " + event.data.error); } else if (event.data.html !== undefined) { await asyncWriteToEditor(monacoOutputEditorHtml, "html", event.data.html); } }); monacoInputEditor.onDidChangeModelContent(convertInputEditorContent); // Some sample JSX monacoInputEditor.setValue( `const Foo = () => { return ; } type BarProps = { name: string; }; function Bar({name}: BarProps) { return (

); } // Add a JSX expression to render as HTML, e.g.: <>

This is some JSX to render as HTML

Footer

> `, ); } checkReady();