Add Tailwind CSS toggle, Markdown/WASM modes, and npm import resolution

- Tailwind CSS: toolbar checkbox injects Play CDN into preview, persisted
  per-fiddle via new options JSON column
- Markdown mode: uses marked.js CDN, renders markdown to HTML preview with
  CSS tab for custom styling
- WASM mode: starter template with inline WebAssembly add function, supports
  top-level await via module detection
- npm imports: auto-detect bare import specifiers in module code and inject
  importmap pointing to esm.sh CDN
- Module auto-detection for html-css-js mode (import/export statements)
- DB migration adds options column, server passes through all API endpoints
- All features work across preview, export, and embed
This commit is contained in:
root
2026-02-26 15:15:53 -06:00
parent e41c3e7dc4
commit 77f64d2862
13 changed files with 273 additions and 41 deletions

View File

@@ -3,6 +3,7 @@ import { loadScript } from './utils.js';
let tsLoaded = false;
let babelLoaded = false;
let svelteLoaded = false;
let markedLoaded = false;
async function ensureTypeScript() {
if (tsLoaded) return;
@@ -31,6 +32,19 @@ async function ensureBabel() {
babelLoaded = true;
}
async function ensureMarked() {
if (markedLoaded) return;
await loadScript('https://cdnjs.cloudflare.com/ajax/libs/marked/15.0.7/marked.min.js');
if (typeof marked === 'undefined') {
await new Promise((resolve) => {
const check = setInterval(() => {
if (typeof marked !== 'undefined') { clearInterval(check); resolve(); }
}, 50);
});
}
markedLoaded = true;
}
async function ensureSvelte() {
if (svelteLoaded) return;
await loadScript('https://unpkg.com/svelte@4.2.19/compiler.cjs');
@@ -45,8 +59,10 @@ export async function compileJs(code, mode) {
if (!code.trim()) return { js: '' };
switch (mode) {
case 'html-css-js':
return { js: code };
case 'html-css-js': {
const isModule = /(?:^|\n)\s*(?:import\s|export\s)/m.test(code);
return { js: code, isModule };
}
case 'typescript':
return compileTypeScript(code);
@@ -63,6 +79,16 @@ export async function compileJs(code, mode) {
case 'svelte':
return compileSvelte(code);
case 'markdown':
return compileMarkdown(code);
case 'wasm': {
// WASM starter uses top-level await, always treat as module
const hasModuleSyntax = /(?:^|\n)\s*(?:import\s|export\s)/m.test(code);
const hasTopLevelAwait = /(?:^|\n)\s*(?:const|let|var)\s+.*=\s*await\b/m.test(code) || /(?:^|\n)\s*await\s/m.test(code);
return { js: code, isModule: hasModuleSyntax || hasTopLevelAwait };
}
default:
return { js: code };
}
@@ -125,6 +151,12 @@ function compileVue(code) {
return { js, extraCss };
}
async function compileMarkdown(code) {
await ensureMarked();
const renderedHtml = marked.parse(code);
return { js: '', renderedHtml };
}
async function compileSvelte(code) {
await ensureSvelte();
const result = svelte.compile(code, {
@@ -185,6 +217,12 @@ export function getFrameworkRuntime(mode) {
bodyHtml: '<div id="app"></div>',
};
case 'markdown':
return { scripts: [], bodyHtml: '' };
case 'wasm':
return { scripts: [], bodyHtml: '' };
default:
return { scripts: [], bodyHtml: '' };
}