Load Sucrase only when a JSX/TSX preview is opened, remove the hand-written transform, and leave subjective render evaluation to the reader while retaining structural fence validation.
59 lines
2.1 KiB
JavaScript
59 lines
2.1 KiB
JavaScript
/*
|
|
* 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 });
|
|
}
|
|
}
|