From 3286ee7ae041ee213d0cd47da82dd7316dec75d4 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Mon, 6 Apr 2026 21:17:22 +0000 Subject: [PATCH 1/2] Add runtime fetch resolver for WASM assets Co-authored-by: Siraj Chokshi --- src/utils/fetch.ts | 2 +- src/utils/lang/html.ts | 52 +++++++++++++++++++++++++++++++++++++++++- tests/html.test.ts | 50 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 102 insertions(+), 2 deletions(-) create mode 100644 tests/html.test.ts diff --git a/src/utils/fetch.ts b/src/utils/fetch.ts index ec470b6..31da7e3 100644 --- a/src/utils/fetch.ts +++ b/src/utils/fetch.ts @@ -2,7 +2,7 @@ import { PreviewError, errorType } from './errors' export async function proxyFetch(url: string) { const res = await fetch( - `https://api.codetabs.com/v1/proxy/?quest=${url}`, + `https://api.codetabs.com/v1/proxy/?quest=${encodeURIComponent(url)}`, undefined, ) diff --git a/src/utils/lang/html.ts b/src/utils/lang/html.ts index 8a897b2..1fe3d40 100644 --- a/src/utils/lang/html.ts +++ b/src/utils/lang/html.ts @@ -4,6 +4,53 @@ export function isHTML(maybeHTML: string): boolean { return [...$div.childNodes].reverse().some(($child) => $child.nodeType === 1) } +function getFetchResolverScript() { + return `` +} + function getTitle(html: string) { // TODO: Regex might reasonably be faster here const $div = document.createElement('div') @@ -24,7 +71,10 @@ export interface HTMLPageData { export function processHTML(html: string, url: string): HTMLPageData { const processedHTML = html // Add base tag to iframe - .replace(/]*)>/i, ``) + .replace( + /]*)>/i, + `${getFetchResolverScript()}`, + ) // Replace absolute paths with relative paths .replace(/((src|href|content)=")\/(.*?")/gm, '$1$3') diff --git a/tests/html.test.ts b/tests/html.test.ts new file mode 100644 index 0000000..53cb387 --- /dev/null +++ b/tests/html.test.ts @@ -0,0 +1,50 @@ +import { processHTML } from '../src/utils/lang/html' + +const HTML_URL = + 'https://gitlab.com/sirajchokshi/static-preview-test/-/raw/main/wasm.html' + +describe('[HTML] processHTML', () => { + it('injects base url and runtime fetch resolver', () => { + const html = ` + + + + WASM | Static Preview Test + + + + + + ` + + const { processedHTML, title } = processHTML(html, HTML_URL) + + expect(processedHTML).toContain(``) + expect(processedHTML).toContain('data-static-preview-fetch-resolver') + expect(processedHTML).toContain('window.fetch = (input, init) =>') + expect(title).toEqual('WASM | Static Preview Test') + }) + + it('rewrites leading slash paths to document-relative paths', () => { + const html = ` + + + + + + + + Home + + + + ` + + const { processedHTML } = processHTML(html, HTML_URL) + + expect(processedHTML).toContain('href="assets/main.css"') + expect(processedHTML).toContain('content="assets/preview.png"') + expect(processedHTML).toContain('href="index.html"') + expect(processedHTML).toContain('src="assets/wasm.js"') + }) +}) From c6ab3520a0fbc899a0db514815ad49a7e80a2b3c Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Mon, 6 Apr 2026 21:41:34 +0000 Subject: [PATCH 2/2] Fix repo script loading for WASM previews Co-authored-by: Siraj Chokshi --- src/utils/logger.ts | 61 ++++++++++++++++++++++++++++++++++---------- src/utils/preview.ts | 53 ++++++++++++++++++++++++++++++-------- 2 files changed, 89 insertions(+), 25 deletions(-) diff --git a/src/utils/logger.ts b/src/utils/logger.ts index 167b982..bfa5a57 100644 --- a/src/utils/logger.ts +++ b/src/utils/logger.ts @@ -1,27 +1,60 @@ import logbench from 'logbench' -const logger = logbench({ - logFn: (message) => - console.log( - `%c${message}`, - ` +type Logger = { + log: (message: unknown) => void + warn: (message: unknown) => void + error: (message: unknown) => void +} + +type LogbenchFactory = (options: { + logFn: (message: unknown) => void + warnFn: (message: unknown) => void + isProduction: boolean +}) => Logger + +const createLogger = (factory: unknown): Logger => { + if (typeof factory === 'function') { + return (factory as LogbenchFactory)({ + logFn: (message: unknown) => + console.log( + `%c${message}`, + ` font-size: 12px; background: black; color: white; padding: 5px; `, - ), - warnFn: (message) => - console.log( - `%c${message}`, - ` + ), + warnFn: (message: unknown) => + console.log( + `%c${message}`, + ` font-size: 12px; background: #200e00; color: #feeada; padding: 5px; `, - ), - isProduction: process.env.NODE_ENV === 'production', -}) + ), + isProduction: process.env.NODE_ENV === 'production', + }) + } + + // Fallback for environments where the default export shape differs. + return { + log: (message) => console.log(message), + warn: (message) => console.warn(message), + error: (message) => console.error(message), + } +} + +const logger = createLogger(logbench) + +// Keep existing styling behavior for compatible environments. +const styledLogger = createLogger((logbench as { default?: unknown }).default) + +const resolvedLogger = + typeof (logbench as { default?: unknown }).default === 'function' + ? styledLogger + : logger -export default logger +export default resolvedLogger diff --git a/src/utils/preview.ts b/src/utils/preview.ts index aaf07f7..29360cf 100644 --- a/src/utils/preview.ts +++ b/src/utils/preview.ts @@ -141,22 +141,53 @@ export class Preview { // Load page JS const $script = - this.iframeDocument.document.querySelectorAll( - 'script[type="text/javascript"]', - ) + this.iframeDocument.document.querySelectorAll('script') + const baseUrl = new URL(this.iframeDocument.document.baseURI) + + const scripts = [...$script].map(async (s) => { + if (s.hasAttribute('data-static-preview-fetch-resolver')) { + // Ignore injected resolver script. + return null + } + + const type = s.type.toLowerCase() + const isClassicType = + type === '' || + type === 'text/javascript' || + type === 'application/javascript' + + if (!isClassicType) { + return null + } + + if (s.src) { + const srcUrl = new URL(s.src, this.iframeDocument.document.baseURI) - const scripts = [...$script].map((s) => { - const { src } = s - if (src.includes('//raw.githubusercontent.com')) { - // TODO: Check if it's from raw.github.com or bitbucket.org - return this.load(src) // Then add it to scripts queue and fetch using CORS proxy + if ( + srcUrl.origin === baseUrl.origin || + srcUrl.hostname === 'raw.githubusercontent.com' + ) { + return this.load(srcUrl.href) + } + + // Let browser handle non-repository script URLs. + return null + } + + // Existing support for explicitly text/javascript inline scripts. + if (type === 'text/javascript') { + s.removeAttribute('type') } - s.removeAttribute('type') - return s.innerHTML // Add inline script to queue to eval in order + + return s.innerHTML }) await Promise.all(scripts).then((res) => { - res.forEach((r) => this.appendToHead(r, 'script')) + res.forEach((r) => { + if (r) { + this.appendToHead(r, 'script') + } + }) this.iframeDocument.document.dispatchEvent( new Event('DOMContentLoaded', { bubbles: true, cancelable: true }),