From 710298ca0e90fbb054b1cbcddfa419b4cbee3105 Mon Sep 17 00:00:00 2001 From: Aaqid Masoodi Date: Wed, 17 Dec 2025 00:55:25 +0000 Subject: [PATCH] fix(web-runner): robust preview refresh and asset loading isolation --- src/runners/web/WebRunner.tsx | 114 ++++++++++++++++++---------------- 1 file changed, 62 insertions(+), 52 deletions(-) diff --git a/src/runners/web/WebRunner.tsx b/src/runners/web/WebRunner.tsx index aa42a5c2..6a7d293c 100644 --- a/src/runners/web/WebRunner.tsx +++ b/src/runners/web/WebRunner.tsx @@ -18,48 +18,9 @@ export const WebRunner = memo( forwardRef(({ files, onCollapse }, ref) => { const iframeRef = useRef(null) - useImperativeHandle(ref, () => ({ - captureThumbnail: async () => { - const iframe = iframeRef.current - if (!iframe || !iframe.contentDocument || !iframe.contentDocument.body) return null - - try { - // OPTIMIZATION: If the scape has a (WebGL/Three.js/p5.js), - // capture it directly! It's faster and avoids CSS parsing issues. - const canvasEl = iframe.contentDocument.querySelector("canvas") - if (canvasEl) { - return canvasEl.toDataURL("image/jpeg", 0.7) - } - - // Fallback: Use html2canvas for HTML/CSS scapes - const canvas = await html2canvas(iframe.contentDocument.body, { - useCORS: true, - logging: false, - ignoreElements: (element) => element.tagName === "SCRIPT" || element.tagName === "LINK", // Avoid parsing external resources if possible - }) - return canvas.toDataURL("image/jpeg", 0.7) - } catch { - return null - } - }, - restart: async () => { - if (iframeRef.current) { - // Force reload by resetting srcdoc - // (Changing key might be cleaner, but simple re-assignment often works if content changed) - // Actually, we rely on the srcDoc dependency array in the hook below. - // But if we want to force a "reload" animation or state reset: - const currentSrcDoc = iframeRef.current.srcdoc - iframeRef.current.srcdoc = "" - setTimeout(() => { - if (iframeRef.current) iframeRef.current.srcdoc = currentSrcDoc - }, 10) - } - }, - installPackage: async () => ({ success: false, error: "Not supported in Web environment" }), - })) - - // Navigation State const [activePreviewPath, setActivePreviewPath] = useState("index.html") + const [refreshKey, setRefreshKey] = useState(0) + const [assetsReady, setAssetsReady] = useState(false) // Listen for navigation requests from the iframe useEffect(() => { @@ -94,6 +55,8 @@ export const WebRunner = memo( useEffect(() => { let isMounted = true + // eslint-disable-next-line + setAssetsReady(false) const generateUrls = async () => { const rawAssets: Record = {} @@ -175,6 +138,7 @@ export const WebRunner = memo( if (isMounted) { setAssetUrls({ ...rawAssets, ...processedAssets }) + setAssetsReady(true) } } @@ -186,6 +150,14 @@ export const WebRunner = memo( // 2. Construct the HTML const srcDoc = useMemo(() => { + // Don't build srcDoc until assets are ready to avoid 404s/flashes + if ( + !assetsReady && + files.some((f) => f.name.match(/\.(png|jpg|jpeg|gif|wasm|json|css|js)$/)) + ) { + return "" + } + const htmlFile = files.find((f) => f.name === activePreviewPath) if (!htmlFile) { return `
File '${activePreviewPath}' not found
` @@ -331,7 +303,38 @@ export const WebRunner = memo( }) return processedHtml - }, [files, assetUrls, activePreviewPath]) + }, [files, assetUrls, activePreviewPath, assetsReady]) + + useImperativeHandle(ref, () => ({ + captureThumbnail: async () => { + const iframe = iframeRef.current + if (!iframe || !iframe.contentDocument || !iframe.contentDocument.body) return null + + try { + // OPTIMIZATION: If the scape has a (WebGL/Three.js/p5.js), + // capture it directly! It's faster and avoids CSS parsing issues. + const canvasEl = iframe.contentDocument.querySelector("canvas") + if (canvasEl) { + return canvasEl.toDataURL("image/jpeg", 0.7) + } + + // Fallback: Use html2canvas for HTML/CSS scapes + const canvas = await html2canvas(iframe.contentDocument.body, { + useCORS: true, + logging: false, + ignoreElements: (element) => element.tagName === "SCRIPT" || element.tagName === "LINK", // Avoid parsing external resources if possible + }) + return canvas.toDataURL("image/jpeg", 0.7) + } catch { + return null + } + }, + restart: async () => { + // Force full re-mount of iframe + setRefreshKey((prev) => prev + 1) + }, + installPackage: async () => ({ success: false, error: "Not supported in Web environment" }), + })) return (
@@ -355,16 +358,23 @@ export const WebRunner = memo( )}
-
-