Skip to content

Commit 6bd615e

Browse files
committed
fix(runtime): prioritize explicit manifest mappings over local preact
1 parent c1dbbde commit 6bd615e

3 files changed

Lines changed: 89 additions & 18 deletions

File tree

packages/cli/src/playground-html.ts

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -239,7 +239,6 @@ export const PLAYGROUND_HTML = `<!doctype html>
239239
const DEFAULT_PREACT_VERSION = "10.28.3";
240240
const ESM_SH_BASE_URL = "https://esm.sh/";
241241
const BABEL_STANDALONE_URL = "https://unpkg.com/@babel/standalone/babel.min.js";
242-
const JSSPM_HOSTNAMES = new Set(["ga.jspm.io", "cdn.jspm.io"]);
243242
244243
let interactiveMountVersion = 0;
245244
let interactiveBlobModuleUrl = null;
@@ -337,15 +336,6 @@ export const PLAYGROUND_HTML = `<!doctype html>
337336
return true;
338337
};
339338
340-
const isJspmResolvedUrl = (url) => {
341-
try {
342-
const parsed = new URL(String(url));
343-
return JSSPM_HOSTNAMES.has(parsed.hostname.toLowerCase());
344-
} catch {
345-
return false;
346-
}
347-
};
348-
349339
const getManifestResolvedUrl = (planDetail, specifier) => {
350340
if (!isRecord(planDetail) || !isRecord(planDetail.moduleManifest)) {
351341
return undefined;
@@ -392,7 +382,7 @@ export const PLAYGROUND_HTML = `<!doctype html>
392382
}
393383
394384
const manifestResolvedUrl = getManifestResolvedUrl(planDetail, normalized);
395-
if (manifestResolvedUrl && !isJspmResolvedUrl(manifestResolvedUrl)) {
385+
if (manifestResolvedUrl) {
396386
return manifestResolvedUrl;
397387
}
398388

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

Lines changed: 27 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -134,15 +134,20 @@ export class RuntimeSourceModuleLoader {
134134
return trimmed;
135135
}
136136

137-
const localPreact = await this.resolveLocalPreactSpecifier(trimmed);
138-
if (localPreact) {
139-
return localPreact;
140-
}
137+
const explicitManifestResolved =
138+
this.resolveExplicitManifestSpecifier(trimmed);
141139

142140
if (trimmed.startsWith("data:") || trimmed.startsWith("blob:")) {
143141
return trimmed;
144142
}
145143

144+
if (!explicitManifestResolved) {
145+
const localPreact = await this.resolveLocalPreactSpecifier(trimmed);
146+
if (localPreact) {
147+
return localPreact;
148+
}
149+
}
150+
146151
if (isHttpUrl(trimmed)) {
147152
return this.materializeRemoteModule(trimmed);
148153
}
@@ -180,9 +185,12 @@ export class RuntimeSourceModuleLoader {
180185
this.diagnostics,
181186
false,
182187
);
183-
const localFromResolved = await this.resolveLocalPreactSpecifier(resolved);
184-
if (localFromResolved) {
185-
return localFromResolved;
188+
if (!explicitManifestResolved) {
189+
const localFromResolved =
190+
await this.resolveLocalPreactSpecifier(resolved);
191+
if (localFromResolved) {
192+
return localFromResolved;
193+
}
186194
}
187195

188196
if (isHttpUrl(resolved)) {
@@ -450,6 +458,18 @@ export class RuntimeSourceModuleLoader {
450458
);
451459
}
452460

461+
private resolveExplicitManifestSpecifier(
462+
specifier: string,
463+
): string | undefined {
464+
const descriptor = this.moduleManifest?.[specifier];
465+
if (!descriptor || typeof descriptor.resolvedUrl !== "string") {
466+
return undefined;
467+
}
468+
469+
const resolved = descriptor.resolvedUrl.trim();
470+
return resolved.length > 0 ? resolved : undefined;
471+
}
472+
453473
private async resolveLocalPreactSpecifier(
454474
specifier: string,
455475
): Promise<string | undefined> {

tests/runtime.test.ts

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2201,6 +2201,67 @@ test("runtime source loader maps remote preact imports to local node file URLs",
22012201
}
22022202
});
22032203

2204+
test("runtime source loader honors explicit manifest mappings before local preact shortcuts", async () => {
2205+
const runtime = new DefaultRuntimeManager({
2206+
remoteFallbackCdnBases: [],
2207+
remoteFetchRetries: 0,
2208+
remoteFetchBackoffMs: 10,
2209+
remoteFetchTimeoutMs: 500,
2210+
});
2211+
2212+
const internals = runtime as unknown as {
2213+
createSourceModuleLoader: (
2214+
moduleManifest: RuntimeModuleManifest | undefined,
2215+
diagnostics: Array<{ code?: string; message?: string }>,
2216+
) => {
2217+
resolveRuntimeImportSpecifier(
2218+
specifier: string,
2219+
parentUrl: string | undefined,
2220+
): Promise<string>;
2221+
};
2222+
};
2223+
2224+
const diagnostics: Array<{ code?: string; message?: string }> = [];
2225+
const loader = internals.createSourceModuleLoader(
2226+
{
2227+
react: {
2228+
resolvedUrl:
2229+
"https://ga.jspm.io/npm:preact@10.28.3/compat/dist/compat.module.js",
2230+
},
2231+
},
2232+
diagnostics,
2233+
);
2234+
2235+
const requestedUrls: string[] = [];
2236+
const originalFetch = globalThis.fetch;
2237+
globalThis.fetch = (async (input: RequestInfo | URL) => {
2238+
requestedUrls.push(String(input));
2239+
return new Response("export default function Compat() { return null; }", {
2240+
status: 200,
2241+
headers: {
2242+
"content-type": "text/javascript; charset=utf-8",
2243+
},
2244+
});
2245+
}) as typeof fetch;
2246+
2247+
try {
2248+
const resolved = await loader.resolveRuntimeImportSpecifier(
2249+
"react",
2250+
undefined,
2251+
);
2252+
assert.match(resolved, /^data:text\/javascript;base64,/);
2253+
assert.equal(
2254+
requestedUrls.some((url) =>
2255+
url.includes("/npm:preact@10.28.3/compat/dist/compat.module.js"),
2256+
),
2257+
true,
2258+
);
2259+
assert.doesNotMatch(resolved, /^file:\/\//);
2260+
} finally {
2261+
globalThis.fetch = originalFetch;
2262+
}
2263+
});
2264+
22042265
test("runtime module caches are released across lifecycle cycles", async () => {
22052266
const runtime = new DefaultRuntimeManager({
22062267
remoteFallbackCdnBases: [],

0 commit comments

Comments
 (0)