Skip to content

Commit d4eaf6c

Browse files
committed
fix(runtime): stabilize killer demos and gate preact preserve to browser
1 parent 2f5c184 commit d4eaf6c

5 files changed

Lines changed: 159 additions & 40 deletions

File tree

examples/killer/one-line-chat-dashboard.html

Lines changed: 37 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,12 @@ <h1 class="headline">One-Line Embed: AI Chat Dashboard</h1>
124124
{
125125
"imports": {
126126
"@renderify/ir": "../../packages/ir/dist/ir.esm.js",
127-
"@renderify/security": "../../packages/security/dist/security.esm.js"
127+
"@renderify/security": "../../packages/security/dist/security.esm.js",
128+
"es-module-lexer": "https://ga.jspm.io/npm:es-module-lexer@1.7.0/dist/lexer.js",
129+
"preact": "https://ga.jspm.io/npm:preact@10.28.3/dist/preact.module.js",
130+
"preact/hooks": "https://ga.jspm.io/npm:preact@10.28.3/hooks/dist/hooks.module.js",
131+
"preact/jsx-runtime": "https://ga.jspm.io/npm:preact@10.28.3/jsx-runtime/dist/jsxRuntime.module.js",
132+
"preact-render-to-string": "https://ga.jspm.io/npm:preact-render-to-string@6.6.5/dist/index.module.js"
128133
}
129134
}
130135
</script>
@@ -138,10 +143,10 @@ <h1 class="headline">One-Line Embed: AI Chat Dashboard</h1>
138143
root: { type: "text", value: "loading" },
139144
capabilities: {
140145
domWrite: true,
141-
allowedModules: ["preact", "preact/hooks", "recharts"],
146+
allowedModules: ["preact", "preact/hooks"],
142147
maxExecutionMs: 5000,
143148
},
144-
imports: ["preact", "preact/hooks", "recharts"],
149+
imports: ["preact", "preact/hooks"],
145150
moduleManifest: {
146151
preact: {
147152
resolvedUrl: "https://ga.jspm.io/npm:preact@10.28.3/dist/preact.module.js",
@@ -155,30 +160,6 @@ <h1 class="headline">One-Line Embed: AI Chat Dashboard</h1>
155160
resolvedUrl: "https://ga.jspm.io/npm:preact@10.28.3/jsx-runtime/dist/jsxRuntime.module.js",
156161
signer: "examples",
157162
},
158-
recharts: {
159-
resolvedUrl: "https://ga.jspm.io/npm:recharts@3.3.0/es6/index.js",
160-
signer: "examples",
161-
},
162-
react: {
163-
resolvedUrl: "https://ga.jspm.io/npm:preact@10.28.3/compat/dist/compat.module.js",
164-
signer: "examples",
165-
},
166-
"react-dom": {
167-
resolvedUrl: "https://ga.jspm.io/npm:preact@10.28.3/compat/dist/compat.module.js",
168-
signer: "examples",
169-
},
170-
"react-dom/client": {
171-
resolvedUrl: "https://ga.jspm.io/npm:preact@10.28.3/compat/dist/compat.module.js",
172-
signer: "examples",
173-
},
174-
"react/jsx-runtime": {
175-
resolvedUrl: "https://ga.jspm.io/npm:preact@10.28.3/jsx-runtime/dist/jsxRuntime.module.js",
176-
signer: "examples",
177-
},
178-
"react/jsx-dev-runtime": {
179-
resolvedUrl: "https://ga.jspm.io/npm:preact@10.28.3/jsx-runtime/dist/jsxRuntime.module.js",
180-
signer: "examples",
181-
},
182163
},
183164
source: {
184165
language: "tsx",
@@ -187,7 +168,6 @@ <h1 class="headline">One-Line Embed: AI Chat Dashboard</h1>
187168
code: [
188169
"import { h } from 'preact';",
189170
"import { useMemo, useState } from 'preact/hooks';",
190-
"import { ResponsiveContainer, LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip } from 'recharts';",
191171
"",
192172
"export default function Dashboard() {",
193173
" const [metric, setMetric] = useState('revenue');",
@@ -198,6 +178,17 @@ <h1 class="headline">One-Line Embed: AI Chat Dashboard</h1>
198178
" { day: 'Thu', revenue: 222, users: 74 },",
199179
" { day: 'Fri', revenue: 261, users: 91 },",
200180
" ], []);",
181+
" const key = metric === 'revenue' ? 'revenue' : 'users';",
182+
" const width = 560;",
183+
" const height = 280;",
184+
" const pad = 28;",
185+
" const max = Math.max(...data.map((entry) => entry[key]));",
186+
" const points = data.map((entry, index) => {",
187+
" const x = pad + (index * (width - pad * 2)) / Math.max(1, data.length - 1);",
188+
" const y = height - pad - (entry[key] / Math.max(1, max)) * (height - pad * 2);",
189+
" return { x, y, day: entry.day, value: entry[key] };",
190+
" });",
191+
" const polyline = points.map((point) => `${point.x},${point.y}`).join(' ');",
201192
" return (",
202193
" <section style={{ fontFamily: 'IBM Plex Sans, sans-serif' }}>",
203194
" <h2 style={{ margin: '0 0 6px' }}>Revenue Assistant Dashboard</h2>",
@@ -206,16 +197,24 @@ <h1 class="headline">One-Line Embed: AI Chat Dashboard</h1>
206197
" <button type='button' onClick={() => setMetric('revenue')}>Revenue</button>",
207198
" <button type='button' onClick={() => setMetric('users')}>Users</button>",
208199
" </div>",
209-
" <div style={{ width: '100%', height: 280 }}>",
210-
" <ResponsiveContainer>",
211-
" <LineChart data={data}>",
212-
" <CartesianGrid strokeDasharray='3 3' />",
213-
" <XAxis dataKey='day' />",
214-
" <YAxis />",
215-
" <Tooltip />",
216-
" <Line type='monotone' dataKey={metric} stroke={metric === 'revenue' ? '#0f766e' : '#1d4ed8'} strokeWidth={3} />",
217-
" </LineChart>",
218-
" </ResponsiveContainer>",
200+
" <div style={{ overflowX: 'auto' }}>",
201+
" <svg width={width} height={height} viewBox={`0 0 ${width} ${height}`} role='img' aria-label='Metric chart'>",
202+
" <line x1={pad} y1={height - pad} x2={width - pad} y2={height - pad} stroke='#d1d8e6' />",
203+
" <line x1={pad} y1={pad} x2={pad} y2={height - pad} stroke='#d1d8e6' />",
204+
" <polyline",
205+
" fill='none'",
206+
" stroke={metric === 'revenue' ? '#0f766e' : '#1d4ed8'}",
207+
" strokeWidth={3}",
208+
" points={polyline}",
209+
" />",
210+
" {points.map((point) => (",
211+
" <g key={point.day}>",
212+
" <circle cx={point.x} cy={point.y} r={4} fill={metric === 'revenue' ? '#0f766e' : '#1d4ed8'} />",
213+
" <text x={point.x} y={height - 8} textAnchor='middle' fontSize='11' fill='#5c6f88'>{point.day}</text>",
214+
" <text x={point.x} y={point.y - 10} textAnchor='middle' fontSize='11' fill='#2a3f58'>{point.value}</text>",
215+
" </g>",
216+
" ))}",
217+
" </svg>",
219218
" </div>",
220219
" <p style={{ marginBottom: 0 }}>Current metric: <strong>{metric}</strong></p>",
221220
" </section>",

examples/killer/one-line-chat-form.html

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,12 @@ <h1>One-Line Embed: Form + State + Any Package</h1>
6464
{
6565
"imports": {
6666
"@renderify/ir": "../../packages/ir/dist/ir.esm.js",
67-
"@renderify/security": "../../packages/security/dist/security.esm.js"
67+
"@renderify/security": "../../packages/security/dist/security.esm.js",
68+
"es-module-lexer": "https://ga.jspm.io/npm:es-module-lexer@1.7.0/dist/lexer.js",
69+
"preact": "https://ga.jspm.io/npm:preact@10.28.3/dist/preact.module.js",
70+
"preact/hooks": "https://ga.jspm.io/npm:preact@10.28.3/hooks/dist/hooks.module.js",
71+
"preact/jsx-runtime": "https://ga.jspm.io/npm:preact@10.28.3/jsx-runtime/dist/jsxRuntime.module.js",
72+
"preact-render-to-string": "https://ga.jspm.io/npm:preact-render-to-string@6.6.5/dist/index.module.js"
6873
}
6974
}
7075
</script>

examples/killer/one-line-sandbox-worker.html

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,12 @@ <h1>One-Line Embed: Worker-Sandboxed Source</h1>
6767
{
6868
"imports": {
6969
"@renderify/ir": "../../packages/ir/dist/ir.esm.js",
70-
"@renderify/security": "../../packages/security/dist/security.esm.js"
70+
"@renderify/security": "../../packages/security/dist/security.esm.js",
71+
"es-module-lexer": "https://ga.jspm.io/npm:es-module-lexer@1.7.0/dist/lexer.js",
72+
"preact": "https://ga.jspm.io/npm:preact@10.28.3/dist/preact.module.js",
73+
"preact/hooks": "https://ga.jspm.io/npm:preact@10.28.3/hooks/dist/hooks.module.js",
74+
"preact/jsx-runtime": "https://ga.jspm.io/npm:preact@10.28.3/jsx-runtime/dist/jsxRuntime.module.js",
75+
"preact-render-to-string": "https://ga.jspm.io/npm:preact-render-to-string@6.6.5/dist/index.module.js"
7176
}
7277
}
7378
</script>

packages/runtime/src/runtime-source-module-loader.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import {
1313
isJsonModuleResponse,
1414
type RemoteModuleFetchResult,
1515
} from "./module-fetch";
16+
import { isBrowserRuntime } from "./runtime-environment";
1617
import { isHttpUrl } from "./runtime-specifier";
1718

1819
export interface RuntimeSourceModuleLoaderOptions {
@@ -173,6 +174,10 @@ export class RuntimeSourceModuleLoader {
173174
return normalizedUrl;
174175
}
175176

177+
if (isBrowserRuntime() && this.shouldPreserveRemoteImport(normalizedUrl)) {
178+
return normalizedUrl;
179+
}
180+
176181
const cachedUrl = this.materializedModuleUrlCache.get(normalizedUrl);
177182
if (cachedUrl) {
178183
return cachedUrl;
@@ -372,4 +377,19 @@ export class RuntimeSourceModuleLoader {
372377

373378
return String(error);
374379
}
380+
381+
private shouldPreserveRemoteImport(url: string): boolean {
382+
let parsed: URL;
383+
try {
384+
parsed = new URL(url);
385+
} catch {
386+
return false;
387+
}
388+
389+
const path = parsed.pathname.toLowerCase();
390+
return (
391+
path.includes("/npm:preact@") ||
392+
path.includes("/npm:preact-render-to-string@")
393+
);
394+
}
375395
}

tests/runtime.test.ts

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1823,6 +1823,96 @@ test("runtime source loader supports disabling fallback cdn attempts", async ()
18231823
}
18241824
});
18251825

1826+
test("runtime source loader preserves preact remote imports in browser runtime", async () => {
1827+
class BrowserWorkerMock {}
1828+
const restoreGlobals = installBrowserSandboxGlobals(BrowserWorkerMock);
1829+
const runtime = new DefaultRuntimeManager({
1830+
remoteFallbackCdnBases: [],
1831+
remoteFetchRetries: 0,
1832+
remoteFetchBackoffMs: 10,
1833+
remoteFetchTimeoutMs: 500,
1834+
});
1835+
1836+
const internals = runtime as unknown as {
1837+
createSourceModuleLoader: (
1838+
moduleManifest: RuntimeModuleManifest | undefined,
1839+
diagnostics: Array<{ code?: string; message?: string }>,
1840+
) => {
1841+
materializeRemoteModule(url: string): Promise<string>;
1842+
};
1843+
};
1844+
1845+
const preactUrl =
1846+
"https://ga.jspm.io/npm:preact@10.28.3/hooks/dist/hooks.module.js";
1847+
const diagnostics: Array<{ code?: string; message?: string }> = [];
1848+
const loader = internals.createSourceModuleLoader(undefined, diagnostics);
1849+
1850+
let fetchCount = 0;
1851+
const originalFetch = globalThis.fetch;
1852+
globalThis.fetch = (async (_input: RequestInfo | URL) => {
1853+
fetchCount += 1;
1854+
return new Response("export default 1;", {
1855+
status: 200,
1856+
headers: {
1857+
"content-type": "text/javascript; charset=utf-8",
1858+
},
1859+
});
1860+
}) as typeof fetch;
1861+
1862+
try {
1863+
const resolved = await loader.materializeRemoteModule(preactUrl);
1864+
assert.equal(resolved, preactUrl);
1865+
assert.equal(fetchCount, 0);
1866+
} finally {
1867+
globalThis.fetch = originalFetch;
1868+
restoreGlobals();
1869+
}
1870+
});
1871+
1872+
test("runtime source loader materializes preact remote imports outside browser runtime", async () => {
1873+
const runtime = new DefaultRuntimeManager({
1874+
remoteFallbackCdnBases: [],
1875+
remoteFetchRetries: 0,
1876+
remoteFetchBackoffMs: 10,
1877+
remoteFetchTimeoutMs: 500,
1878+
});
1879+
1880+
const internals = runtime as unknown as {
1881+
createSourceModuleLoader: (
1882+
moduleManifest: RuntimeModuleManifest | undefined,
1883+
diagnostics: Array<{ code?: string; message?: string }>,
1884+
) => {
1885+
materializeRemoteModule(url: string): Promise<string>;
1886+
};
1887+
};
1888+
1889+
const preactUrl =
1890+
"https://ga.jspm.io/npm:preact@10.28.3/hooks/dist/hooks.module.js";
1891+
const diagnostics: Array<{ code?: string; message?: string }> = [];
1892+
const loader = internals.createSourceModuleLoader(undefined, diagnostics);
1893+
1894+
let fetchCount = 0;
1895+
const originalFetch = globalThis.fetch;
1896+
globalThis.fetch = (async (_input: RequestInfo | URL) => {
1897+
fetchCount += 1;
1898+
return new Response("export default 1;", {
1899+
status: 200,
1900+
headers: {
1901+
"content-type": "text/javascript; charset=utf-8",
1902+
},
1903+
});
1904+
}) as typeof fetch;
1905+
1906+
try {
1907+
const resolved = await loader.materializeRemoteModule(preactUrl);
1908+
assert.notEqual(resolved, preactUrl);
1909+
assert.match(resolved, /^data:text\/javascript;base64,/);
1910+
assert.ok(fetchCount >= 1);
1911+
} finally {
1912+
globalThis.fetch = originalFetch;
1913+
}
1914+
});
1915+
18261916
test("runtime module caches are released across lifecycle cycles", async () => {
18271917
const runtime = new DefaultRuntimeManager({
18281918
remoteFallbackCdnBases: [],

0 commit comments

Comments
 (0)