1111 * <outDir>/dist/styles.css (concat colors_and_type.css + src/styles.css)
1212 * <outDir>/docs/index.html (component reference, card grid)
1313 * <outDir>/docs/docs.css (per-component page styles)
14- * <outDir>/docs/runner.js (Babel + render helper)
15- * <outDir>/docs/playground/index.html (editable playground)
14+ * <outDir>/docs/runner.js (render helper — no in-browser compiler )
15+ * <outDir>/docs/playground/index.html (editable playground; uses Babel )
1616 * <outDir>/docs/<Name>/index.html (one per runtime export)
1717 *
1818 * Source of truth (parsed from the repo):
2222 * src/charts/index.ts (charts subpath exports — "Charts" category)
2323 *
2424 * Every component page renders the live React component via the IIFE
25- * bundle plus Babel-standalone. There are no hand-written HTML cards.
25+ * bundle. JSX in @example blocks is pre-transformed to JS at build
26+ * time via esbuild, so component pages do not load Babel-standalone in
27+ * the browser. The editable playground is the one exception — it keeps
28+ * Babel-standalone since users edit JSX live there.
2629 *
27- * Pure Node built-ins.
30+ * Pure Node built-ins + esbuild (already a dev-dep for the IIFE bundle) .
2831 */
2932
3033import { readFileSync , writeFileSync , mkdirSync } from "node:fs" ;
34+ import { transformSync } from "esbuild" ;
3135import { join , resolve } from "node:path" ;
3236import { generateDemos } from "./component-examples.mjs" ;
3337import { extractInterfaces , extractTypeAliases , parseRuntimeExports , parseChartsExports } from "./parse-source.mjs" ;
@@ -629,9 +633,34 @@ html { scroll-behavior: smooth; }
629633` ;
630634}
631635
636+ // Pre-transform a JSX expression string into a JS expression at build
637+ // time. The runner splices this into "return (...)" inside a factory
638+ // scoped to `var { React, Button, ... } = R`, so the transformed output
639+ // references React.createElement / React.Fragment, both of which the
640+ // bundle exports. No Babel-standalone needed in the browser.
641+ function compileDemo ( jsxCode ) {
642+ if ( ! jsxCode ) return "" ;
643+ // esbuild requires statement-shape input — wrap as `(EXPR)` so the
644+ // single JSX expression survives as a single expression on output.
645+ const result = transformSync ( `(${ jsxCode } )` , {
646+ loader : "tsx" ,
647+ jsx : "transform" ,
648+ jsxFactory : "React.createElement" ,
649+ jsxFragment : "React.Fragment" ,
650+ target : "es2020" ,
651+ minify : false ,
652+ } ) ;
653+ // esbuild emits the expression as a statement: "(...);\n". Strip the
654+ // trailing punctuation so the result drops cleanly into "return ( … )".
655+ return result . code . replace ( / ; \s * $ / , "" ) . trimEnd ( ) ;
656+ }
657+
632658function renderRunner ( ) {
633- return `// Shared runtime for component live previews, the playground, and the
634- // charts subpath. Loaded after the IIFE bundle(s) and Babel-standalone.
659+ return `// Shared runtime for component live previews and chart docs pages.
660+ // Loaded after the IIFE bundle(s). Demo code is pre-transformed to JS
661+ // at build time, so this runner only needs to eval and render — there
662+ // is no JSX compiler in the page. (The editable playground keeps
663+ // Babel-standalone because it transforms whatever the user types.)
635664//
636665// runExample(code, targetId) — core components, uses window.RCS
637666// runChartExample(code, targetId) — chart components, uses window.RCSCharts
@@ -642,7 +671,7 @@ function renderRunner() {
642671 function showError(target, msg) {
643672 target.innerHTML = '<div class="err">' + escapeHtml(msg) + '</div>';
644673 }
645- function transformExample (R, code) {
674+ function buildFactorySrc (R, code) {
646675 var names = Object.keys(R || {}).filter(function (n) { return n !== "default"; });
647676 return (
648677 "(function() { var R = arguments[0]; " +
@@ -655,12 +684,9 @@ function renderRunner() {
655684 var target = document.getElementById(targetId || "preview");
656685 if (!target) return;
657686 if (!global.RCS) return showError(target, "window.RCS not loaded");
658- if (!global.Babel) return showError(target, "Babel standalone not loaded");
659- var wrapped = transformExample(global.RCS, code);
660- var transformed, factory, result;
661- try { transformed = global.Babel.transform(wrapped, { presets: ["env", "react"] }).code; }
662- catch (e) { return showError(target, "Compile error: " + (e && e.message || e)); }
663- try { factory = (0, eval)(transformed); result = factory(global.RCS); }
687+ var src = buildFactorySrc(global.RCS, code);
688+ var factory, result;
689+ try { factory = (0, eval)(src); result = factory(global.RCS); }
664690 catch (e) { return showError(target, "Runtime error: " + (e && e.message || e)); }
665691 try {
666692 var R = global.RCS;
@@ -675,12 +701,9 @@ function renderRunner() {
675701 var target = document.getElementById(targetId || "preview");
676702 if (!target) return;
677703 if (!global.RCSCharts) return showError(target, "window.RCSCharts not loaded");
678- if (!global.Babel) return showError(target, "Babel standalone not loaded");
679- var wrapped = transformExample(global.RCSCharts, code);
680- var transformed, factory, result;
681- try { transformed = global.Babel.transform(wrapped, { presets: ["env", "react"] }).code; }
682- catch (e) { return showError(target, "Compile error: " + (e && e.message || e)); }
683- try { factory = (0, eval)(transformed); result = factory(global.RCSCharts); }
704+ var src = buildFactorySrc(global.RCSCharts, code);
705+ var factory, result;
706+ try { factory = (0, eval)(src); result = factory(global.RCSCharts); }
684707 catch (e) { return showError(target, "Runtime error: " + (e && e.message || e)); }
685708 try {
686709 var R = global.RCSCharts;
@@ -1072,14 +1095,20 @@ function renderComponentPage(name) {
10721095 // The runner functions themselves verify their bundle is loaded (window.RCS
10731096 // for runExample, window.RCSCharts for runChartExample) and surface the
10741097 // failure inline, so we only need to guard against runner.js itself failing.
1098+ // JSX is pre-transformed at build time via compileDemo() — the runtime
1099+ // string passed to runFn is plain JS (References React.createElement /
1100+ // React.Fragment, both destructured from R inside the runner factory).
1101+ // The displayed code panel and copy-button still receive the original JSX
1102+ // via DEMO_CODES below.
10751103 const runFn = chart ? "runChartExample" : "runExample" ;
1076- const runCalls = demos . map ( ( d , i ) => `${ runFn } (${ JSON . stringify ( d . code ) } , "demo-render-${ i } ");` ) . join ( "\n " ) ;
1104+ const compiledDemos = demos . map ( ( d ) => compileDemo ( d . code ) ) ;
1105+ const runCalls = compiledDemos . map ( ( js , i ) => `${ runFn } (${ JSON . stringify ( js ) } , "demo-render-${ i } ");` ) . join ( "\n " ) ;
10771106 const codeJson = JSON . stringify ( demos . map ( ( d ) => d . code ) ) ;
1107+ const bundleGlobal = chart ? "window.RCSCharts" : "window.RCS" ;
10781108
10791109 const tail = `
10801110 <script src="../bundle/rcs.iife.js"></script>${ chart ? `
10811111 <script src="../bundle/rcs-charts.iife.js"></script>` : "" }
1082- <script src="https://unpkg.com/@babel/standalone@7.29.0/babel.min.js"></script>
10831112 <script src="../runner.js"></script>
10841113 <script>
10851114 (function () {
@@ -1149,8 +1178,9 @@ function renderComponentPage(name) {
11491178 li.style.display = t.indexOf(q) >= 0 ? '' : 'none';
11501179 });
11511180 });
1152- // Run demos as soon as bundle + babel are ready.
1153- if (window.RCS && window.Babel) runAll();
1181+ // Run demos as soon as the IIFE bundle for this page has loaded.
1182+ // No JSX compiler needed in the browser — demos are pre-transformed.
1183+ if (${ bundleGlobal } ) runAll();
11541184 else window.addEventListener('load', runAll);
11551185 })();
11561186 </script>` ;
0 commit comments