@module-federation/vite 1.17.0 → 1.18.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -262,6 +262,7 @@ const sharedCacheHelperCode = `const __mfGetSharedCacheDescriptor = (pkg, single
262
262
  if (value !== undefined) return value;
263
263
  const aliases = descriptor.aliases || [];
264
264
  for (const alias of aliases) {
265
+ if (!Object.prototype.hasOwnProperty.call(cache, alias)) continue;
265
266
  const aliasValue = cache[alias];
266
267
  if (aliasValue !== undefined) {
267
268
  cache[descriptor.canonical] = aliasValue;
@@ -270,11 +271,60 @@ const sharedCacheHelperCode = `const __mfGetSharedCacheDescriptor = (pkg, single
270
271
  }
271
272
  return undefined;
272
273
  };
273
- const __mfWriteSharedCache = (cache, descriptor, value) => {
274
+ const __mfSharedCacheListenersKey = Symbol.for("module-federation.shared-cache-listeners");
275
+ const __mfGetSharedCacheListeners = (cache) => {
276
+ let listeners = cache[__mfSharedCacheListenersKey];
277
+ if (listeners === undefined) {
278
+ listeners = Object.create(null);
279
+ Object.defineProperty(cache, __mfSharedCacheListenersKey, {
280
+ value: listeners,
281
+ enumerable: false,
282
+ configurable: false,
283
+ writable: false
284
+ });
285
+ }
286
+ return listeners;
287
+ };
288
+ const __mfSubscribeSharedCache = (cache, descriptor, listener) => {
289
+ const listeners = __mfGetSharedCacheListeners(cache);
290
+ (listeners[descriptor.canonical] ||= new Set()).add(listener);
291
+ };
292
+ const __mfSharedCacheOwnersKey = Symbol.for("module-federation.shared-cache-owners");
293
+ const __mfGetSharedCacheOwners = (cache) => {
294
+ let owners = cache[__mfSharedCacheOwnersKey];
295
+ if (owners === undefined) {
296
+ owners = Object.create(null);
297
+ Object.defineProperty(cache, __mfSharedCacheOwnersKey, {
298
+ value: owners,
299
+ enumerable: false,
300
+ configurable: false,
301
+ writable: false
302
+ });
303
+ }
304
+ return owners;
305
+ };
306
+ const __mfReadSharedCacheOwner = (cache, descriptor) =>
307
+ cache[__mfSharedCacheOwnersKey]?.[descriptor.canonical];
308
+ const __mfWriteSharedCache = (cache, descriptor, value, owner) => {
274
309
  cache[descriptor.canonical] = value;
275
310
  const aliases = descriptor.aliases || [];
276
311
  for (const alias of aliases) {
277
- if (cache[alias] === undefined) cache[alias] = value;
312
+ Object.defineProperty(cache, alias, {
313
+ value,
314
+ enumerable: true,
315
+ configurable: true,
316
+ writable: true
317
+ });
318
+ }
319
+ const owners = cache[__mfSharedCacheOwnersKey];
320
+ if (owner === undefined) {
321
+ if (owners) delete owners[descriptor.canonical];
322
+ } else {
323
+ __mfGetSharedCacheOwners(cache)[descriptor.canonical] = owner;
324
+ }
325
+ const listeners = cache[__mfSharedCacheListenersKey]?.[descriptor.canonical];
326
+ if (listeners) {
327
+ for (const listener of listeners) listener(value);
278
328
  }
279
329
  return value;
280
330
  };
@@ -0,0 +1,592 @@
1
+ //#region src/utils/fetchWithTimeout.ts
2
+ const DEFAULT_SSR_FETCH_TIMEOUT_MS = 1e4;
3
+ function getFetchUrl(input) {
4
+ const raw = typeof input === "string" || input instanceof URL ? String(input) : input.url;
5
+ return new URL(raw);
6
+ }
7
+ function getSecureFetchUrl(input) {
8
+ const url = getFetchUrl(input);
9
+ const isLoopback = url.hostname === "localhost" || url.hostname === "127.0.0.1" || url.hostname === "[::1]";
10
+ if (url.protocol !== "https:" && !(url.protocol === "http:" && isLoopback)) throw new TypeError(`Refusing to fetch SSR resource over an insecure connection: ${url}`);
11
+ return url;
12
+ }
13
+ /** Fetch with a bounded wait. Set timeoutMs to 0 to disable the timeout. */
14
+ async function fetchWithTimeout(input, init = {}, timeoutMs = DEFAULT_SSR_FETCH_TIMEOUT_MS) {
15
+ const inputUrl = getSecureFetchUrl(input);
16
+ const request = (target) => {
17
+ const requestInit = {
18
+ ...init,
19
+ redirect: "error"
20
+ };
21
+ if (!Number.isFinite(timeoutMs) || timeoutMs <= 0) return fetch(target.href, requestInit);
22
+ const timeoutSignal = AbortSignal.timeout(timeoutMs);
23
+ const signal = init.signal ? AbortSignal.any([init.signal, timeoutSignal]) : timeoutSignal;
24
+ return fetch(target.href, {
25
+ ...requestInit,
26
+ signal
27
+ });
28
+ };
29
+ try {
30
+ return await request(inputUrl);
31
+ } catch (error) {
32
+ if (inputUrl.hostname !== "localhost") throw error;
33
+ inputUrl.hostname = "[::1]";
34
+ return request(inputUrl);
35
+ }
36
+ }
37
+ //#endregion
38
+ //#region src/utils/ssrEntryLoader.ts
39
+ /**
40
+ * MF runtime plugin that intercepts the `loadEntry` lifecycle hook on the
41
+ * server and loads the SSR-compatible remote entry instead of the browser one.
42
+ *
43
+ * This completely replaces the need for any `@module-federation/sdk` patches.
44
+ * The `loadEntry` hook is emitted by `runtime-core` before it falls through to
45
+ * `loadScriptNode` — if the hook returns a value, the runtime uses it directly.
46
+ *
47
+ * Strategy:
48
+ * - In Node (detected through process.versions.node), fetch the remote's mf-manifest.json
49
+ * to discover the ssrRemoteEntry URL and its type.
50
+ * - ESM entry: use a dynamic `import()` — the SSR entry has no browser
51
+ * globals and all shared packages are external.
52
+ * - Dev mode (Vite 8+ only): use `ModuleRunner` with an HTTP transport backed
53
+ * by the remote's `/__mf_runner__` endpoint. This fetches fully-transformed
54
+ * module source through Vite's plugin pipeline, avoiding serialisation which
55
+ * cannot faithfully represent React components or closures.
56
+ *
57
+ * Dev mode on Vite < 8 is NOT supported — `ModuleRunner` and
58
+ * `FetchableDevEnvironment` are Vite 8+ APIs. If you need dev-mode SSR on
59
+ * an older Vite version, implement an alternative loader in `loadSSRRemoteEntry`
60
+ * for the `isDevSsrEntry` branch and expose a corresponding server endpoint
61
+ * from `pluginSSRRemoteEntry.configureServer`.
62
+ *
63
+ * Exported as a plain factory function so it can be serialised into the
64
+ * generated runtimePlugins list in virtualRemotes.ts.
65
+ */
66
+ const importCache = /* @__PURE__ */ new Map();
67
+ async function nodeImport(id) {
68
+ if (!importCache.has(id)) importCache.set(id, import(
69
+ /* @vite-ignore */
70
+ id
71
+ ));
72
+ return importCache.get(id);
73
+ }
74
+ const isNodeServer = () => typeof globalThis.process?.versions?.node === "string";
75
+ const runnerCache = /* @__PURE__ */ new Map();
76
+ /**
77
+ * Import `vite/module-runner` dynamically. Returns null on Vite < 8 where the
78
+ * subpath doesn't exist. Uses a plain dynamic import (not nodeImport) so that
79
+ * Vitest can intercept it with vi.mock in tests.
80
+ */
81
+ async function getModuleRunnerModule() {
82
+ try {
83
+ return await import("vite/module-runner");
84
+ } catch {
85
+ return null;
86
+ }
87
+ }
88
+ /**
89
+ * Create a ModuleRunner that fetches modules from a remote Vite dev server's
90
+ * `/__mf_runner__` endpoint. Each HTTP POST carries a `fetchModule` invoke
91
+ * payload; the remote responds with the transformed module source as JSON.
92
+ *
93
+ * This is Vite 8+ only — older versions don't expose `vite/module-runner` or
94
+ * the `/__mf_runner__` proxy endpoint.
95
+ */
96
+ async function getOrCreateRunner(remoteOrigin, fetchTimeoutMs) {
97
+ const cacheKey = `${fetchTimeoutMs}::${remoteOrigin}`;
98
+ if (runnerCache.has(cacheKey)) return runnerCache.get(cacheKey);
99
+ const promise = (async () => {
100
+ const viteRunner = await getModuleRunnerModule();
101
+ if (!viteRunner) return null;
102
+ const { ModuleRunner, ESModulesEvaluator } = viteRunner;
103
+ const runnerEndpoint = `${remoteOrigin}/__mf_runner__`;
104
+ try {
105
+ return new ModuleRunner({
106
+ hmr: false,
107
+ transport: { async invoke(payload) {
108
+ return await (await fetchWithTimeout(runnerEndpoint, {
109
+ method: "POST",
110
+ headers: { "Content-Type": "application/json" },
111
+ body: JSON.stringify(payload)
112
+ }, fetchTimeoutMs)).json();
113
+ } }
114
+ }, new ESModulesEvaluator());
115
+ } catch {
116
+ return null;
117
+ }
118
+ })();
119
+ runnerCache.set(cacheKey, promise);
120
+ return promise;
121
+ }
122
+ const _path = () => nodeImport("path");
123
+ const _fs = () => nodeImport("fs");
124
+ const _crypto = () => nodeImport("crypto");
125
+ const _module = () => nodeImport("module");
126
+ /**
127
+ * Version key for a resolved SSR entry. Derived from the remote's manifest
128
+ * content so a redeploy at the same URL produces a different key, which in
129
+ * turn produces different temp-file names — busting both our caches and
130
+ * Node's ESM module cache. Convention-resolved entries (no manifest) get a
131
+ * stable placeholder key and cannot be revalidated automatically.
132
+ */
133
+ const UNVERSIONED = "unversioned";
134
+ function hashString(value) {
135
+ let hash = 2166136261;
136
+ for (let i = 0; i < value.length; i++) {
137
+ hash ^= value.charCodeAt(i);
138
+ hash = Math.imul(hash, 16777619);
139
+ }
140
+ return (hash >>> 0).toString(16).padStart(8, "0");
141
+ }
142
+ function computeManifestVersionKey(manifest) {
143
+ const buildVersion = manifest.metaData?.buildInfo?.buildVersion;
144
+ const contentHash = hashString(JSON.stringify(manifest));
145
+ return buildVersion ? `${buildVersion}-${contentHash}` : contentHash;
146
+ }
147
+ const ssrEntryCache = /* @__PURE__ */ new Map();
148
+ const manifestFetchCache = /* @__PURE__ */ new Map();
149
+ function makeUrlCacheKey(url, fetchTimeoutMs) {
150
+ return `${fetchTimeoutMs}::${url}`;
151
+ }
152
+ var SsrEntryHttpError = class extends Error {
153
+ constructor(url, status, statusText, bodyPreview) {
154
+ super(`Failed to fetch SSR module "${url}": ${status} ${statusText}` + (bodyPreview ? `\npreview: ${bodyPreview}` : ""));
155
+ this.url = url;
156
+ this.status = status;
157
+ this.statusText = statusText;
158
+ this.bodyPreview = bodyPreview;
159
+ this.name = "SsrEntryHttpError";
160
+ }
161
+ };
162
+ function getBodyPreview(body) {
163
+ return body.slice(0, 240).replace(/\s+/g, " ").trim();
164
+ }
165
+ function isSsrEntryHttpError(error) {
166
+ return error instanceof SsrEntryHttpError;
167
+ }
168
+ async function fetchManifest(manifestUrl, fetchTimeoutMs) {
169
+ try {
170
+ const res = await fetchWithTimeout(manifestUrl, {}, fetchTimeoutMs);
171
+ if (!res.ok) return null;
172
+ return await res.json();
173
+ } catch {
174
+ return null;
175
+ }
176
+ }
177
+ async function fetchManifestCached(manifestUrl, fetchTimeoutMs) {
178
+ const cacheKey = makeUrlCacheKey(manifestUrl, fetchTimeoutMs);
179
+ if (!manifestFetchCache.has(cacheKey)) {
180
+ const promise = fetchManifest(manifestUrl, fetchTimeoutMs);
181
+ manifestFetchCache.set(cacheKey, promise);
182
+ promise.then((manifest) => {
183
+ if (!manifest && manifestFetchCache.get(cacheKey) === promise) manifestFetchCache.delete(cacheKey);
184
+ });
185
+ }
186
+ return manifestFetchCache.get(cacheKey);
187
+ }
188
+ /** True when the host configured a manifest URL as the remote entry (any .json name). */
189
+ function isManifestEntry(remoteEntryUrl) {
190
+ try {
191
+ const { pathname } = new URL(remoteEntryUrl);
192
+ return /\.json$/i.test(pathname);
193
+ } catch {
194
+ return /\.json(?:[?#]|$)/i.test(remoteEntryUrl);
195
+ }
196
+ }
197
+ function isSsrEntry(remoteEntryUrl) {
198
+ return /\.ssr\.js(?:[?#].*)?$/.test(remoteEntryUrl);
199
+ }
200
+ function getManifestUrl(remoteEntryUrl) {
201
+ if (isManifestEntry(remoteEntryUrl)) return remoteEntryUrl;
202
+ return remoteEntryUrl.replace(/\/[^/]+$/, "/mf-manifest.json");
203
+ }
204
+ function getEntryFilename(entryUrl) {
205
+ return entryUrl.split("/").pop()?.replace(/[?#].*$/, "").replace(/\.[^.]+$/, "") ?? "remoteEntry";
206
+ }
207
+ function resolveEntryAssetUrl(entry, manifestUrl) {
208
+ const base = manifestUrl.replace(/\/[^/]+$/, "/");
209
+ return new URL(`${entry.path || ""}${entry.name}`, base).href;
210
+ }
211
+ function resolveSSREntryUrl(manifest, manifestUrl) {
212
+ const meta = manifest?.metaData;
213
+ if (!meta?.ssrRemoteEntry?.name) return null;
214
+ const base = manifestUrl.replace(/\/[^/]+$/, "/");
215
+ const entryPath = (meta.ssrRemoteEntry.path || "") + meta.ssrRemoteEntry.name;
216
+ return {
217
+ url: new URL(entryPath, base).href,
218
+ type: meta.ssrRemoteEntry.type || "module",
219
+ versionKey: computeManifestVersionKey(manifest)
220
+ };
221
+ }
222
+ /**
223
+ * Derive the SSR entry URL by convention when no manifest is available.
224
+ * remoteEntry.js → remoteEntry.ssr.js
225
+ * remoteEntry.js → /__mf_ssr__/remoteEntry.ssr.js (dev middleware)
226
+ * Returns the first URL that responds with a 200.
227
+ */
228
+ async function headCheckSsrEntry(candidate, fetchTimeoutMs) {
229
+ try {
230
+ const res = await fetchWithTimeout(candidate.url, { method: "HEAD" }, fetchTimeoutMs);
231
+ const ct = res.headers.get("content-type") ?? "";
232
+ if (res.ok && !ct.includes("text/html")) return candidate;
233
+ } catch {}
234
+ return null;
235
+ }
236
+ function resolveAssetBaseUrl(entryUrl, manifest, manifestUrl) {
237
+ const remoteEntry = manifest?.metaData?.remoteEntry;
238
+ if (remoteEntry?.name) return resolveEntryAssetUrl(remoteEntry, manifestUrl);
239
+ if (!isManifestEntry(entryUrl)) return entryUrl;
240
+ return new URL("remoteEntry.js", manifestUrl.replace(/\/[^/]+$/, "/")).href;
241
+ }
242
+ async function buildEntryContext(entryUrl, fetchTimeoutMs) {
243
+ const manifestUrl = getManifestUrl(entryUrl);
244
+ const manifest = await fetchManifestCached(manifestUrl, fetchTimeoutMs);
245
+ const assetBaseUrl = resolveAssetBaseUrl(entryUrl, manifest, manifestUrl);
246
+ return {
247
+ entryUrl,
248
+ manifestUrl,
249
+ manifest,
250
+ assetBaseUrl,
251
+ filename: getEntryFilename(assetBaseUrl),
252
+ remoteOrigin: assetBaseUrl.replace(/\/[^/]+$/, "")
253
+ };
254
+ }
255
+ function buildSsrEntryCandidates(ctx, options = {}) {
256
+ const { assetBaseUrl, filename, remoteOrigin } = ctx;
257
+ const base = assetBaseUrl.replace(/\.[^.]+$/, "");
258
+ const candidates = [];
259
+ if (!options.skipServerBuild) candidates.push({
260
+ url: `${remoteOrigin}/__mf_server__/${filename}.ssr.js`,
261
+ type: "module",
262
+ versionKey: UNVERSIONED
263
+ });
264
+ candidates.push({
265
+ url: `${base}.ssr.js`,
266
+ type: "module",
267
+ versionKey: UNVERSIONED
268
+ }, {
269
+ url: `${remoteOrigin}/__mf_ssr__/${filename}.ssr.js`,
270
+ type: "module",
271
+ versionKey: UNVERSIONED
272
+ });
273
+ return candidates;
274
+ }
275
+ async function resolveFirstReachableCandidate(candidates, fetchTimeoutMs) {
276
+ for (const candidate of candidates) {
277
+ const hit = await headCheckSsrEntry(candidate, fetchTimeoutMs);
278
+ if (hit) return hit;
279
+ }
280
+ return null;
281
+ }
282
+ async function resolveSSREntryImpl(remoteEntryUrl, fetchTimeoutMs) {
283
+ if (isSsrEntry(remoteEntryUrl)) return {
284
+ url: remoteEntryUrl,
285
+ type: "module",
286
+ versionKey: UNVERSIONED
287
+ };
288
+ if (!isManifestEntry(remoteEntryUrl)) {
289
+ const filename = getEntryFilename(remoteEntryUrl);
290
+ const fromServerBuild = await headCheckSsrEntry({
291
+ url: `${remoteEntryUrl.replace(/\/[^/]+$/, "")}/__mf_server__/${filename}.ssr.js`,
292
+ type: "module",
293
+ versionKey: UNVERSIONED
294
+ }, fetchTimeoutMs);
295
+ if (fromServerBuild) return fromServerBuild;
296
+ }
297
+ const ctx = await buildEntryContext(remoteEntryUrl, fetchTimeoutMs);
298
+ if (ctx.manifest) {
299
+ const fromManifest = resolveSSREntryUrl(ctx.manifest, ctx.manifestUrl);
300
+ if (fromManifest) return fromManifest;
301
+ }
302
+ return resolveFirstReachableCandidate(buildSsrEntryCandidates(ctx, { skipServerBuild: !isManifestEntry(remoteEntryUrl) }), fetchTimeoutMs);
303
+ }
304
+ function setSsrEntryCache(remoteEntryUrl, fetchTimeoutMs) {
305
+ const cacheKey = makeUrlCacheKey(remoteEntryUrl, fetchTimeoutMs);
306
+ const record = {
307
+ promise: resolveSSREntryImpl(remoteEntryUrl, fetchTimeoutMs),
308
+ resolvedAt: Date.now()
309
+ };
310
+ ssrEntryCache.set(cacheKey, record);
311
+ record.promise.then((entry) => {
312
+ if (!entry && ssrEntryCache.get(cacheKey) === record) ssrEntryCache.delete(cacheKey);
313
+ });
314
+ return record;
315
+ }
316
+ async function getSSREntry(remoteEntryUrl, maxAgeMs, fetchTimeoutMs) {
317
+ const cacheKey = makeUrlCacheKey(remoteEntryUrl, fetchTimeoutMs);
318
+ const cached = ssrEntryCache.get(cacheKey);
319
+ if (!cached) return setSsrEntryCache(remoteEntryUrl, fetchTimeoutMs).promise;
320
+ if (!(typeof maxAgeMs === "number" && maxAgeMs >= 0 && Date.now() - cached.resolvedAt >= maxAgeMs)) return cached.promise;
321
+ const previous = await cached.promise.catch(() => null);
322
+ manifestFetchCache.delete(makeUrlCacheKey(getManifestUrl(remoteEntryUrl), fetchTimeoutMs));
323
+ const record = setSsrEntryCache(remoteEntryUrl, fetchTimeoutMs);
324
+ const next = await record.promise.catch(() => null);
325
+ if (previous && next && previous.versionKey !== next.versionKey) dropRemoteCaches(remoteEntryUrl);
326
+ return record.promise;
327
+ }
328
+ /**
329
+ * Drop per-remote caches after a version change so old artifacts stop being
330
+ * reused. Temp-file cache keys hold SSR entry/chunk URLs (not the browser
331
+ * entry URL), so scope the invalidation by origin.
332
+ */
333
+ function dropRemoteCaches(remoteEntryUrl) {
334
+ let origin;
335
+ try {
336
+ origin = new URL(remoteEntryUrl).origin;
337
+ } catch {
338
+ return;
339
+ }
340
+ for (const [key] of tempFileCache) if (JSON.parse(key)[2].startsWith(origin)) {
341
+ tempFileCache.delete(key);
342
+ tempFilePathCache.delete(key);
343
+ }
344
+ }
345
+ /**
346
+ * Drop the loader's caches so the next `loadEntry` re-resolves and re-fetches
347
+ * remote SSR entries. Pass a remote entry URL to scope the invalidation to one
348
+ * remote; call with no arguments to invalidate everything.
349
+ *
350
+ * Note: the MF runtime keeps its own container/module caches per federation
351
+ * instance. This function best-effort clears the module caches of all global
352
+ * federation instances so re-renders load fresh remote modules, but hosts that
353
+ * hold direct references to previously loaded modules keep those references.
354
+ */
355
+ function revalidate(remoteEntryUrl) {
356
+ if (remoteEntryUrl) {
357
+ for (const key of ssrEntryCache.keys()) if (key.endsWith(`::${remoteEntryUrl}`)) ssrEntryCache.delete(key);
358
+ const manifestUrl = getManifestUrl(remoteEntryUrl);
359
+ for (const key of manifestFetchCache.keys()) if (key.endsWith(`::${manifestUrl}`)) manifestFetchCache.delete(key);
360
+ dropRemoteCaches(remoteEntryUrl);
361
+ } else {
362
+ ssrEntryCache.clear();
363
+ manifestFetchCache.clear();
364
+ tempFileCache.clear();
365
+ tempFilePathCache.clear();
366
+ }
367
+ const federation = globalThis.__FEDERATION__;
368
+ for (const instance of federation?.__INSTANCES__ ?? []) try {
369
+ instance?.moduleCache?.clear?.();
370
+ } catch {}
371
+ }
372
+ const tempFileCache = /* @__PURE__ */ new Map();
373
+ const tempFilePathCache = /* @__PURE__ */ new Map();
374
+ function getSsrTransformContextKey(resolvedShared, shareScopeName) {
375
+ return JSON.stringify([shareScopeName, Object.entries(resolvedShared).sort(([left], [right]) => left.localeCompare(right))]);
376
+ }
377
+ let ssrCacheDirPromise;
378
+ async function getSSRCacheDir() {
379
+ if (!ssrCacheDirPromise) ssrCacheDirPromise = (async () => {
380
+ const { join } = await _path();
381
+ const { rmSync } = await _fs();
382
+ const dir = join(process.cwd(), "node_modules", ".ssr-cache");
383
+ process.once("exit", () => {
384
+ try {
385
+ rmSync(dir, {
386
+ recursive: true,
387
+ force: true
388
+ });
389
+ } catch {}
390
+ });
391
+ return dir;
392
+ })();
393
+ return ssrCacheDirPromise;
394
+ }
395
+ /**
396
+ * Neutralize browser-only preload machinery in Vite/Rolldown output so the
397
+ * code can evaluate in Node. Shared by the temp-file and vm strategies.
398
+ */
399
+ function neutralizeBrowserPreloadHelpers(code) {
400
+ code = code.replace(/import\s*\{([^}]*)\}\s*from\s*["'][^"']*preload-helper[^"']*["'];?/g, (_m, bindings) => {
401
+ return bindings.split(",").map((b) => {
402
+ const parts = b.trim().split(/\s+as\s+/);
403
+ return (parts[1] ?? parts[0]).trim();
404
+ }).filter(Boolean).map((l) => `const ${l} = (fn) => fn();`).join("\n");
405
+ });
406
+ code = code.replace(/__vite__mapDeps\([^)]+\)/g, "[]");
407
+ code = code.replace(/\b([A-Za-z_$][\w$]*)\s*\(\s*\(\s*\)\s*=>\s*import\(([^)]*)\)\s*,\s*\[\]\s*\)/g, "import($2)");
408
+ return code;
409
+ }
410
+ function transformSsrCode(code, base, sharedPkgMap) {
411
+ code = code.replace(/((?:from|export\s*\*\s*from)\s*)(["'`])(\.\.?\/[^"'`\s][^"'`]*)["'`]/g, (_m, prefix, _q, specifier) => `${prefix}"${new URL(specifier, base).href}"`);
412
+ code = code.replace(/(import\s*)(["'`])(\.\.?\/[^"'`\s][^"'`]*)["'`]/g, (_m, prefix, _q, specifier) => `${prefix}"${new URL(specifier, base).href}"`);
413
+ code = code.replace(/(import\s*\(\s*)(["'`])(\.\.?\/[^"'`\s][^"'`]*)["'`](\s*\))/g, (_m, prefix, _q, specifier, suffix) => `${prefix}"${new URL(specifier, base).href}"${suffix}`);
414
+ if (sharedPkgMap && sharedPkgMap.size > 0) code = code.replace(/(?:from|import\s*\()\s*(["'`])([^"'`./][^"'`]*)["'`]/g, (m, _q, specifier) => {
415
+ const resolved = sharedPkgMap.get(specifier);
416
+ return resolved ? m.replace(specifier, `file://${resolved}`) : m;
417
+ });
418
+ return neutralizeBrowserPreloadHelpers(code);
419
+ }
420
+ function isVitePreloadHelperSpecifier(specifier) {
421
+ return specifier.includes("preload-helper");
422
+ }
423
+ /**
424
+ * Fetch an HTTP ESM module, transform it, write it to a temp .js file and
425
+ * return the file path. Recursively does the same for HTTP transitive imports
426
+ * so that `import('file:///...temp.js')` can resolve them.
427
+ *
428
+ * `versionKey` participates in both the cache key and the temp file name, so
429
+ * a remote redeploy (new manifest → new key) produces new files and bypasses
430
+ * Node's ESM module cache instead of serving the stale build.
431
+ */
432
+ async function fetchEsmToTempFile(url, tmpDir, visited, pending, sharedPkgMap, versionKey = UNVERSIONED, fetchTimeoutMs = DEFAULT_SSR_FETCH_TIMEOUT_MS, contextKey = "default") {
433
+ const cacheKey = JSON.stringify([
434
+ fetchTimeoutMs,
435
+ versionKey,
436
+ url,
437
+ contextKey
438
+ ]);
439
+ if (visited.has(url)) return visited.get(url);
440
+ const cached = tempFileCache.get(cacheKey);
441
+ if (cached) {
442
+ pending.add(cached);
443
+ const tmpFile = await tempFilePathCache.get(cacheKey);
444
+ visited.set(url, tmpFile);
445
+ return tmpFile;
446
+ }
447
+ const tmpFilePromise = (async () => {
448
+ const { createHash } = await _crypto();
449
+ const { join } = await _path();
450
+ return join(tmpDir, `${createHash("sha1").update(cacheKey).digest("hex").slice(0, 12)}.js`);
451
+ })();
452
+ tempFilePathCache.set(cacheKey, tmpFilePromise);
453
+ const promise = (async () => {
454
+ const tmpFile = await tmpFilePromise;
455
+ visited.set(url, tmpFile);
456
+ const res = await fetchWithTimeout(url, {}, fetchTimeoutMs);
457
+ let code = await res.text();
458
+ if (!res.ok) throw new SsrEntryHttpError(url, res.status, res.statusText, getBodyPreview(code));
459
+ const base = url.replace(/\/[^/]*$/, "/");
460
+ const relImports = [];
461
+ const relRegex = /(?:from|export\s*\*\s*from|import\s*(?:\(|\s))\s*["'`]([^"'`\s]+)["'`]/g;
462
+ let m;
463
+ while ((m = relRegex.exec(code)) !== null) if ((m[1].startsWith("./") || m[1].startsWith("../")) && !isVitePreloadHelperSpecifier(m[1])) relImports.push(new URL(m[1], base).href);
464
+ const subMap = /* @__PURE__ */ new Map();
465
+ await Promise.all([...new Set(relImports)].filter((u) => u.startsWith("http://") || u.startsWith("https://")).map(async (u) => {
466
+ const tmpPath = await fetchEsmToTempFile(u, tmpDir, visited, pending, sharedPkgMap, versionKey, fetchTimeoutMs, contextKey);
467
+ subMap.set(u, `file://${tmpPath}`);
468
+ }));
469
+ code = transformSsrCode(code, base, sharedPkgMap);
470
+ for (const [httpUrl, fileUrl] of subMap) code = code.split(httpUrl).join(fileUrl);
471
+ const { writeFileSync } = await _fs();
472
+ writeFileSync(tmpFile, code, "utf8");
473
+ return tmpFile;
474
+ })();
475
+ tempFileCache.set(cacheKey, promise);
476
+ pending.add(promise);
477
+ promise.catch(() => {
478
+ if (tempFileCache.get(cacheKey) === promise) tempFileCache.delete(cacheKey);
479
+ if (tempFilePathCache.get(cacheKey) === tmpFilePromise) tempFilePathCache.delete(cacheKey);
480
+ });
481
+ return promise;
482
+ }
483
+ async function fetchEsmGraphToTempFile(url, tmpDir, sharedPkgMap, versionKey = UNVERSIONED, fetchTimeoutMs = DEFAULT_SSR_FETCH_TIMEOUT_MS, contextKey = "default") {
484
+ const pending = /* @__PURE__ */ new Set();
485
+ const rootFile = await fetchEsmToTempFile(url, tmpDir, /* @__PURE__ */ new Map(), pending, sharedPkgMap, versionKey, fetchTimeoutMs, contextKey);
486
+ await Promise.all(pending);
487
+ return rootFile;
488
+ }
489
+ async function importTempModule(filePath, versionKey) {
490
+ return await import(
491
+ /* @vite-ignore */
492
+ `${filePath}?v=${encodeURIComponent(versionKey)}`
493
+ );
494
+ }
495
+ let warnedVmUnavailable = false;
496
+ async function tryVmStrategy(ssrEntry, options) {
497
+ const { loadViaVmStrategy, isVmStrategyAvailable } = await import("./ssrVmStrategy-ChF8MB7l.js");
498
+ if (!await isVmStrategyAvailable()) {
499
+ if (!warnedVmUnavailable) {
500
+ warnedVmUnavailable = true;
501
+ console.warn("[mf-vite:ssr-entry-loader] strategy \"vm\" requires vm.SourceTextModule (run Node with --experimental-vm-modules); falling back to the temp-file strategy.");
502
+ }
503
+ return null;
504
+ }
505
+ return await loadViaVmStrategy(ssrEntry.url, {
506
+ resolvedShared: options.resolvedShared,
507
+ shareScopeName: options.shareScopeName,
508
+ versionKey: ssrEntry.versionKey,
509
+ fetchTimeoutMs: options.fetchTimeoutMs,
510
+ cacheContext: options.cacheContext,
511
+ federationInstance: options.federationInstance
512
+ });
513
+ }
514
+ async function loadSSRRemoteEntry(ssrEntry, options) {
515
+ const { url, type, versionKey } = ssrEntry;
516
+ const { resolvedShared } = options;
517
+ if (type === "commonjs-module" || type === "commonjs") {
518
+ const { createRequire } = await _module();
519
+ const req = createRequire(import.meta.url);
520
+ try {
521
+ return req(url);
522
+ } catch {}
523
+ }
524
+ if (url.startsWith("http://") || url.startsWith("https://")) {
525
+ const urlObj = new URL(url);
526
+ if (urlObj.pathname.includes("/__mf_ssr__/")) {
527
+ const remoteOrigin = urlObj.origin;
528
+ const runner = await getOrCreateRunner(remoteOrigin, options.fetchTimeoutMs);
529
+ if (!runner) {
530
+ if (process.env.NODE_ENV !== "production") return null;
531
+ } else try {
532
+ const mod = await runner.import(urlObj.pathname);
533
+ if (mod && typeof mod === "object" && "init" in mod) return mod;
534
+ if (process.env.NODE_ENV !== "production") return null;
535
+ } catch {
536
+ if (process.env.NODE_ENV !== "production") return null;
537
+ }
538
+ }
539
+ if (options.strategy === "vm") try {
540
+ const fromVm = await tryVmStrategy(ssrEntry, options);
541
+ if (fromVm) return fromVm;
542
+ } catch (error) {
543
+ if (isSsrEntryHttpError(error)) throw error;
544
+ }
545
+ const { mkdirSync } = await _fs();
546
+ const cacheDir = await getSSRCacheDir();
547
+ mkdirSync(cacheDir, { recursive: true });
548
+ const sharedPkgMap = new Map(Object.entries(resolvedShared));
549
+ try {
550
+ return await importTempModule(await fetchEsmGraphToTempFile(url, cacheDir, sharedPkgMap, versionKey, options.fetchTimeoutMs, getSsrTransformContextKey(resolvedShared, options.shareScopeName)), versionKey);
551
+ } catch (error) {
552
+ if (isSsrEntryHttpError(error)) throw error;
553
+ return null;
554
+ }
555
+ }
556
+ try {
557
+ return await import(
558
+ /* @vite-ignore */
559
+ url
560
+ );
561
+ } catch {
562
+ return null;
563
+ }
564
+ }
565
+ function ssrEntryLoaderPlugin(options = {}) {
566
+ const resolved = {
567
+ resolvedShared: options.resolvedShared ?? {},
568
+ strategy: options.strategy ?? "temp-file",
569
+ shareScopeName: options.shareScopeName ?? "default",
570
+ maxAgeMs: options.maxAgeMs,
571
+ fetchTimeoutMs: options.fetchTimeoutMs ?? 1e4,
572
+ cacheContext: {}
573
+ };
574
+ return {
575
+ name: "mf-vite:ssr-entry-loader",
576
+ async loadEntry({ remoteInfo, origin }) {
577
+ if (!isNodeServer()) return;
578
+ const loadOptions = origin ? {
579
+ ...resolved,
580
+ cacheContext: origin,
581
+ federationInstance: origin
582
+ } : resolved;
583
+ const ssrEntry = await getSSREntry(remoteInfo.entry, loadOptions.maxAgeMs, loadOptions.fetchTimeoutMs);
584
+ if (!ssrEntry) return;
585
+ const mod = await loadSSRRemoteEntry(ssrEntry, loadOptions);
586
+ if (!mod) return;
587
+ return mod;
588
+ }
589
+ };
590
+ }
591
+ //#endregion
592
+ export { DEFAULT_SSR_FETCH_TIMEOUT_MS as a, ssrEntryLoaderPlugin as i, neutralizeBrowserPreloadHelpers as n, fetchWithTimeout as o, revalidate as r, SsrEntryHttpError as t };