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