/* * JSX/TSX compiler adapter. * * JSX and TypeScript are parsed by Sucrase rather than by preview-specific * lexer code. The dependency is dynamically imported so ordinary chat and * HTML/SVG previews do not download the compiler chunk. Only this small adapter * stays in the main bundle. * * Sucrase's CommonJS transform is intentional: a preview frame has no module * loader or network access, but languages.js can provide local React/Preact * modules through a tiny `require` shim. Unsupported imports then fail loudly * at evaluation time with the package name that cannot be loaded. */ export class TransformError extends Error { constructor(message, options) { super(message, options); this.name = "TransformError"; } } /** * Find fallback component declarations for model output that omits an export. * * This is deliberately not syntax transformation. Sucrase owns all parsing; * these names only form guarded `typeof Name !== "undefined"` mount choices. * A false match is therefore ignored at runtime. Default exports and App take * precedence, so this compatibility fallback is used only for a bare component * such as `function Counter() { ... }`. */ function componentCandidates(source) { const names = []; const declarations = /\b(?:function|class|const|let|var)\s+([A-Z][$\w]*)/g; for (const match of source.matchAll(declarations)) { if (!names.includes(match[1])) names.push(match[1]); } return names; } /** Compile a self-contained JSX/TSX component into browser-ready CommonJS. */ export async function transform(source) { const input = String(source ?? ""); try { const { transform: compile } = await import("sucrase"); const { code } = compile(input, { transforms: ["typescript", "jsx", "imports"], jsxPragma: "h", jsxFragmentPragma: "Fragment", production: true, filePath: "preview.tsx", }); return { code, components: componentCandidates(input) }; } catch (error) { const detail = error && error.message ? error.message : String(error); throw new TransformError(`Could not compile JSX/TSX: ${detail}`, { cause: error }); } }