Initial commit: code playground with multi-framework support
Express + SQLite backend with Monaco editor frontend. Supports HTML/CSS/JS, TypeScript, React (JSX/TSX), Vue SFC, and Svelte with live preview, console output, save/fork/share. Includes CSS preprocessors (SCSS, Less), framework-specific compilation (Babel, TypeScript, Svelte compiler), and CDN-loaded runtime libraries for preview rendering.
This commit is contained in:
94
public/js/preview.js
Normal file
94
public/js/preview.js
Normal file
@@ -0,0 +1,94 @@
|
||||
import { getFrameworkRuntime } from './js-preprocessors.js';
|
||||
|
||||
const consoleInterceptor = `
|
||||
<script>
|
||||
(function() {
|
||||
const methods = ['log', 'warn', 'error', 'info', 'debug', 'clear'];
|
||||
methods.forEach(function(method) {
|
||||
var original = console[method];
|
||||
console[method] = function() {
|
||||
if (method === 'clear') {
|
||||
parent.postMessage({ type: 'console', method: 'clear' }, '*');
|
||||
if (original) original.apply(console, arguments);
|
||||
return;
|
||||
}
|
||||
var args = Array.from(arguments).map(function(a) {
|
||||
if (a === null) return 'null';
|
||||
if (a === undefined) return 'undefined';
|
||||
if (typeof a === 'object') {
|
||||
try { return JSON.stringify(a, null, 2); } catch(e) { return String(a); }
|
||||
}
|
||||
return String(a);
|
||||
});
|
||||
parent.postMessage({ type: 'console', method: method, args: args }, '*');
|
||||
if (original) original.apply(console, arguments);
|
||||
};
|
||||
});
|
||||
window.onerror = function(msg, url, line, col) {
|
||||
parent.postMessage({ type: 'console', method: 'error', args: ['Error: ' + msg + ' (line ' + line + ')'] }, '*');
|
||||
};
|
||||
})();
|
||||
<\/script>
|
||||
`;
|
||||
|
||||
/**
|
||||
* Escape </script in user code to prevent premature HTML script tag closing.
|
||||
*/
|
||||
function escapeScriptClose(code) {
|
||||
return code.replace(/<\/script/gi, '<\\/script');
|
||||
}
|
||||
|
||||
/**
|
||||
* Build script tags for the preview iframe.
|
||||
* Uses static <script src> tags for runtime libraries (parser-blocking per HTML spec)
|
||||
* followed by an inline <script> for user code.
|
||||
*/
|
||||
function buildLoaderScript(runtimeUrls, userJs, isModule) {
|
||||
if (isModule) {
|
||||
return `\n<script type="module">\n${escapeScriptClose(userJs)}\n<\/script>`;
|
||||
}
|
||||
|
||||
let parts = '';
|
||||
for (const url of runtimeUrls) {
|
||||
parts += `<script src="${url}"><\/script>\n`;
|
||||
}
|
||||
parts += `<script>\n${escapeScriptClose(userJs)}\n<\/script>`;
|
||||
return parts;
|
||||
}
|
||||
|
||||
export function renderPreview(html, css, js, mode = 'html-css-js', extraCss = '') {
|
||||
const frame = document.getElementById('preview-frame');
|
||||
const runtime = getFrameworkRuntime(mode);
|
||||
|
||||
// Combine CSS
|
||||
const allCss = extraCss ? `${css}\n${extraCss}` : css;
|
||||
|
||||
// Determine body content
|
||||
let bodyContent;
|
||||
if (mode === 'vue' || mode === 'svelte') {
|
||||
bodyContent = html ? `${html}\n${runtime.bodyHtml}` : runtime.bodyHtml;
|
||||
} else if (runtime.bodyHtml) {
|
||||
bodyContent = `${html}\n${runtime.bodyHtml}`;
|
||||
} else {
|
||||
bodyContent = html;
|
||||
}
|
||||
|
||||
const isModule = mode === 'svelte';
|
||||
const loaderScript = js
|
||||
? buildLoaderScript(runtime.scripts, js, isModule)
|
||||
: '';
|
||||
|
||||
const doc = `<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
${consoleInterceptor}
|
||||
<style>${allCss}</style>
|
||||
</head>
|
||||
<body>
|
||||
${bodyContent}
|
||||
${loaderScript}
|
||||
</body>
|
||||
</html>`;
|
||||
frame.srcdoc = doc;
|
||||
}
|
||||
Reference in New Issue
Block a user