@module-federation/vite 1.15.4 → 1.16.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 +30 -1
- package/lib/index.cjs +1123 -870
- package/lib/index.d.cts +22 -6
- package/lib/index.d.mts +22 -6
- package/lib/index.mjs +1063 -793
- package/lib/packageUtils-CbbnJvKu.cjs +358 -0
- package/lib/packageUtils-DOekOsFz.mjs +250 -0
- package/lib/pluginDts-B5pcUmam.mjs +309 -0
- package/lib/pluginDts-BEOKyKQ-.cjs +311 -0
- package/lib/utils/ssrEntryLoader.cjs +301 -0
- package/lib/utils/ssrEntryLoader.d.cts +65 -0
- package/lib/utils/ssrEntryLoader.d.mts +66 -0
- package/lib/utils/ssrEntryLoader.mjs +301 -0
- package/package.json +13 -5
package/lib/index.mjs
CHANGED
|
@@ -1,17 +1,12 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { a as getPackageName, c as isNuxtProjectRoot, d as setPackageDetectionCwd, f as createModuleFederationError, h as __require, i as getPackageDetectionCwd, l as packageNameDecode, m as mfWarn, n as getInstalledPackageJson, o as getPackageNameFromNodeModulePath, p as mfError, r as getIsRolldown, s as hasPackageDependency, t as getInstalledPackageEntry, u as packageNameEncode } from "./packageUtils-DOekOsFz.mjs";
|
|
2
2
|
import * as fs$1 from "fs";
|
|
3
|
-
import
|
|
4
|
-
import { createRequire
|
|
3
|
+
import { existsSync, readFileSync, realpathSync, statSync, writeFileSync } from "fs";
|
|
4
|
+
import { createRequire } from "module";
|
|
5
5
|
import * as path$1 from "pathe";
|
|
6
6
|
import path, { basename } from "pathe";
|
|
7
|
-
import {
|
|
8
|
-
import { consumeTypesAPI, generateTypesAPI, isTSProject, normalizeConsumeTypesOptions, normalizeDtsOptions, normalizeGenerateTypesOptions } from "@module-federation/dts-plugin";
|
|
9
|
-
import { rpc } from "@module-federation/dts-plugin/core";
|
|
7
|
+
import { version } from "vite";
|
|
10
8
|
import { fileURLToPath } from "url";
|
|
11
9
|
import { init, parse } from "es-module-lexer";
|
|
12
|
-
//#region \0rolldown/runtime.js
|
|
13
|
-
var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
14
|
-
//#endregion
|
|
15
10
|
//#region src/utils/codeRewriter.ts
|
|
16
11
|
const BASE64_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
|
17
12
|
var CodeRewriter = class {
|
|
@@ -173,237 +168,6 @@ function injectEntryScript(html, initSrc) {
|
|
|
173
168
|
return html.replace("<head>", `<head><script type="module" src=${JSON.stringify(src)}><\/script>`);
|
|
174
169
|
}
|
|
175
170
|
//#endregion
|
|
176
|
-
//#region src/utils/logger.ts
|
|
177
|
-
const MODULE_FEDERATION_LOG_PREFIX = "[Module Federation]";
|
|
178
|
-
function formatModuleFederationMessage(message) {
|
|
179
|
-
return `${MODULE_FEDERATION_LOG_PREFIX} ${message}`;
|
|
180
|
-
}
|
|
181
|
-
function createModuleFederationError(message) {
|
|
182
|
-
return new Error(formatModuleFederationMessage(message));
|
|
183
|
-
}
|
|
184
|
-
function toConsoleArgs(message, rest = []) {
|
|
185
|
-
if (typeof message === "string") return [formatModuleFederationMessage(message), ...rest];
|
|
186
|
-
if (message === void 0) return [MODULE_FEDERATION_LOG_PREFIX, ...rest];
|
|
187
|
-
return [
|
|
188
|
-
MODULE_FEDERATION_LOG_PREFIX,
|
|
189
|
-
message,
|
|
190
|
-
...rest
|
|
191
|
-
];
|
|
192
|
-
}
|
|
193
|
-
const moduleFederationConsole = {
|
|
194
|
-
log(message, ...rest) {
|
|
195
|
-
console.log(...toConsoleArgs(message, rest));
|
|
196
|
-
},
|
|
197
|
-
warn(message, ...rest) {
|
|
198
|
-
console.warn(...toConsoleArgs(message, rest));
|
|
199
|
-
},
|
|
200
|
-
error(message, ...rest) {
|
|
201
|
-
console.error(...toConsoleArgs(message, rest));
|
|
202
|
-
}
|
|
203
|
-
};
|
|
204
|
-
moduleFederationConsole.log;
|
|
205
|
-
const mfWarn = moduleFederationConsole.warn;
|
|
206
|
-
const mfError = moduleFederationConsole.error;
|
|
207
|
-
//#endregion
|
|
208
|
-
//#region src/utils/packageUtils.ts
|
|
209
|
-
const dependencyPresenceCache = /* @__PURE__ */ new Map();
|
|
210
|
-
let packageDetectionCwd;
|
|
211
|
-
function getDependencyCacheKey(cwd, dependencyName) {
|
|
212
|
-
return `${cwd}:${dependencyName}`;
|
|
213
|
-
}
|
|
214
|
-
function setPackageDetectionCwd(cwd) {
|
|
215
|
-
packageDetectionCwd = cwd;
|
|
216
|
-
}
|
|
217
|
-
function getPackageDetectionCwd() {
|
|
218
|
-
return packageDetectionCwd || process.cwd();
|
|
219
|
-
}
|
|
220
|
-
const DEFAULT_EXPORT_CONDITIONS = [
|
|
221
|
-
"browser",
|
|
222
|
-
"import",
|
|
223
|
-
"module",
|
|
224
|
-
"default",
|
|
225
|
-
"require"
|
|
226
|
-
];
|
|
227
|
-
function resolveExportsEntry(exportsField, conditions = DEFAULT_EXPORT_CONDITIONS) {
|
|
228
|
-
if (typeof exportsField === "string") return exportsField;
|
|
229
|
-
if (!exportsField || typeof exportsField !== "object") return void 0;
|
|
230
|
-
const record = exportsField;
|
|
231
|
-
const rootExport = record["."];
|
|
232
|
-
if (rootExport) return resolveExportsEntry(rootExport);
|
|
233
|
-
for (const condition of conditions) {
|
|
234
|
-
const target = resolveExportsEntry(record[condition], conditions);
|
|
235
|
-
if (target) return target;
|
|
236
|
-
}
|
|
237
|
-
for (const target of Object.values(record)) {
|
|
238
|
-
const resolved = resolveExportsEntry(target, conditions);
|
|
239
|
-
if (resolved) return resolved;
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
|
-
function getPackageExportsTarget(pkg, packageName, exportsField) {
|
|
243
|
-
if (typeof exportsField === "string") return pkg === packageName ? exportsField : void 0;
|
|
244
|
-
if (!exportsField || typeof exportsField !== "object") return void 0;
|
|
245
|
-
const record = exportsField;
|
|
246
|
-
const subpath = pkg === packageName ? "." : `.${pkg.slice(packageName.length)}`;
|
|
247
|
-
if (subpath !== ".") return record[subpath];
|
|
248
|
-
return record["."] ?? (!Object.keys(record).some((key) => key.startsWith(".")) ? record : void 0);
|
|
249
|
-
}
|
|
250
|
-
/**
|
|
251
|
-
* Escaping rules:
|
|
252
|
-
* Convert using the format __${mapping}__, where _ and $ are not allowed in npm package names but can be used in variable names.
|
|
253
|
-
* @ => 1
|
|
254
|
-
* / => 2
|
|
255
|
-
* - => 3
|
|
256
|
-
* . => 4
|
|
257
|
-
*/
|
|
258
|
-
/**
|
|
259
|
-
* Encodes a package name into a valid file name.
|
|
260
|
-
* @param {string} name - The package name, e.g., "@scope/xx-xx.xx".
|
|
261
|
-
* @returns {string} - The encoded file name.
|
|
262
|
-
*/
|
|
263
|
-
function packageNameEncode(name) {
|
|
264
|
-
if (typeof name !== "string") throw createModuleFederationError("A string package name is required");
|
|
265
|
-
return name.replace(/@/g, "_mf_0_").replace(/\//g, "_mf_1_").replace(/-/g, "_mf_2_").replace(/\./g, "_mf_3_");
|
|
266
|
-
}
|
|
267
|
-
/**
|
|
268
|
-
* Decodes an encoded file name back to the original package name.
|
|
269
|
-
* @param {string} encoded - The encoded file name, e.g., "_mf_0_scope_mf_1_xx_mf_2_xx_mf_3_xx".
|
|
270
|
-
* @returns {string} - The decoded package name.
|
|
271
|
-
*/
|
|
272
|
-
function packageNameDecode(encoded) {
|
|
273
|
-
if (typeof encoded !== "string") throw createModuleFederationError("A string encoded file name is required");
|
|
274
|
-
return encoded.replace(/_mf_0_/g, "@").replace(/_mf_1_/g, "/").replace(/_mf_2_/g, "-").replace(/_mf_3_/g, ".");
|
|
275
|
-
}
|
|
276
|
-
/**
|
|
277
|
-
* Removes any subpath from an npm package specifier and returns the package name only.
|
|
278
|
-
* @param {string} packageString - The package specifier, e.g., "@scope/pkg/runtime" or "react/jsx-runtime".
|
|
279
|
-
* @returns {string} - The base npm package name.
|
|
280
|
-
*/
|
|
281
|
-
function getPackageName(packageString) {
|
|
282
|
-
const match = packageString.match(/^(?:@[^/]+\/)?[^/]+/);
|
|
283
|
-
return match ? match[0] : packageString;
|
|
284
|
-
}
|
|
285
|
-
function getPackageNameFromNodeModulePath(source) {
|
|
286
|
-
const normalized = source.replace(/\\/g, "/");
|
|
287
|
-
const nodeModulesIndex = normalized.lastIndexOf("/node_modules/");
|
|
288
|
-
if (nodeModulesIndex < 0) return;
|
|
289
|
-
const parts = normalized.slice(nodeModulesIndex + 14).split("/");
|
|
290
|
-
if (!parts[0]) return;
|
|
291
|
-
if (parts[0].startsWith("@")) return parts[1] ? `${parts[0]}/${parts[1]}` : void 0;
|
|
292
|
-
return parts[0];
|
|
293
|
-
}
|
|
294
|
-
function getInstalledPackageJson(pkg, opts) {
|
|
295
|
-
const cwd = opts?.cwd || getPackageDetectionCwd();
|
|
296
|
-
const packageName = opts?.packageName || getPackageName(pkg);
|
|
297
|
-
const tryReadPackageJson = (packageJsonPath) => {
|
|
298
|
-
if (!existsSync(packageJsonPath)) return void 0;
|
|
299
|
-
try {
|
|
300
|
-
return {
|
|
301
|
-
path: packageJsonPath,
|
|
302
|
-
dir: path.dirname(packageJsonPath),
|
|
303
|
-
packageJson: JSON.parse(readFileSync(packageJsonPath, "utf-8"))
|
|
304
|
-
};
|
|
305
|
-
} catch {
|
|
306
|
-
return;
|
|
307
|
-
}
|
|
308
|
-
};
|
|
309
|
-
const findPackageInPnpmStore = (startDir) => {
|
|
310
|
-
let currentDir = startDir;
|
|
311
|
-
const rootDir = path.parse(currentDir).root;
|
|
312
|
-
while (true) {
|
|
313
|
-
const pnpmStoreDir = path.join(currentDir, "node_modules", ".pnpm");
|
|
314
|
-
if (existsSync(pnpmStoreDir)) try {
|
|
315
|
-
for (const entry of readdirSync(pnpmStoreDir, { withFileTypes: true })) {
|
|
316
|
-
if (!entry.isDirectory()) continue;
|
|
317
|
-
const candidate = tryReadPackageJson(path.join(pnpmStoreDir, entry.name, "node_modules", packageName, "package.json"));
|
|
318
|
-
if (candidate?.packageJson.name === packageName) return candidate;
|
|
319
|
-
}
|
|
320
|
-
} catch {}
|
|
321
|
-
if (currentDir === rootDir) break;
|
|
322
|
-
currentDir = path.dirname(currentDir);
|
|
323
|
-
}
|
|
324
|
-
};
|
|
325
|
-
try {
|
|
326
|
-
const projectRequire = createRequire$1(new URL(`file://${path.join(cwd, "package.json")}`));
|
|
327
|
-
let resolvedPath;
|
|
328
|
-
if (opts?.fromResolvedEntry) resolvedPath = opts.fromResolvedEntry;
|
|
329
|
-
else try {
|
|
330
|
-
resolvedPath = projectRequire.resolve(pkg);
|
|
331
|
-
} catch {
|
|
332
|
-
resolvedPath = projectRequire.resolve(packageName);
|
|
333
|
-
}
|
|
334
|
-
let currentDir = path.dirname(resolvedPath);
|
|
335
|
-
const rootDir = path.parse(currentDir).root;
|
|
336
|
-
while (true) {
|
|
337
|
-
const packageJsonPath = path.join(currentDir, "package.json");
|
|
338
|
-
if (existsSync(packageJsonPath)) {
|
|
339
|
-
const packageJsonContent = readFileSync(packageJsonPath, "utf-8");
|
|
340
|
-
try {
|
|
341
|
-
const packageJson = JSON.parse(packageJsonContent);
|
|
342
|
-
if (packageJson.name === packageName) return {
|
|
343
|
-
path: packageJsonPath,
|
|
344
|
-
dir: currentDir,
|
|
345
|
-
packageJson
|
|
346
|
-
};
|
|
347
|
-
} catch (error) {
|
|
348
|
-
if (!(error instanceof SyntaxError)) throw error;
|
|
349
|
-
}
|
|
350
|
-
}
|
|
351
|
-
if (currentDir === rootDir) break;
|
|
352
|
-
currentDir = path.dirname(currentDir);
|
|
353
|
-
}
|
|
354
|
-
} catch {
|
|
355
|
-
let currentDir = cwd;
|
|
356
|
-
const rootDir = path.parse(currentDir).root;
|
|
357
|
-
while (true) {
|
|
358
|
-
const directCandidate = tryReadPackageJson(path.join(currentDir, "node_modules", packageName, "package.json"));
|
|
359
|
-
if (directCandidate?.packageJson.name === packageName) return directCandidate;
|
|
360
|
-
if (currentDir === rootDir) break;
|
|
361
|
-
currentDir = path.dirname(currentDir);
|
|
362
|
-
}
|
|
363
|
-
return findPackageInPnpmStore(cwd);
|
|
364
|
-
}
|
|
365
|
-
}
|
|
366
|
-
function getInstalledPackageEntry(pkg, opts) {
|
|
367
|
-
const installed = getInstalledPackageJson(pkg, opts);
|
|
368
|
-
if (!installed) return void 0;
|
|
369
|
-
const cwd = opts?.cwd || getPackageDetectionCwd();
|
|
370
|
-
const packageName = opts?.packageName || getPackageName(pkg);
|
|
371
|
-
if (pkg !== packageName && opts?.resolveSubpathWithRequire !== false) try {
|
|
372
|
-
return createRequire$1(new URL(`file://${path.join(cwd, "package.json")}`)).resolve(pkg);
|
|
373
|
-
} catch {}
|
|
374
|
-
const packageJson = installed.packageJson;
|
|
375
|
-
const explicitEntry = resolveExportsEntry(getPackageExportsTarget(pkg, packageName, packageJson.exports), opts?.conditions) || (typeof packageJson.module === "string" ? packageJson.module : void 0) || (typeof packageJson.main === "string" ? packageJson.main : void 0) || "index.js";
|
|
376
|
-
return path.join(installed.dir, explicitEntry);
|
|
377
|
-
}
|
|
378
|
-
/**
|
|
379
|
-
* Detect whether the current runtime is Vite 8+ by checking for a Vite version flag
|
|
380
|
-
* on the plugin hook context, with Rolldown metadata kept as a compatibility fallback.
|
|
381
|
-
*/
|
|
382
|
-
function getIsRolldown(ctx) {
|
|
383
|
-
const viteVersion = ctx?.meta?.viteVersion;
|
|
384
|
-
const viteMajor = Number(String(viteVersion ?? "").split(".")[0]);
|
|
385
|
-
return Number.isFinite(viteMajor) && viteMajor >= 8 || !!ctx?.meta?.rolldownVersion;
|
|
386
|
-
}
|
|
387
|
-
function hasPackageDependency(dependencyName, cwd = packageDetectionCwd || process.cwd()) {
|
|
388
|
-
const cacheKey = getDependencyCacheKey(cwd, dependencyName);
|
|
389
|
-
const cached = dependencyPresenceCache.get(cacheKey);
|
|
390
|
-
if (cached !== void 0) return cached;
|
|
391
|
-
try {
|
|
392
|
-
const packageJson = JSON.parse(readFileSync(path.join(cwd, "package.json"), "utf8"));
|
|
393
|
-
const hasDependency = [
|
|
394
|
-
packageJson.dependencies,
|
|
395
|
-
packageJson.devDependencies,
|
|
396
|
-
packageJson.peerDependencies,
|
|
397
|
-
packageJson.optionalDependencies
|
|
398
|
-
].some((deps) => !!deps?.[dependencyName]);
|
|
399
|
-
dependencyPresenceCache.set(cacheKey, hasDependency);
|
|
400
|
-
return hasDependency;
|
|
401
|
-
} catch {
|
|
402
|
-
dependencyPresenceCache.set(cacheKey, false);
|
|
403
|
-
return false;
|
|
404
|
-
}
|
|
405
|
-
}
|
|
406
|
-
//#endregion
|
|
407
171
|
//#region src/utils/normalizeModuleFederationOptions.ts
|
|
408
172
|
const INTERNAL_NAME_PREFIX = "__mfe_internal__";
|
|
409
173
|
function toInternalModuleFederationName(name) {
|
|
@@ -475,7 +239,7 @@ function normalizeRemoteItem(key, remote) {
|
|
|
475
239
|
* @returns {string | undefined}
|
|
476
240
|
*/
|
|
477
241
|
function searchPackageVersion(sharedName) {
|
|
478
|
-
const version = getInstalledPackageJson(sharedName
|
|
242
|
+
const version = getInstalledPackageJson(sharedName)?.packageJson.version;
|
|
479
243
|
return typeof version === "string" ? version : void 0;
|
|
480
244
|
}
|
|
481
245
|
function inferVersionFromRequiredVersion(requiredVersion) {
|
|
@@ -598,6 +362,18 @@ function normalizeManifest(manifest) {
|
|
|
598
362
|
}
|
|
599
363
|
let config;
|
|
600
364
|
let explicitSharedKeys = /* @__PURE__ */ new Set();
|
|
365
|
+
function resolveRuntimeImplementation() {
|
|
366
|
+
const fallback = __require.resolve("@module-federation/runtime");
|
|
367
|
+
try {
|
|
368
|
+
const packageJsonPath = __require.resolve("@module-federation/runtime/package.json");
|
|
369
|
+
const packageJson = JSON.parse(fs$1.readFileSync(packageJsonPath, "utf-8"));
|
|
370
|
+
const importExport = packageJson.exports?.["."];
|
|
371
|
+
const exportImport = typeof importExport === "object" ? typeof importExport.import === "string" ? importExport.import : importExport.import?.default : void 0;
|
|
372
|
+
const esmEntry = packageJson.module || exportImport;
|
|
373
|
+
if (esmEntry) return path$1.join(path$1.dirname(packageJsonPath), esmEntry);
|
|
374
|
+
} catch {}
|
|
375
|
+
return fallback;
|
|
376
|
+
}
|
|
601
377
|
function getNormalizeModuleFederationOptions() {
|
|
602
378
|
return config;
|
|
603
379
|
}
|
|
@@ -622,7 +398,7 @@ function normalizeModuleFederationOptions(options) {
|
|
|
622
398
|
shareScope: options.shareScope || "default",
|
|
623
399
|
shared: normalizeShared(options.shared),
|
|
624
400
|
runtimePlugins: options.runtimePlugins || [],
|
|
625
|
-
implementation: options.implementation ||
|
|
401
|
+
implementation: options.implementation || resolveRuntimeImplementation(),
|
|
626
402
|
manifest: normalizeManifest(options.manifest),
|
|
627
403
|
dev: options.dev,
|
|
628
404
|
dts: options.dts,
|
|
@@ -836,11 +612,24 @@ function getDeferredInitPromiseCode() {
|
|
|
836
612
|
initReject = rj;
|
|
837
613
|
});`;
|
|
838
614
|
}
|
|
839
|
-
|
|
615
|
+
let _ssrRemotes = [];
|
|
616
|
+
function setSsrRemotes(remotes) {
|
|
617
|
+
_ssrRemotes = remotes;
|
|
618
|
+
}
|
|
619
|
+
function getSsrNoopResolveCode(enableSsrInit) {
|
|
620
|
+
if (!enableSsrInit) return "";
|
|
840
621
|
return `if (typeof window === 'undefined') {
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
622
|
+
var _noop = { loadRemote: function() { return Promise.resolve(undefined); }, loadShare: function() { return Promise.resolve(undefined); } };
|
|
623
|
+
import(/* @vite-ignore */ '@module-federation/runtime').then(function(runtimeMod) {
|
|
624
|
+
return import(/* @vite-ignore */ '@module-federation/vite/ssrEntryLoader').then(
|
|
625
|
+
function(loaderMod) { return [runtimeMod, [loaderMod.default()]]; },
|
|
626
|
+
function() { return [runtimeMod, []]; }
|
|
627
|
+
);
|
|
628
|
+
}).then(function(pair) {
|
|
629
|
+
var runtime = pair[0].init({ name: '__mf_ssr_host__', remotes: ${JSON.stringify(_ssrRemotes)}, shared: {}, plugins: pair[1] });
|
|
630
|
+
initResolve(runtime);
|
|
631
|
+
}, function() {
|
|
632
|
+
initResolve(_noop);
|
|
844
633
|
});
|
|
845
634
|
}`;
|
|
846
635
|
}
|
|
@@ -855,12 +644,12 @@ if (!${options.stateVar}) {
|
|
|
855
644
|
initResolve,
|
|
856
645
|
initReject,
|
|
857
646
|
};
|
|
858
|
-
${getSsrNoopResolveCode()}
|
|
647
|
+
${getSsrNoopResolveCode(options.enableSsrInit)}
|
|
859
648
|
}
|
|
860
649
|
const ${options.exposedConst} = ${options.stateVar}.${options.exposedProperty};
|
|
861
650
|
`;
|
|
862
651
|
}
|
|
863
|
-
function getRuntimeInitBootstrapCode() {
|
|
652
|
+
function getRuntimeInitBootstrapCode(enableSsrInit = false) {
|
|
864
653
|
return `
|
|
865
654
|
const globalKey = ${JSON.stringify(getRuntimeInitGlobalKey())};
|
|
866
655
|
const moduleCacheGlobalKey = ${JSON.stringify(MODULE_CACHE_GLOBAL_KEY)};
|
|
@@ -875,7 +664,7 @@ if (!globalThis[globalKey]) {
|
|
|
875
664
|
initReject,
|
|
876
665
|
moduleCache: globalThis[moduleCacheGlobalKey],
|
|
877
666
|
};
|
|
878
|
-
${getSsrNoopResolveCode()}
|
|
667
|
+
${getSsrNoopResolveCode(enableSsrInit)}
|
|
879
668
|
}
|
|
880
669
|
globalThis[globalKey].moduleCache ||= globalThis[moduleCacheGlobalKey];
|
|
881
670
|
globalThis[globalKey].moduleCache.share ||= {};
|
|
@@ -891,27 +680,29 @@ globalThis[__mfCacheGlobalKey].remote ||= {};
|
|
|
891
680
|
const __mfModuleCache = globalThis[__mfCacheGlobalKey];
|
|
892
681
|
`;
|
|
893
682
|
}
|
|
894
|
-
function getRuntimeInitPromiseBootstrapCode() {
|
|
683
|
+
function getRuntimeInitPromiseBootstrapCode(enableSsrInit = false) {
|
|
895
684
|
return getRuntimeInitStateBootstrapCode({
|
|
896
685
|
globalKeyVar: "__mfPromiseGlobalKey",
|
|
897
686
|
stateVar: "__mfPromiseState",
|
|
898
687
|
exposedConst: "initPromise",
|
|
899
|
-
exposedProperty: "initPromise"
|
|
688
|
+
exposedProperty: "initPromise",
|
|
689
|
+
enableSsrInit
|
|
900
690
|
});
|
|
901
691
|
}
|
|
902
|
-
function getRuntimeInitResolveBootstrapCode() {
|
|
692
|
+
function getRuntimeInitResolveBootstrapCode(enableSsrInit = false) {
|
|
903
693
|
return getRuntimeInitStateBootstrapCode({
|
|
904
694
|
globalKeyVar: "__mfResolveGlobalKey",
|
|
905
695
|
stateVar: "__mfResolveState",
|
|
906
696
|
exposedConst: "initResolve",
|
|
907
|
-
exposedProperty: "initResolve"
|
|
697
|
+
exposedProperty: "initResolve",
|
|
698
|
+
enableSsrInit
|
|
908
699
|
});
|
|
909
700
|
}
|
|
910
|
-
function writeRuntimeInitStatus(command) {
|
|
701
|
+
function writeRuntimeInitStatus(command, enableSsrInit = false) {
|
|
911
702
|
const exportStatement = command === "build" ? `const { initPromise, initResolve, initReject, moduleCache } = globalThis[globalKey];
|
|
912
703
|
export { initPromise, initResolve, initReject, moduleCache };` : `module.exports = globalThis[globalKey];`;
|
|
913
704
|
virtualRuntimeInitStatus.writeSync(`
|
|
914
|
-
${getRuntimeInitBootstrapCode()}
|
|
705
|
+
${getRuntimeInitBootstrapCode(enableSsrInit)}
|
|
915
706
|
${exportStatement}
|
|
916
707
|
`);
|
|
917
708
|
}
|
|
@@ -946,10 +737,10 @@ function isValidEsmExportName(name) {
|
|
|
946
737
|
return !!name && name !== "default" && name !== "__esModule" && isValidJsIdentifier(name);
|
|
947
738
|
}
|
|
948
739
|
const JS_IDENTIFIER_PATTERN = `[\$_\\p{ID_Start}][\$_\\u200C\\u200D\\p{ID_Continue}]*`;
|
|
949
|
-
const localRequire = createRequire
|
|
740
|
+
const localRequire = createRequire(import.meta.url);
|
|
950
741
|
function resolvePackageEntryFromProjectRoot(pkg) {
|
|
951
742
|
try {
|
|
952
|
-
return createRequire
|
|
743
|
+
return createRequire(new URL(`file://${path.join(getPackageDetectionCwd(), "package.json")}`)).resolve(pkg);
|
|
953
744
|
} catch {
|
|
954
745
|
return;
|
|
955
746
|
}
|
|
@@ -1047,7 +838,7 @@ function getNamedExportsViaRegex(source, filePath, visited) {
|
|
|
1047
838
|
}
|
|
1048
839
|
function getPackageNamedExports(pkg) {
|
|
1049
840
|
try {
|
|
1050
|
-
const mod = createRequire
|
|
841
|
+
const mod = createRequire(new URL(`file://${path.join(getPackageDetectionCwd(), "package.json")}`))(pkg);
|
|
1051
842
|
return Object.keys(mod).filter((k) => isValidEsmExportName(k));
|
|
1052
843
|
} catch {
|
|
1053
844
|
return getEsmNamedExports(pkg);
|
|
@@ -1055,7 +846,7 @@ function getPackageNamedExports(pkg) {
|
|
|
1055
846
|
}
|
|
1056
847
|
function getLocalProviderImportPath(pkg) {
|
|
1057
848
|
try {
|
|
1058
|
-
const resolved = createRequire
|
|
849
|
+
const resolved = createRequire(new URL(`file://${path.join(getPackageDetectionCwd(), "package.json")}`)).resolve(pkg);
|
|
1059
850
|
return isWorkspaceFilePath(resolved) ? resolved : void 0;
|
|
1060
851
|
} catch {
|
|
1061
852
|
const resolved = getInstalledPackageEntry(pkg, {
|
|
@@ -1076,7 +867,7 @@ function getProjectResolvedImportPath(pkg) {
|
|
|
1076
867
|
if (esmEntry) return esmEntry;
|
|
1077
868
|
}
|
|
1078
869
|
try {
|
|
1079
|
-
return createRequire
|
|
870
|
+
return createRequire(new URL(`file://${path.join(getPackageDetectionCwd(), "package.json")}`)).resolve(pkg);
|
|
1080
871
|
} catch {
|
|
1081
872
|
return;
|
|
1082
873
|
}
|
|
@@ -1098,7 +889,7 @@ function isWorkspacePackageEntry(pkg, resolved) {
|
|
|
1098
889
|
}
|
|
1099
890
|
function tryResolveImportFromPackageRoot(pkg, root) {
|
|
1100
891
|
try {
|
|
1101
|
-
return createRequire
|
|
892
|
+
return createRequire(new URL(`file://${path.join(root, "package.json")}`)).resolve(pkg);
|
|
1102
893
|
} catch {
|
|
1103
894
|
return;
|
|
1104
895
|
}
|
|
@@ -1451,7 +1242,7 @@ const normalizeRuntimeShareCode = `const __mfNormalizeRuntimeShare = (mod) => {
|
|
|
1451
1242
|
let current = mod;
|
|
1452
1243
|
for (let i = 0; i < 5; i++) {
|
|
1453
1244
|
const defaultExport = current?.default;
|
|
1454
|
-
if (!defaultExport || typeof defaultExport !== "object") break;
|
|
1245
|
+
if (!defaultExport || typeof defaultExport !== "object" || Object.keys(defaultExport).length === 0) break;
|
|
1455
1246
|
const namedValues = Object.keys(current).filter((key) => key !== "default").map((key) => current[key]);
|
|
1456
1247
|
if (namedValues.length > 0 && namedValues.some((value) => value !== undefined)) break;
|
|
1457
1248
|
current = defaultExport;
|
|
@@ -1491,6 +1282,9 @@ const REMOTE_ENTRY_ID = "virtual:mf-REMOTE_ENTRY_ID";
|
|
|
1491
1282
|
function getRemoteEntryId(options) {
|
|
1492
1283
|
return `${REMOTE_ENTRY_ID}:${`${options.internalName}__${options.filename}`.replace(/[^a-zA-Z0-9_-]/g, "_")}`;
|
|
1493
1284
|
}
|
|
1285
|
+
const SSR_ONLY_PLUGIN_SPECIFIERS = new Set(["@module-federation/vite/ssrEntryLoader"]);
|
|
1286
|
+
const isSsrOnlyPlugin = (importStatement) => [...SSR_ONLY_PLUGIN_SPECIFIERS].some((s) => importStatement.includes(s));
|
|
1287
|
+
const getSsrOnlyPluginSpecifier = (importStatement) => [...SSR_ONLY_PLUGIN_SPECIFIERS].find((s) => importStatement.includes(s));
|
|
1494
1288
|
function generateRemoteEntry(options, virtualExposesId = getVirtualExposesId(options), command = "build") {
|
|
1495
1289
|
const pluginImportNames = options.runtimePlugins.map((p, i) => {
|
|
1496
1290
|
if (typeof p === "string") return [
|
|
@@ -1514,7 +1308,7 @@ function generateRemoteEntry(options, virtualExposesId = getVirtualExposesId(opt
|
|
|
1514
1308
|
globalThis.__VUE_HMR_RUNTIME__ = { createRecord() {}, rerender() {}, reload() {} };
|
|
1515
1309
|
}
|
|
1516
1310
|
import {init as runtimeInit, loadRemote} from "@module-federation/runtime";
|
|
1517
|
-
${pluginImportNames.map((item) => item[1]).join("\n")}
|
|
1311
|
+
${pluginImportNames.filter((item) => !isSsrOnlyPlugin(item[1])).map((item) => item[1]).join("\n")}
|
|
1518
1312
|
${command === "build" ? getRuntimeInitResolveBootstrapCode() : getRuntimeInitBootstrapCode() + "\n const { initResolve } = globalThis[globalKey];"}
|
|
1519
1313
|
${getRuntimeModuleCacheBootstrapCode()}
|
|
1520
1314
|
const initTokens = {}
|
|
@@ -1562,11 +1356,19 @@ function generateRemoteEntry(options, virtualExposesId = getVirtualExposesId(opt
|
|
|
1562
1356
|
async function init(shared = {}, initScope = []) {
|
|
1563
1357
|
const {usedShared, usedRemotes} = await getLocalSharedImportMap()
|
|
1564
1358
|
${generateDirectSharedCacheSeedCode(command)}
|
|
1359
|
+
const __browserPlugins = [${pluginImportNames.filter((item) => !isSsrOnlyPlugin(item[1])).map((item) => `${item[0]}(${item[2]})`).join(", ")}];
|
|
1360
|
+
const __ssrPlugins = typeof globalThis.window === 'undefined'
|
|
1361
|
+
? await Promise.all([${pluginImportNames.filter((item) => isSsrOnlyPlugin(item[1])).map((item) => {
|
|
1362
|
+
const specifier = getSsrOnlyPluginSpecifier(item[1]);
|
|
1363
|
+
const opts = item[2];
|
|
1364
|
+
return `import(${JSON.stringify(specifier)}).then(m => (m.default ?? m)(${opts}))`;
|
|
1365
|
+
}).join(", ")}])
|
|
1366
|
+
: [];
|
|
1565
1367
|
const initRes = runtimeInit({
|
|
1566
1368
|
name: mfName,
|
|
1567
|
-
remotes: usedRemotes,
|
|
1369
|
+
remotes: ${options.shareStrategy === "loaded-first" ? "[]" : "usedRemotes"},
|
|
1568
1370
|
shared: usedShared,
|
|
1569
|
-
plugins: [
|
|
1371
|
+
plugins: [...__browserPlugins, ...__ssrPlugins],
|
|
1570
1372
|
${options.shareStrategy ? `shareStrategy: '${options.shareStrategy}'` : ""}
|
|
1571
1373
|
});
|
|
1572
1374
|
// handling circular init calls
|
|
@@ -1617,6 +1419,7 @@ const hostAutoInitModule = new VirtualModule("hostAutoInit", "__H_A_I__");
|
|
|
1617
1419
|
let currentHostAutoInitRemoteEntryId = REMOTE_ENTRY_ID;
|
|
1618
1420
|
let currentHostAutoInitCommand = "build";
|
|
1619
1421
|
function generateHostAutoInitCode(remoteEntryImport, _command = "build") {
|
|
1422
|
+
const shouldPreloadShares = getNormalizeModuleFederationOptions().shareStrategy !== "loaded-first";
|
|
1620
1423
|
return `
|
|
1621
1424
|
${getRuntimeModuleCacheBootstrapCode()}
|
|
1622
1425
|
let hostInitPromise;
|
|
@@ -1628,6 +1431,7 @@ function generateHostAutoInitCode(remoteEntryImport, _command = "build") {
|
|
|
1628
1431
|
const runtime = await remoteEntry.init();
|
|
1629
1432
|
const usedShared = ${generateUsedSharedPreloadConfig()};
|
|
1630
1433
|
${normalizeRuntimeShareCode}
|
|
1434
|
+
${shouldPreloadShares ? `
|
|
1631
1435
|
for (const [pkg, share] of Object.entries(usedShared)) {
|
|
1632
1436
|
if (__mfModuleCache.share[pkg] !== undefined) {
|
|
1633
1437
|
continue;
|
|
@@ -1641,6 +1445,7 @@ function generateHostAutoInitCode(remoteEntryImport, _command = "build") {
|
|
|
1641
1445
|
});
|
|
1642
1446
|
});
|
|
1643
1447
|
}
|
|
1448
|
+
` : ""}
|
|
1644
1449
|
const __mfRemotePreloads = [];
|
|
1645
1450
|
await Promise.all(__mfRemotePreloads);
|
|
1646
1451
|
return runtime;
|
|
@@ -1672,12 +1477,13 @@ function getHostAutoInitPath() {
|
|
|
1672
1477
|
//#region src/virtualModules/virtualRemotes.ts
|
|
1673
1478
|
const cacheRemoteMap = {};
|
|
1674
1479
|
const LOAD_REMOTE_TAG = "__loadRemote__";
|
|
1675
|
-
function getRemoteVirtualModule(remote, command) {
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
cacheRemoteMap[
|
|
1480
|
+
function getRemoteVirtualModule(remote, command, enableSsrInit = false) {
|
|
1481
|
+
const cacheKey = `${remote}__${command}__${enableSsrInit ? "ssr" : "no-ssr"}`;
|
|
1482
|
+
if (!cacheRemoteMap[cacheKey]) {
|
|
1483
|
+
cacheRemoteMap[cacheKey] = new VirtualModule(remote, LOAD_REMOTE_TAG, ".mjs");
|
|
1484
|
+
cacheRemoteMap[cacheKey].writeSync(generateRemotes(remote, command, enableSsrInit));
|
|
1679
1485
|
}
|
|
1680
|
-
return cacheRemoteMap[
|
|
1486
|
+
return cacheRemoteMap[cacheKey];
|
|
1681
1487
|
}
|
|
1682
1488
|
const usedRemotesMap = {};
|
|
1683
1489
|
function addUsedRemote(remoteKey, remoteModule) {
|
|
@@ -1687,40 +1493,93 @@ function addUsedRemote(remoteKey, remoteModule) {
|
|
|
1687
1493
|
function getUsedRemotesMap() {
|
|
1688
1494
|
return usedRemotesMap;
|
|
1689
1495
|
}
|
|
1690
|
-
function
|
|
1691
|
-
const
|
|
1496
|
+
function getRemoteFromId(id, remotes) {
|
|
1497
|
+
const remoteName = Object.keys(remotes).filter((name) => id === name || id.startsWith(name + "/")).sort((a, b) => b.length - a.length)[0];
|
|
1498
|
+
return remoteName ? remotes[remoteName] : void 0;
|
|
1499
|
+
}
|
|
1500
|
+
function generateRemotes(id, command, enableSsrInit = false) {
|
|
1501
|
+
const useReactProxy = hasPackageDependency("react");
|
|
1502
|
+
const options = getNormalizeModuleFederationOptions();
|
|
1503
|
+
const isLoadedFirst = options.shareStrategy === "loaded-first";
|
|
1504
|
+
const remote = getRemoteFromId(id, options.remotes);
|
|
1505
|
+
const registerRemoteCode = isLoadedFirst && remote ? `runtime.registerRemotes([${JSON.stringify({
|
|
1506
|
+
entryGlobalName: remote.entryGlobalName,
|
|
1507
|
+
name: remote.name,
|
|
1508
|
+
type: remote.type,
|
|
1509
|
+
entry: remote.entry,
|
|
1510
|
+
shareScope: remote.shareScope ?? "default"
|
|
1511
|
+
})}]);` : "";
|
|
1692
1512
|
const reactImportLine = useReactProxy ? `import __mfReactDefault from "react";
|
|
1693
1513
|
import * as __mfReactNamespace from "react";
|
|
1694
1514
|
const __mfReact = __mfReactDefault ?? __mfReactNamespace.default ?? __mfReactNamespace;` : "";
|
|
1695
1515
|
const importLine = command === "build" ? `${getRuntimeModuleCacheBootstrapCode()}
|
|
1696
|
-
import { hostInitPromise as __mfHostInitPromise } from ${JSON.stringify(getHostAutoInitPath())};` : `${getRuntimeInitBootstrapCode()}
|
|
1697
|
-
const { initPromise, moduleCache: __mfModuleCache } = globalThis[globalKey]
|
|
1516
|
+
import { hostInitPromise as __mfHostInitPromise } from ${JSON.stringify(getHostAutoInitPath())};` : `${getRuntimeInitBootstrapCode(enableSsrInit)}
|
|
1517
|
+
const { initPromise, initResolve, initReject, moduleCache: __mfModuleCache } = globalThis[globalKey];
|
|
1518
|
+
if (typeof window !== "undefined") {
|
|
1519
|
+
import(${JSON.stringify(getHostAutoInitPath())})
|
|
1520
|
+
.then((mod) => mod.hostInitPromise)
|
|
1521
|
+
.then(initResolve, initReject);
|
|
1522
|
+
}`;
|
|
1698
1523
|
const exportLine = command === "serve" ? `if (__mfRemotePending) {
|
|
1699
|
-
|
|
1700
|
-
|
|
1524
|
+
__mfRemotePending = __mfRemotePending.then((mod) => {
|
|
1525
|
+
if (mod !== undefined) exportModule = mod;
|
|
1526
|
+
return exportModule;
|
|
1527
|
+
});
|
|
1701
1528
|
}
|
|
1702
|
-
export
|
|
1703
|
-
export const __mf_remote_pending = Promise.resolve(exportModule);
|
|
1529
|
+
export { exportModule as __moduleExports };
|
|
1530
|
+
export const __mf_remote_pending = __mfRemotePending || Promise.resolve(exportModule);
|
|
1704
1531
|
export default exportModule?.__mf_is_remote_proxy ? exportModule : exportModule?.__esModule ? exportModule.default : exportModule.default ?? exportModule` : command === "build" ? `if (__mfRemotePending) {
|
|
1705
|
-
|
|
1706
|
-
|
|
1532
|
+
__mfRemotePending = __mfRemotePending.then((mod) => {
|
|
1533
|
+
if (mod !== undefined) exportModule = mod;
|
|
1534
|
+
return exportModule;
|
|
1535
|
+
});
|
|
1707
1536
|
}
|
|
1708
|
-
export
|
|
1709
|
-
export const __mf_remote_pending = Promise.resolve(exportModule);
|
|
1537
|
+
export { exportModule as __moduleExports };
|
|
1538
|
+
export const __mf_remote_pending = __mfRemotePending || Promise.resolve(exportModule);
|
|
1710
1539
|
export default exportModule?.__mf_is_remote_proxy ? exportModule : exportModule?.__esModule ? exportModule.default : exportModule.default ?? exportModule` : "export default exportModule";
|
|
1540
|
+
const remoteLoadRuntimePromise = command === "build" ? "__mfHostInitPromise" : "initPromise";
|
|
1541
|
+
const remoteLoadFailureHandler = command === "build" ? `.catch((error) => {
|
|
1542
|
+
delete __mfModuleCache.remote[pendingKey];
|
|
1543
|
+
throw error;
|
|
1544
|
+
})` : `.catch(() => {
|
|
1545
|
+
delete __mfModuleCache.remote[pendingKey];
|
|
1546
|
+
})`;
|
|
1711
1547
|
return `
|
|
1712
1548
|
${reactImportLine}
|
|
1713
1549
|
${importLine}
|
|
1714
|
-
${
|
|
1550
|
+
${`
|
|
1551
|
+
function __mfStartRemoteLoad() {
|
|
1552
|
+
${`
|
|
1553
|
+
const pendingKey = ${JSON.stringify(`__mf_pending__${id}`)};
|
|
1554
|
+
if (!__mfModuleCache.remote[pendingKey]) {
|
|
1555
|
+
__mfModuleCache.remote[pendingKey] = ${remoteLoadRuntimePromise}
|
|
1556
|
+
.then((runtime) => {
|
|
1557
|
+
${registerRemoteCode}
|
|
1558
|
+
return runtime.loadRemote(${JSON.stringify(id)});
|
|
1559
|
+
})
|
|
1560
|
+
.then((mod) => {
|
|
1561
|
+
__mfModuleCache.remote[${JSON.stringify(id)}] = mod;
|
|
1562
|
+
delete __mfModuleCache.remote[pendingKey];
|
|
1563
|
+
return mod;
|
|
1564
|
+
})
|
|
1565
|
+
${remoteLoadFailureHandler};
|
|
1566
|
+
}
|
|
1567
|
+
return __mfModuleCache.remote[pendingKey];`}
|
|
1568
|
+
}
|
|
1715
1569
|
function __mfCreateRemoteProxy(pendingPromise) {
|
|
1716
1570
|
const listeners = new Set();
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1571
|
+
const ensurePending = () => {
|
|
1572
|
+
pendingPromise ||= __mfStartRemoteLoad();
|
|
1573
|
+
pendingPromise?.finally(() => {
|
|
1574
|
+
for (const listener of listeners) listener();
|
|
1575
|
+
});
|
|
1576
|
+
return pendingPromise;
|
|
1577
|
+
};
|
|
1720
1578
|
const getModule = () => __mfModuleCache.remote[${JSON.stringify(id)}];
|
|
1721
1579
|
const proxyTarget = function (...args) {
|
|
1722
1580
|
${useReactProxy ? `const [, setVersion] = __mfReact.useState(0);
|
|
1723
1581
|
__mfReact.useEffect(() => {
|
|
1582
|
+
ensurePending();
|
|
1724
1583
|
const listener = () => setVersion((value) => value + 1);
|
|
1725
1584
|
listeners.add(listener);
|
|
1726
1585
|
if (getModule()) listener();
|
|
@@ -1731,18 +1590,33 @@ export default exportModule?.__mf_is_remote_proxy ? exportModule : exportModule?
|
|
|
1731
1590
|
if (fn !== undefined && fn !== null) {
|
|
1732
1591
|
${useReactProxy ? `return __mfReact.createElement(fn, args[0]);` : `return fn.apply(this, args);`}
|
|
1733
1592
|
}
|
|
1734
|
-
${useReactProxy ? `return null;` : `throw
|
|
1593
|
+
${useReactProxy ? `return null;` : `throw ensurePending();`}
|
|
1735
1594
|
};
|
|
1736
1595
|
return new Proxy(proxyTarget, {
|
|
1737
1596
|
get(_target, prop) {
|
|
1738
1597
|
if (prop === "__mf_is_remote_proxy") return true;
|
|
1739
1598
|
if (prop === "__esModule") return true;
|
|
1740
1599
|
if (prop === "then") return undefined;
|
|
1600
|
+
// Allow React's dev-mode console.warn to stringify the proxy without
|
|
1601
|
+
// throwing "Cannot convert object to primitive value".
|
|
1602
|
+
if (prop === Symbol.toPrimitive || prop === "toString")
|
|
1603
|
+
return () => "[MF remote proxy: pending]";
|
|
1741
1604
|
const mod = getModule();
|
|
1742
1605
|
if (mod) {
|
|
1743
1606
|
return prop in mod ? mod[prop] : mod.default?.[prop];
|
|
1744
1607
|
}
|
|
1745
|
-
|
|
1608
|
+
// When the module is pending and React.lazy() checks for "default",
|
|
1609
|
+
// return the proxy function itself so React renders it (returns null)
|
|
1610
|
+
// rather than crashing on undefined.
|
|
1611
|
+
${useReactProxy ? `if (prop === "default") return proxyTarget;
|
|
1612
|
+
return undefined;` : `throw ensurePending();`}
|
|
1613
|
+
},
|
|
1614
|
+
has(_target, prop) {
|
|
1615
|
+
const mod = getModule();
|
|
1616
|
+
if (mod) return prop in mod;
|
|
1617
|
+
// Tell React that "default" exists when module is pending so it
|
|
1618
|
+
// doesn't warn "lazy: Expected the result of a dynamic import()".
|
|
1619
|
+
${useReactProxy ? `return prop === "default" || prop === "__esModule" || prop === "__mf_is_remote_proxy";` : `return false;`}
|
|
1746
1620
|
},
|
|
1747
1621
|
ownKeys() {
|
|
1748
1622
|
const mod = getModule();
|
|
@@ -1770,40 +1644,12 @@ export default exportModule?.__mf_is_remote_proxy ? exportModule : exportModule?
|
|
|
1770
1644
|
return target.apply(thisArg, args);
|
|
1771
1645
|
}
|
|
1772
1646
|
});
|
|
1773
|
-
}`
|
|
1647
|
+
}`}
|
|
1774
1648
|
let __mfRemotePending;
|
|
1775
1649
|
let exportModule = __mfModuleCache.remote[${JSON.stringify(id)}]
|
|
1776
1650
|
if (exportModule === undefined) {
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
__mfModuleCache.remote[pendingKey] = initPromise
|
|
1780
|
-
.then((runtime) => runtime.loadRemote(${JSON.stringify(id)}))
|
|
1781
|
-
.then((mod) => {
|
|
1782
|
-
__mfModuleCache.remote[${JSON.stringify(id)}] = mod;
|
|
1783
|
-
delete __mfModuleCache.remote[pendingKey];
|
|
1784
|
-
return mod;
|
|
1785
|
-
})
|
|
1786
|
-
.catch(() => {
|
|
1787
|
-
delete __mfModuleCache.remote[pendingKey];
|
|
1788
|
-
});
|
|
1789
|
-
}` : ""}
|
|
1790
|
-
${command !== "build" ? `__mfRemotePending = __mfModuleCache.remote[pendingKey];
|
|
1791
|
-
exportModule = __mfCreateRemoteProxy(__mfRemotePending);` : `const pendingKey = ${JSON.stringify(`__mf_pending__${id}`)};
|
|
1792
|
-
if (!__mfModuleCache.remote[pendingKey]) {
|
|
1793
|
-
__mfModuleCache.remote[pendingKey] = __mfHostInitPromise
|
|
1794
|
-
.then((runtime) => runtime.loadRemote(${JSON.stringify(id)}))
|
|
1795
|
-
.then((mod) => {
|
|
1796
|
-
__mfModuleCache.remote[${JSON.stringify(id)}] = mod;
|
|
1797
|
-
delete __mfModuleCache.remote[pendingKey];
|
|
1798
|
-
return mod;
|
|
1799
|
-
})
|
|
1800
|
-
.catch((error) => {
|
|
1801
|
-
delete __mfModuleCache.remote[pendingKey];
|
|
1802
|
-
throw error;
|
|
1803
|
-
});
|
|
1804
|
-
}
|
|
1805
|
-
__mfRemotePending = __mfModuleCache.remote[pendingKey];
|
|
1806
|
-
exportModule = {};`}
|
|
1651
|
+
__mfRemotePending = __mfStartRemoteLoad();
|
|
1652
|
+
exportModule = __mfCreateRemoteProxy(__mfRemotePending);
|
|
1807
1653
|
}
|
|
1808
1654
|
${exportLine}
|
|
1809
1655
|
`;
|
|
@@ -1813,7 +1659,38 @@ export default exportModule?.__mf_is_remote_proxy ? exportModule : exportModule?
|
|
|
1813
1659
|
function getFirstHtmlEntryFile(entryFiles) {
|
|
1814
1660
|
return entryFiles.find((file) => file.endsWith(".html"));
|
|
1815
1661
|
}
|
|
1816
|
-
|
|
1662
|
+
function stripQueryAndHash(file) {
|
|
1663
|
+
return file.split(/[?#]/)[0];
|
|
1664
|
+
}
|
|
1665
|
+
function getBuildInput(config) {
|
|
1666
|
+
return config.build?.rollupOptions?.input ?? config.build?.rolldownOptions?.input;
|
|
1667
|
+
}
|
|
1668
|
+
function patchHashEntryFileName(output, entryName, fileName) {
|
|
1669
|
+
const originalEntryFileNames = output.entryFileNames;
|
|
1670
|
+
output.entryFileNames = (chunkInfo, ...args) => {
|
|
1671
|
+
if (chunkInfo?.name === entryName) return fileName;
|
|
1672
|
+
if (typeof originalEntryFileNames === "function") return originalEntryFileNames(chunkInfo, ...args);
|
|
1673
|
+
return originalEntryFileNames || "assets/[name]-[hash].js";
|
|
1674
|
+
};
|
|
1675
|
+
}
|
|
1676
|
+
function patchHashEntryFileNames(config, entryName, fileName) {
|
|
1677
|
+
if (!fileName?.includes?.("[hash")) return;
|
|
1678
|
+
config.build ??= {};
|
|
1679
|
+
config.build.rollupOptions ??= {};
|
|
1680
|
+
config.build.rolldownOptions ??= {};
|
|
1681
|
+
const patchOutput = (output) => patchHashEntryFileName(output, entryName, fileName);
|
|
1682
|
+
const patchBundlerOutput = (bundlerOptions) => {
|
|
1683
|
+
const output = bundlerOptions.output;
|
|
1684
|
+
if (Array.isArray(output)) {
|
|
1685
|
+
output.forEach(patchOutput);
|
|
1686
|
+
return;
|
|
1687
|
+
}
|
|
1688
|
+
patchOutput(bundlerOptions.output ??= {});
|
|
1689
|
+
};
|
|
1690
|
+
patchBundlerOutput(config.build.rollupOptions);
|
|
1691
|
+
patchBundlerOutput(config.build.rolldownOptions);
|
|
1692
|
+
}
|
|
1693
|
+
const addEntry = ({ entryName, entryPath, fileName, inject = "entry", forceClientInjected, skipTransformFor = [] }) => {
|
|
1817
1694
|
const DEV_HTML_PROXY_PREFIX = "virtual:mf-html-entry-proxy?";
|
|
1818
1695
|
const ENTRY_BOOTSTRAP_QUERY = "?mf-entry-bootstrap";
|
|
1819
1696
|
const waitsForInit = entryName === "hostInit";
|
|
@@ -1826,6 +1703,7 @@ const addEntry = ({ entryName, entryPath, fileName, inject = "entry", forceClien
|
|
|
1826
1703
|
let viteConfig;
|
|
1827
1704
|
let clientInjected = forceClientInjected ?? false;
|
|
1828
1705
|
let emittedFileName;
|
|
1706
|
+
let skipTransformIds = /* @__PURE__ */ new Set();
|
|
1829
1707
|
function skipSvelteKitSsrBuild() {
|
|
1830
1708
|
return (_command === "build" || viteConfig?.command === "build") && viteConfig?.build?.ssr && hasPackageDependency("@sveltejs/kit");
|
|
1831
1709
|
}
|
|
@@ -1881,21 +1759,44 @@ const addEntry = ({ entryName, entryPath, fileName, inject = "entry", forceClien
|
|
|
1881
1759
|
return patched;
|
|
1882
1760
|
}
|
|
1883
1761
|
function getBootstrapSource(initSrc, entrySrc, useSystemImportFallback = false) {
|
|
1884
|
-
const remotePreloads = Object.entries(getUsedRemotesMap()).flatMap(([remoteKey, remotes]) => Array.from(remotes).filter((remote) => remote !== remoteKey)).sort().map((remote) => `runtime.loadRemote(${JSON.stringify(remote)})`).join(",");
|
|
1885
1762
|
const importHelper = useSystemImportFallback ? `const __mfImport = (src) =>
|
|
1886
1763
|
globalThis.System && typeof globalThis.System.import === 'function'
|
|
1887
1764
|
? globalThis.System.import(src)
|
|
1888
1765
|
: import(src);
|
|
1889
1766
|
` : "";
|
|
1890
1767
|
const importExpression = (src) => useSystemImportFallback ? `__mfImport(${JSON.stringify(src)})` : `import(${JSON.stringify(src)})`;
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
const { initHost } = await ${importExpression(initSrc)};
|
|
1768
|
+
const remotePreloads = getNormalizeModuleFederationOptions()?.shareStrategy !== "loaded-first" ? Object.entries(getUsedRemotesMap()).flatMap(([remoteKey, remotes]) => Array.from(remotes).filter((remote) => remote !== remoteKey)).sort().map((remote) => `__mfPreloadRemote(${JSON.stringify(remote)})`).join(",") : "";
|
|
1769
|
+
const preloadBlock = remotePreloads ? `
|
|
1894
1770
|
const runtime = await initHost();
|
|
1771
|
+
const __mfPreloadRemote = (remote) => {
|
|
1772
|
+
const pendingKey = "__mf_pending__" + remote;
|
|
1773
|
+
if (!__mfModuleCache.remote[pendingKey]) {
|
|
1774
|
+
__mfModuleCache.remote[pendingKey] = runtime.loadRemote(remote)
|
|
1775
|
+
.then((mod) => {
|
|
1776
|
+
__mfModuleCache.remote[remote] = mod;
|
|
1777
|
+
delete __mfModuleCache.remote[pendingKey];
|
|
1778
|
+
return mod;
|
|
1779
|
+
})
|
|
1780
|
+
.catch((error) => {
|
|
1781
|
+
delete __mfModuleCache.remote[pendingKey];
|
|
1782
|
+
throw error;
|
|
1783
|
+
});
|
|
1784
|
+
}
|
|
1785
|
+
return __mfModuleCache.remote[pendingKey];
|
|
1786
|
+
};
|
|
1895
1787
|
const __mfRemotePreloads = [${remotePreloads}];
|
|
1896
|
-
await Promise.
|
|
1788
|
+
await Promise.allSettled(__mfRemotePreloads);` : `await initHost();`;
|
|
1789
|
+
const importCode = `
|
|
1790
|
+
(async () => {
|
|
1791
|
+
const { initHost } = await ${importExpression(initSrc)};
|
|
1792
|
+
${preloadBlock}
|
|
1897
1793
|
})().then(() => ${importExpression(entrySrc)});
|
|
1898
1794
|
`;
|
|
1795
|
+
return [
|
|
1796
|
+
getRuntimeModuleCacheBootstrapCode(),
|
|
1797
|
+
importHelper,
|
|
1798
|
+
importCode
|
|
1799
|
+
].join("\n");
|
|
1899
1800
|
}
|
|
1900
1801
|
function getSystemBootstrapSource(initSrc, entrySrc) {
|
|
1901
1802
|
return getBootstrapSource(initSrc, entrySrc, true);
|
|
@@ -1910,6 +1811,29 @@ ${importHelper}(async () => {
|
|
|
1910
1811
|
function normalizeDevHtmlProxyId(id) {
|
|
1911
1812
|
return id.replace(/^\0/, "").replace(/^\/@id\//, "").replace(/^__x00__/, "");
|
|
1912
1813
|
}
|
|
1814
|
+
function normalizeModuleId(id) {
|
|
1815
|
+
return id.split("?")[0].replace(/\\/g, "/");
|
|
1816
|
+
}
|
|
1817
|
+
function resolveProjectId(id) {
|
|
1818
|
+
if (id.startsWith("\0") || id.startsWith("virtual:")) return normalizeModuleId(id);
|
|
1819
|
+
return normalizeModuleId(path$1.isAbsolute(id) ? id : path$1.resolve(viteConfig.root, id));
|
|
1820
|
+
}
|
|
1821
|
+
function addEntryFile(file) {
|
|
1822
|
+
const normalized = normalizeModuleId(file);
|
|
1823
|
+
if (!entryFiles.includes(normalized)) entryFiles.push(normalized);
|
|
1824
|
+
}
|
|
1825
|
+
function addHtmlScriptEntries(htmlPath) {
|
|
1826
|
+
if (!fs$1.existsSync(htmlPath)) return;
|
|
1827
|
+
const htmlContent = fs$1.readFileSync(htmlPath, "utf-8");
|
|
1828
|
+
const scriptRegex = /<script\b(?=[^>]*\btype=["']module["'])(?=[^>]*\bsrc=["']([^"']+)["'])[^>]*>/gi;
|
|
1829
|
+
let match;
|
|
1830
|
+
while ((match = scriptRegex.exec(htmlContent)) !== null) {
|
|
1831
|
+
const scriptSrc = stripQueryAndHash(match[1]);
|
|
1832
|
+
if (/^(?:[a-z]+:)?\/\//i.test(scriptSrc)) continue;
|
|
1833
|
+
addEntryFile(scriptSrc);
|
|
1834
|
+
addEntryFile(scriptSrc.startsWith("/") ? path$1.resolve(viteConfig.root, scriptSrc.slice(1)) : path$1.resolve(path$1.dirname(htmlPath), scriptSrc));
|
|
1835
|
+
}
|
|
1836
|
+
}
|
|
1913
1837
|
return [{
|
|
1914
1838
|
name: "add-entry",
|
|
1915
1839
|
apply: "serve",
|
|
@@ -1926,6 +1850,7 @@ ${importHelper}(async () => {
|
|
|
1926
1850
|
const relativePath = normalized.startsWith(root + "/") ? normalized.slice(root.length) : "/" + normalized.replace(/^[A-Za-z]:[\\/]/, "");
|
|
1927
1851
|
devEntryPath = config.base + relativePath.replace(/^\//, "");
|
|
1928
1852
|
}
|
|
1853
|
+
skipTransformIds = new Set(skipTransformFor.map(resolveProjectId));
|
|
1929
1854
|
},
|
|
1930
1855
|
configureServer(server) {
|
|
1931
1856
|
server.middlewares.use((req, res, next) => {
|
|
@@ -1953,7 +1878,8 @@ ${importHelper}(async () => {
|
|
|
1953
1878
|
transformIndexHtml: {
|
|
1954
1879
|
order: "pre",
|
|
1955
1880
|
handler(c) {
|
|
1956
|
-
|
|
1881
|
+
const shouldWrapEntryHtml = _command === "serve" && inject === "entry" && waitsForInit;
|
|
1882
|
+
if (!injectHtml() && !shouldWrapEntryHtml) return;
|
|
1957
1883
|
clientInjected = true;
|
|
1958
1884
|
const base = viteConfig.base.replace(/\/$/, "");
|
|
1959
1885
|
const stripBase = (p) => base && p.startsWith(base + "/") ? p.slice(base.length) : p;
|
|
@@ -1985,24 +1911,30 @@ ${importHelper}(async () => {
|
|
|
1985
1911
|
}, {
|
|
1986
1912
|
name: "add-entry",
|
|
1987
1913
|
enforce: "post",
|
|
1914
|
+
applyToEnvironment() {
|
|
1915
|
+
return true;
|
|
1916
|
+
},
|
|
1917
|
+
config(config) {
|
|
1918
|
+
patchHashEntryFileNames(config, entryName, fileName);
|
|
1919
|
+
},
|
|
1988
1920
|
configResolved(config) {
|
|
1989
1921
|
viteConfig = config;
|
|
1990
|
-
|
|
1922
|
+
skipTransformIds = new Set(skipTransformFor.map(resolveProjectId));
|
|
1923
|
+
const ctx = this;
|
|
1924
|
+
const envName = ctx != null && typeof ctx === "object" ? ctx["environment"] : void 0;
|
|
1925
|
+
if (envName?.name && envName.name !== "client") return;
|
|
1926
|
+
const inputOptions = getBuildInput(config);
|
|
1991
1927
|
if (!inputOptions) htmlFilePath = path$1.resolve(config.root, "index.html");
|
|
1992
|
-
else if (typeof inputOptions === "string") entryFiles = [inputOptions];
|
|
1993
|
-
else if (Array.isArray(inputOptions)) entryFiles = inputOptions;
|
|
1994
|
-
else if (typeof inputOptions === "object") entryFiles = Object.values(inputOptions);
|
|
1928
|
+
else if (typeof inputOptions === "string") entryFiles = [normalizeModuleId(inputOptions)];
|
|
1929
|
+
else if (Array.isArray(inputOptions)) entryFiles = inputOptions.map(normalizeModuleId);
|
|
1930
|
+
else if (typeof inputOptions === "object") entryFiles = Object.values(inputOptions).map((input) => normalizeModuleId(String(input)));
|
|
1995
1931
|
if (entryFiles.length > 0) htmlFilePath = getFirstHtmlEntryFile(entryFiles);
|
|
1996
|
-
if (
|
|
1997
|
-
const htmlContent = fs$1.readFileSync(htmlFilePath, "utf-8");
|
|
1998
|
-
const scriptRegex = /<script\s+[^>]*src=["']([^"']+)["'][^>]*>/gi;
|
|
1999
|
-
let match;
|
|
2000
|
-
while ((match = scriptRegex.exec(htmlContent)) !== null) entryFiles.push(match[1]);
|
|
2001
|
-
}
|
|
1932
|
+
if (htmlFilePath) addHtmlScriptEntries(htmlFilePath);
|
|
2002
1933
|
},
|
|
2003
1934
|
buildStart() {
|
|
2004
1935
|
if (_command === "serve") return;
|
|
2005
1936
|
if (skipSvelteKitSsrBuild()) return;
|
|
1937
|
+
if (this.environment?.name === "ssr") return;
|
|
2006
1938
|
const hasHash = fileName?.includes?.("[hash");
|
|
2007
1939
|
const emitFileOptions = {
|
|
2008
1940
|
name: entryName,
|
|
@@ -2012,16 +1944,14 @@ ${importHelper}(async () => {
|
|
|
2012
1944
|
};
|
|
2013
1945
|
if (!hasHash) emitFileOptions.fileName = fileName;
|
|
2014
1946
|
emitFileId = this.emitFile(emitFileOptions);
|
|
2015
|
-
if (htmlFilePath
|
|
2016
|
-
const htmlContent = fs$1.readFileSync(htmlFilePath, "utf-8");
|
|
2017
|
-
const scriptRegex = /<script\s+[^>]*src=["']([^"']+)["'][^>]*>/gi;
|
|
2018
|
-
let match;
|
|
2019
|
-
while ((match = scriptRegex.exec(htmlContent)) !== null) entryFiles.push(match[1]);
|
|
2020
|
-
}
|
|
1947
|
+
if (htmlFilePath) addHtmlScriptEntries(htmlFilePath);
|
|
2021
1948
|
},
|
|
2022
1949
|
generateBundle(_options, bundle) {
|
|
2023
1950
|
if (skipSvelteKitSsrBuild()) return;
|
|
2024
1951
|
if (!injectHtml()) return;
|
|
1952
|
+
if (!emitFileId) return;
|
|
1953
|
+
const htmlFileNames = Object.keys(bundle).filter((fileName) => fileName.endsWith(".html"));
|
|
1954
|
+
if (htmlFileNames.length === 0) return;
|
|
2025
1955
|
const file = this.getFileName(emitFileId);
|
|
2026
1956
|
emittedFileName = file;
|
|
2027
1957
|
const resolvePath = (htmlFileName) => {
|
|
@@ -2043,7 +1973,7 @@ ${importHelper}(async () => {
|
|
|
2043
1973
|
return viteConfig.base + file;
|
|
2044
1974
|
};
|
|
2045
1975
|
let bootstrapIndex = 0;
|
|
2046
|
-
for (const fileName
|
|
1976
|
+
for (const fileName of htmlFileNames) {
|
|
2047
1977
|
let htmlAsset = bundle[fileName];
|
|
2048
1978
|
if (htmlAsset.type === "chunk") return;
|
|
2049
1979
|
let htmlContent = htmlAsset.source.toString() || "";
|
|
@@ -2088,6 +2018,11 @@ ${importHelper}(async () => {
|
|
|
2088
2018
|
if (skipSvelteKitSsrBuild()) return;
|
|
2089
2019
|
if (isSvelteKitServerModule(id)) return;
|
|
2090
2020
|
if (id.includes(ENTRY_BOOTSTRAP_QUERY)) return;
|
|
2021
|
+
if (normalizeModuleId(id).endsWith(".html")) return;
|
|
2022
|
+
if (skipTransformIds.has(resolveProjectId(id))) return;
|
|
2023
|
+
const transformCtx = this;
|
|
2024
|
+
const transformEnv = transformCtx != null && typeof transformCtx === "object" ? transformCtx["environment"] : void 0;
|
|
2025
|
+
if (transformEnv?.name && transformEnv.name !== "client") return;
|
|
2091
2026
|
const isVinext = hasPackageDependency("vinext");
|
|
2092
2027
|
if (isVinext && inject === "html" && id.includes("virtual:vite-rsc/remove-duplicate-server-css")) {
|
|
2093
2028
|
const namespaceReactImport = `import * as React from 'react';`;
|
|
@@ -2104,9 +2039,16 @@ ${importHelper}(async () => {
|
|
|
2104
2039
|
clientInjected = true;
|
|
2105
2040
|
return mapCodeToCodeWithSourcemap(injection + code);
|
|
2106
2041
|
}
|
|
2107
|
-
if (
|
|
2042
|
+
if (_command === "serve" && inject === "entry" && waitsForInit && !clientInjected && /(?:^|\/)nuxt\/dist\/app\/entry\.js(?:\?|$)/.test(id) && code.includes("vueApp.mount(vueAppRootContainer);")) {
|
|
2043
|
+
clientInjected = true;
|
|
2044
|
+
const injection = `await import(${JSON.stringify(getEntryPath())}).then(({ initHost }) => initHost());\n `;
|
|
2045
|
+
return mapCodeToCodeWithSourcemap(code.replace("vueApp.mount(vueAppRootContainer);", `${injection}vueApp.mount(vueAppRootContainer);`));
|
|
2046
|
+
}
|
|
2047
|
+
const isHydrationEntryFallback = inject === "entry" && entryFiles.length === 0 && (!htmlFilePath || !fs$1.existsSync(htmlFilePath)) && !clientInjected && !id.includes("node_modules") && (id.startsWith("\0") || /\.(js|ts|mjs|vue|jsx|tsx)(\?|$)/.test(id)) && /hydrateRoot|createRoot|ReactDOM\.render/.test(code);
|
|
2048
|
+
const isNuxtClientEntryFallback = _command === "serve" && inject === "entry" && entryFiles.length === 0 && (!htmlFilePath || !fs$1.existsSync(htmlFilePath)) && !clientInjected && !id.includes("node_modules/.vite") && /(?:^|\/)nuxt\/dist\/app\/entry\.async\.js(?:\?|$)/.test(id) && code.includes("entry();");
|
|
2049
|
+
if (injectEntry() && entryFiles.some((file) => resolveProjectId(id) === file) || _command === "serve" && inject === "html" && !isVinext && !clientInjected && !id.startsWith("\0") && !id.includes("node_modules") && /\.(js|ts|mjs|vue|jsx|tsx)(\?|$)/.test(id) || isHydrationEntryFallback || isNuxtClientEntryFallback) {
|
|
2108
2050
|
clientInjected = true;
|
|
2109
|
-
if (!waitsForInit) return mapCodeToCodeWithSourcemap(`import ${JSON.stringify(getEntryPath())};\n` + code);
|
|
2051
|
+
if (!waitsForInit || _command === "serve" && inject === "entry" && isHydrationEntryFallback) return mapCodeToCodeWithSourcemap(`import ${JSON.stringify(getEntryPath())};\n` + code);
|
|
2110
2052
|
const entrySrc = id.includes("?") ? `${id}&${ENTRY_BOOTSTRAP_QUERY.slice(1)}` : `${id}${ENTRY_BOOTSTRAP_QUERY}`;
|
|
2111
2053
|
return mapCodeToCodeWithSourcemap(getBootstrapSource(getEntryPath(), entrySrc));
|
|
2112
2054
|
}
|
|
@@ -2158,11 +2100,7 @@ function checkAliasConflicts(options) {
|
|
|
2158
2100
|
};
|
|
2159
2101
|
}
|
|
2160
2102
|
//#endregion
|
|
2161
|
-
//#region src/plugins/
|
|
2162
|
-
const REMOTE_HMR_ENDPOINT = "__mf_hmr";
|
|
2163
|
-
const REMOTE_HMR_EVENT = "mf:remote-update";
|
|
2164
|
-
const REMOTE_HMR_CONNECT_RETRY_DELAY_MS = 1e3;
|
|
2165
|
-
const REMOTE_HMR_CONNECT_MAX_RETRIES = 10;
|
|
2103
|
+
//#region src/plugins/hmr/react.ts
|
|
2166
2104
|
/**
|
|
2167
2105
|
* Proxy module served for `/@react-refresh` on MF remote dev servers.
|
|
2168
2106
|
* Delegates to the host page's RefreshRuntime instance via
|
|
@@ -2182,6 +2120,112 @@ const REACT_REFRESH_PROXY_MODULE = [
|
|
|
2182
2120
|
`export const __hmr_import = __rt.__hmr_import;`,
|
|
2183
2121
|
`export default __rt.default || __rt;`
|
|
2184
2122
|
].join("\n");
|
|
2123
|
+
const reactAdapter = {
|
|
2124
|
+
name: "react",
|
|
2125
|
+
pluginNames: ["vite:react-refresh", "vite:react-swc:refresh"],
|
|
2126
|
+
remote: { configureServer({ server }) {
|
|
2127
|
+
server.middlewares.use((req, res, next) => {
|
|
2128
|
+
if (req.url?.replace(/\?.*$/, "") !== "/@react-refresh") return next();
|
|
2129
|
+
res.setHeader("Content-Type", "application/javascript; charset=utf-8");
|
|
2130
|
+
res.setHeader("Access-Control-Allow-Origin", "*");
|
|
2131
|
+
res.end(REACT_REFRESH_PROXY_MODULE);
|
|
2132
|
+
});
|
|
2133
|
+
} }
|
|
2134
|
+
};
|
|
2135
|
+
//#endregion
|
|
2136
|
+
//#region src/plugins/hmr/vue.ts
|
|
2137
|
+
/**
|
|
2138
|
+
* In dev mode each Vite dev server serves its own copy of Vue
|
|
2139
|
+
* (`/node_modules/.vite/deps/vue.js`). When a remote module is loaded into the
|
|
2140
|
+
* host page, the remote's Vue copy evaluates and runs:
|
|
2141
|
+
*
|
|
2142
|
+
* globalThis.__VUE_HMR_RUNTIME__ = createHotReloadAPI()
|
|
2143
|
+
*
|
|
2144
|
+
* which silently overwrites the host's runtime. After that, `createRecord` for
|
|
2145
|
+
* host components lives in the orphaned first runtime, but `reload(hmrId, ...)`
|
|
2146
|
+
* goes through the second runtime — the lookup misses and HMR stops working.
|
|
2147
|
+
*
|
|
2148
|
+
* This guard pins `__VUE_HMR_RUNTIME__` to the first writer (the host's Vue)
|
|
2149
|
+
* via a property trap on `globalThis`. Subsequent writes from remote-side Vue
|
|
2150
|
+
* copies are silently dropped. Must execute before any Vue module loads —
|
|
2151
|
+
* injected as a plain (non-module) script at `head-prepend`.
|
|
2152
|
+
*
|
|
2153
|
+
* `singleton: true` in `shared` is not sufficient: in dev mode MF's share-scope
|
|
2154
|
+
* does not actually dedupe Vue across dev servers, so without this guard the
|
|
2155
|
+
* last-loaded copy wins.
|
|
2156
|
+
*/
|
|
2157
|
+
const VUE_HMR_RUNTIME_GUARD_SCRIPT = `
|
|
2158
|
+
(function () {
|
|
2159
|
+
var h = null;
|
|
2160
|
+
Object.defineProperty(globalThis, '__VUE_HMR_RUNTIME__', {
|
|
2161
|
+
get: function () { return h; },
|
|
2162
|
+
set: function (v) { if (h === null) h = v; },
|
|
2163
|
+
configurable: true,
|
|
2164
|
+
enumerable: true,
|
|
2165
|
+
});
|
|
2166
|
+
})();`;
|
|
2167
|
+
/**
|
|
2168
|
+
* `@vitejs/plugin-vue` derives an SFC's `__hmrId` from a hash of the file path
|
|
2169
|
+
* relative to Vite's `root`. With module federation, host and remote are
|
|
2170
|
+
* separate Vite projects with independent roots — so an SFC at `src/App.vue`
|
|
2171
|
+
* in both will hash to the same id. Once both copies of Vue collapse onto the
|
|
2172
|
+
* shared `__VUE_HMR_RUNTIME__` (see the guard above), the host's instance and
|
|
2173
|
+
* the remote's instance both register under that single id. A remote-only
|
|
2174
|
+
* file change then calls `rerender(id, newRender)`, which iterates *every*
|
|
2175
|
+
* instance under that id — including the host one — and applies the remote's
|
|
2176
|
+
* render function to the host instance. The host's `setupState` doesn't have
|
|
2177
|
+
* the remote's bindings, so the render throws and Vue falls back to a full
|
|
2178
|
+
* reload required warning.
|
|
2179
|
+
*
|
|
2180
|
+
* Fix: rewrite the remote's emitted `__hmrId` literal to be prefixed with the
|
|
2181
|
+
* federation `name`, so the remote's instances live under a distinct key. The
|
|
2182
|
+
* accept callback emitted by plugin-vue reads `_sfc_main.__hmrId` /
|
|
2183
|
+
* `updated.__hmrId` at runtime, so rewriting the literal once is enough.
|
|
2184
|
+
*/
|
|
2185
|
+
const SFC_HMR_ID_LITERAL_RE = /(\.__hmrId\s*=\s*["'`])([^"'`]+)(["'`])/g;
|
|
2186
|
+
function rewriteSfcHmrId(code, federationName) {
|
|
2187
|
+
let matched = false;
|
|
2188
|
+
return {
|
|
2189
|
+
code: code.replace(SFC_HMR_ID_LITERAL_RE, (_match, prefix, id, suffix) => {
|
|
2190
|
+
matched = true;
|
|
2191
|
+
if (id.startsWith(`${federationName}-`)) return `${prefix}${id}${suffix}`;
|
|
2192
|
+
return `${prefix}${federationName}-${id}${suffix}`;
|
|
2193
|
+
}),
|
|
2194
|
+
matched
|
|
2195
|
+
};
|
|
2196
|
+
}
|
|
2197
|
+
let pluginVueRegressionWarned = false;
|
|
2198
|
+
function warnPluginVueRegression() {
|
|
2199
|
+
if (pluginVueRegressionWarned) return;
|
|
2200
|
+
pluginVueRegressionWarned = true;
|
|
2201
|
+
mfWarn("Detected a Vue SFC module that calls `__VUE_HMR_RUNTIME__.createRecord(` but no `.__hmrId = \"...\"` literal could be rewritten. @vitejs/plugin-vue may have changed its output format — without the rewrite, host and remote SFCs that share a path will collide on the shared HMR runtime. Please report this to @module-federation/vite.");
|
|
2202
|
+
}
|
|
2203
|
+
const vueAdapter = {
|
|
2204
|
+
name: "vue",
|
|
2205
|
+
pluginNames: ["vite:vue", "vite:vue-jsx"],
|
|
2206
|
+
host: { transformIndexHtml() {
|
|
2207
|
+
return [{
|
|
2208
|
+
tag: "script",
|
|
2209
|
+
children: VUE_HMR_RUNTIME_GUARD_SCRIPT,
|
|
2210
|
+
injectTo: "head-prepend"
|
|
2211
|
+
}];
|
|
2212
|
+
} },
|
|
2213
|
+
remote: { transform(code, _id, ctx) {
|
|
2214
|
+
if (!code.includes("__VUE_HMR_RUNTIME__.createRecord(")) return;
|
|
2215
|
+
const { code: rewritten, matched } = rewriteSfcHmrId(code, ctx.options.name);
|
|
2216
|
+
if (!matched) {
|
|
2217
|
+
warnPluginVueRegression();
|
|
2218
|
+
return;
|
|
2219
|
+
}
|
|
2220
|
+
return rewritten === code ? void 0 : rewritten;
|
|
2221
|
+
} }
|
|
2222
|
+
};
|
|
2223
|
+
//#endregion
|
|
2224
|
+
//#region src/plugins/hmr/fullReload.ts
|
|
2225
|
+
const REMOTE_HMR_ENDPOINT = "__mf_hmr";
|
|
2226
|
+
const REMOTE_HMR_EVENT = "mf:remote-update";
|
|
2227
|
+
const REMOTE_HMR_CONNECT_RETRY_DELAY_MS = 1e3;
|
|
2228
|
+
const REMOTE_HMR_CONNECT_MAX_RETRIES = 10;
|
|
2185
2229
|
function getBasePath$1(base) {
|
|
2186
2230
|
if (!base) return "/";
|
|
2187
2231
|
if (base.startsWith("http://") || base.startsWith("https://")) try {
|
|
@@ -2200,9 +2244,6 @@ function getHmrWsPath(base, hmrPath) {
|
|
|
2200
2244
|
if (!normalizedPath || normalizedPath === "/") return normalizedBase;
|
|
2201
2245
|
return `${normalizedBase.endsWith("/") ? normalizedBase.slice(0, -1) : normalizedBase}/${normalizedPath.startsWith("/") ? normalizedPath.slice(1) : normalizedPath}`;
|
|
2202
2246
|
}
|
|
2203
|
-
function shouldIgnoreFile(file, options) {
|
|
2204
|
-
return file.includes("/node_modules/") || file.includes("\\node_modules\\") || file.includes(`/${options.virtualModuleDir}/`) || file.includes(`\\${options.virtualModuleDir}\\`) || file.includes("/.vite/") || file.includes("\\.vite\\") || file.includes("/.mf/") || file.includes("\\.mf\\") || file.includes("/mf-manifest.json") || file.includes("\\mf-manifest.json") || file.includes("/mf-stats.json") || file.includes("\\mf-stats.json");
|
|
2205
|
-
}
|
|
2206
2247
|
function getRemoteHmrWsUrl(server) {
|
|
2207
2248
|
const hmr = server.config.server.hmr;
|
|
2208
2249
|
return `${hmr && typeof hmr === "object" && hmr.protocol ? hmr.protocol : server.config.server.https ? "wss" : "ws"}://${hmr && typeof hmr === "object" && hmr.host ? hmr.host : typeof server.config.server.host === "string" && server.config.server.host !== "0.0.0.0" ? server.config.server.host : "localhost"}:${hmr && typeof hmr === "object" && (hmr.clientPort || hmr.port) ? hmr.clientPort || hmr.port : server.config.server.port}${getHmrWsPath(server.config.base, hmr && typeof hmr === "object" ? hmr.path : "")}?token=${server.config.webSocketToken}`;
|
|
@@ -2241,466 +2282,275 @@ function getStringPreview(value, max = 180) {
|
|
|
2241
2282
|
} catch {}
|
|
2242
2283
|
return rawValue.slice(0, max);
|
|
2243
2284
|
}
|
|
2244
|
-
|
|
2245
|
-
|
|
2285
|
+
/**
|
|
2286
|
+
* Installs the `/__mf_hmr` metadata endpoint on a remote dev server. The host
|
|
2287
|
+
* fetches this to discover the remote's HMR WebSocket URL before opening a
|
|
2288
|
+
* Node-to-Node relay socket. Always installed on remotes when `remoteHmr` is
|
|
2289
|
+
* enabled — under `'native'` strategy the endpoint is unused but harmless;
|
|
2290
|
+
* under `'full-reload'` it's the discovery hop for the host relay.
|
|
2291
|
+
*/
|
|
2292
|
+
function setupRemoteMetadataEndpoint(server, options) {
|
|
2293
|
+
const endpointPath = getRemoteHmrPath(server.config.base);
|
|
2294
|
+
const wsUrl = getRemoteHmrWsUrl(server);
|
|
2295
|
+
server.middlewares.use((req, res, next) => {
|
|
2296
|
+
if (req.url?.replace(/\?.*/, "") !== endpointPath) {
|
|
2297
|
+
next();
|
|
2298
|
+
return;
|
|
2299
|
+
}
|
|
2300
|
+
res.setHeader("Content-Type", "application/json");
|
|
2301
|
+
res.setHeader("Access-Control-Allow-Origin", "*");
|
|
2302
|
+
res.end(JSON.stringify({
|
|
2303
|
+
remote: options.name,
|
|
2304
|
+
event: REMOTE_HMR_EVENT,
|
|
2305
|
+
wsUrl
|
|
2306
|
+
}));
|
|
2307
|
+
});
|
|
2246
2308
|
}
|
|
2247
2309
|
/**
|
|
2248
|
-
*
|
|
2249
|
-
*
|
|
2250
|
-
*
|
|
2310
|
+
* Installs file-watcher broadcasts on a remote dev server. Every non-ignored
|
|
2311
|
+
* change/add/unlink emits a `mf:remote-update` custom event on the remote's
|
|
2312
|
+
* own WS channel. The host relay (see `setupHostFullReloadRelay`) listens
|
|
2313
|
+
* for these events and triggers a host-side full reload in response.
|
|
2251
2314
|
*
|
|
2252
|
-
*
|
|
2315
|
+
* Only called under the `'full-reload'` strategy.
|
|
2253
2316
|
*/
|
|
2254
|
-
function
|
|
2255
|
-
const
|
|
2256
|
-
|
|
2257
|
-
|
|
2258
|
-
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
|
|
2262
|
-
|
|
2263
|
-
|
|
2264
|
-
name: "module-federation-dev-remote-hmr",
|
|
2265
|
-
apply: "serve",
|
|
2266
|
-
configureServer(server) {
|
|
2267
|
-
if (!isRemoteHmrEnabled(options.dev)) return;
|
|
2268
|
-
const isRemote = Object.keys(options.exposes).length > 0;
|
|
2269
|
-
const isHost = Object.keys(options.remotes).length > 0;
|
|
2270
|
-
const strategy = resolveHmrStrategy(options.dev, server.config.plugins);
|
|
2271
|
-
if (isRemote) {
|
|
2272
|
-
const endpointPath = getRemoteHmrPath(server.config.base);
|
|
2273
|
-
const wsUrl = getRemoteHmrWsUrl(server);
|
|
2274
|
-
server.middlewares.use((req, res, next) => {
|
|
2275
|
-
if (req.url?.replace(/\?.*$/, "") !== "/@react-refresh") return next();
|
|
2276
|
-
res.setHeader("Content-Type", "application/javascript; charset=utf-8");
|
|
2277
|
-
res.setHeader("Access-Control-Allow-Origin", "*");
|
|
2278
|
-
res.end(REACT_REFRESH_PROXY_MODULE);
|
|
2279
|
-
});
|
|
2280
|
-
server.middlewares.use((req, res, next) => {
|
|
2281
|
-
if (req.url?.replace(/\?.*/, "") !== endpointPath) {
|
|
2282
|
-
next();
|
|
2283
|
-
return;
|
|
2284
|
-
}
|
|
2285
|
-
res.setHeader("Content-Type", "application/json");
|
|
2286
|
-
res.setHeader("Access-Control-Allow-Origin", "*");
|
|
2287
|
-
res.end(JSON.stringify({
|
|
2288
|
-
remote: options.name,
|
|
2289
|
-
event: REMOTE_HMR_EVENT,
|
|
2290
|
-
wsUrl
|
|
2291
|
-
}));
|
|
2292
|
-
});
|
|
2293
|
-
const broadcast = (file) => {
|
|
2294
|
-
if (strategy === "native") return;
|
|
2295
|
-
if (shouldIgnoreFile(file, options)) return;
|
|
2296
|
-
server.ws.send({
|
|
2297
|
-
type: "custom",
|
|
2298
|
-
event: REMOTE_HMR_EVENT,
|
|
2299
|
-
data: {
|
|
2300
|
-
remote: options.name,
|
|
2301
|
-
file,
|
|
2302
|
-
ts: Date.now()
|
|
2303
|
-
}
|
|
2304
|
-
});
|
|
2305
|
-
};
|
|
2306
|
-
server.watcher.on("change", broadcast);
|
|
2307
|
-
server.watcher.on("add", broadcast);
|
|
2308
|
-
server.watcher.on("unlink", broadcast);
|
|
2309
|
-
server.httpServer?.once("close", () => {
|
|
2310
|
-
server.watcher.off("change", broadcast);
|
|
2311
|
-
server.watcher.off("add", broadcast);
|
|
2312
|
-
server.watcher.off("unlink", broadcast);
|
|
2313
|
-
});
|
|
2317
|
+
function setupRemoteBroadcast(server, options) {
|
|
2318
|
+
const broadcast = (file) => {
|
|
2319
|
+
if (shouldIgnoreFile(file, options)) return;
|
|
2320
|
+
server.ws.send({
|
|
2321
|
+
type: "custom",
|
|
2322
|
+
event: REMOTE_HMR_EVENT,
|
|
2323
|
+
data: {
|
|
2324
|
+
remote: options.name,
|
|
2325
|
+
file,
|
|
2326
|
+
ts: Date.now()
|
|
2314
2327
|
}
|
|
2315
|
-
if (isHost) {
|
|
2316
|
-
const connections = [];
|
|
2317
|
-
const reconnectTimers = /* @__PURE__ */ new Map();
|
|
2318
|
-
let isTearingDown = false;
|
|
2319
|
-
const clearReconnectTimer = (remoteName) => {
|
|
2320
|
-
const timer = reconnectTimers.get(remoteName);
|
|
2321
|
-
if (!timer) return;
|
|
2322
|
-
clearTimeout(timer);
|
|
2323
|
-
reconnectTimers.delete(remoteName);
|
|
2324
|
-
};
|
|
2325
|
-
const scheduleReconnect = (remoteName, remote, attempt, reason) => {
|
|
2326
|
-
if (isTearingDown) return;
|
|
2327
|
-
if (attempt >= REMOTE_HMR_CONNECT_MAX_RETRIES) {
|
|
2328
|
-
mfWarn(`Remote "${remoteName}" full HMR reconnect skipped after ${REMOTE_HMR_CONNECT_MAX_RETRIES} attempts: ${reason}`);
|
|
2329
|
-
return;
|
|
2330
|
-
}
|
|
2331
|
-
clearReconnectTimer(remoteName);
|
|
2332
|
-
const timer = setTimeout(() => {
|
|
2333
|
-
reconnectTimers.delete(remoteName);
|
|
2334
|
-
connectRemote(remoteName, remote, attempt + 1);
|
|
2335
|
-
}, REMOTE_HMR_CONNECT_RETRY_DELAY_MS);
|
|
2336
|
-
reconnectTimers.set(remoteName, timer);
|
|
2337
|
-
};
|
|
2338
|
-
const connectRemote = async (remoteName, remote, attempt = 0) => {
|
|
2339
|
-
if (isTearingDown) return;
|
|
2340
|
-
const endpoint = getRemoteHmrEndpoint(remote.entry, server);
|
|
2341
|
-
if (!endpoint) {
|
|
2342
|
-
mfWarn(`Failed to build HMR endpoint URL for remote "${remoteName}"`);
|
|
2343
|
-
return;
|
|
2344
|
-
}
|
|
2345
|
-
try {
|
|
2346
|
-
const metadataResponse = await fetch(endpoint);
|
|
2347
|
-
if (!metadataResponse.ok) {
|
|
2348
|
-
mfWarn(`Failed to fetch remote HMR metadata from "${remoteName}": ${metadataResponse.status}`);
|
|
2349
|
-
scheduleReconnect(remoteName, remote, attempt, `HTTP ${metadataResponse.status}`);
|
|
2350
|
-
return;
|
|
2351
|
-
}
|
|
2352
|
-
const metadata = await metadataResponse.json();
|
|
2353
|
-
if (metadata.event !== REMOTE_HMR_EVENT || !metadata.wsUrl) {
|
|
2354
|
-
mfWarn(`Remote "${remoteName}" returned unexpected HMR metadata shape`);
|
|
2355
|
-
return;
|
|
2356
|
-
}
|
|
2357
|
-
const ws = new WebSocket(metadata.wsUrl, "vite-hmr");
|
|
2358
|
-
ws.onmessage = (rawEvent) => {
|
|
2359
|
-
if (strategy === "native") return;
|
|
2360
|
-
const message = parseRemoteHmrMessage(rawEvent.data);
|
|
2361
|
-
if (!message || message.event !== REMOTE_HMR_EVENT) return;
|
|
2362
|
-
server.ws.send({ type: "full-reload" });
|
|
2363
|
-
};
|
|
2364
|
-
ws.onopen = () => clearReconnectTimer(remoteName);
|
|
2365
|
-
ws.onerror = (error) => mfWarn(`Remote HMR socket error for "${remoteName}":`, error);
|
|
2366
|
-
ws.onclose = () => scheduleReconnect(remoteName, remote, attempt, "socket closed");
|
|
2367
|
-
connections.push(ws);
|
|
2368
|
-
} catch (error) {
|
|
2369
|
-
mfWarn(`Failed to connect remote HMR for "${remoteName}" on attempt ${attempt + 1}: ${getStringPreview(error)}`);
|
|
2370
|
-
scheduleReconnect(remoteName, remote, attempt, getStringPreview(error));
|
|
2371
|
-
}
|
|
2372
|
-
};
|
|
2373
|
-
const teardown = () => {
|
|
2374
|
-
isTearingDown = true;
|
|
2375
|
-
reconnectTimers.forEach((timer) => clearTimeout(timer));
|
|
2376
|
-
reconnectTimers.clear();
|
|
2377
|
-
connections.forEach((connection) => {
|
|
2378
|
-
if (connection.readyState !== connection.CLOSING && connection.readyState !== connection.CLOSED) connection.close();
|
|
2379
|
-
});
|
|
2380
|
-
connections.length = 0;
|
|
2381
|
-
};
|
|
2382
|
-
for (const [remoteName, remote] of Object.entries(options.remotes)) connectRemote(remoteName, remote);
|
|
2383
|
-
const triggerHostReload = (file) => {
|
|
2384
|
-
if (strategy === "native") return;
|
|
2385
|
-
if (shouldIgnoreFile(file, options)) return;
|
|
2386
|
-
server.ws.send({ type: "full-reload" });
|
|
2387
|
-
};
|
|
2388
|
-
server.watcher.on("change", triggerHostReload);
|
|
2389
|
-
server.watcher.on("add", triggerHostReload);
|
|
2390
|
-
server.watcher.on("unlink", triggerHostReload);
|
|
2391
|
-
server.httpServer?.once("close", teardown);
|
|
2392
|
-
}
|
|
2393
|
-
}
|
|
2394
|
-
};
|
|
2395
|
-
}
|
|
2396
|
-
//#endregion
|
|
2397
|
-
//#region src/plugins/pluginDts.ts
|
|
2398
|
-
const DEFAULT_DEV_OPTIONS = {
|
|
2399
|
-
disableLiveReload: true,
|
|
2400
|
-
disableHotTypesReload: false,
|
|
2401
|
-
disableDynamicRemoteTypeHints: false
|
|
2402
|
-
};
|
|
2403
|
-
const DYNAMIC_HINTS_PLUGIN = "@module-federation/dts-plugin/dynamic-remote-type-hints-plugin";
|
|
2404
|
-
const getIPv4 = () => process.env["FEDERATION_IPV4"] || "127.0.0.1";
|
|
2405
|
-
const DEV_TYPES_FOLDER = ".dev-server";
|
|
2406
|
-
const DEFAULT_PUBLIC_TYPES_FOLDER = "@mf-types";
|
|
2407
|
-
const forkDevWorkerPath = __require.resolve("@module-federation/dts-plugin/dist/fork-dev-worker.js");
|
|
2408
|
-
var DevWorker = class {
|
|
2409
|
-
worker = rpc.createRpcWorker(forkDevWorkerPath, {}, void 0, false);
|
|
2410
|
-
constructor(options) {
|
|
2411
|
-
this.worker.connect(options);
|
|
2412
|
-
}
|
|
2413
|
-
update() {
|
|
2414
|
-
this.worker.process?.send?.({
|
|
2415
|
-
type: rpc.RpcGMCallTypes.CALL,
|
|
2416
|
-
id: this.worker.id,
|
|
2417
|
-
args: [void 0, "update"]
|
|
2418
2328
|
});
|
|
2419
|
-
}
|
|
2420
|
-
exit() {
|
|
2421
|
-
this.worker.terminate();
|
|
2422
|
-
}
|
|
2423
|
-
};
|
|
2424
|
-
const normalizeDevOptions = (dev) => {
|
|
2425
|
-
if (dev === false) return false;
|
|
2426
|
-
if (dev === true || typeof dev === "undefined") return { ...DEFAULT_DEV_OPTIONS };
|
|
2427
|
-
return {
|
|
2428
|
-
...DEFAULT_DEV_OPTIONS,
|
|
2429
|
-
...dev
|
|
2430
2329
|
};
|
|
2431
|
-
|
|
2432
|
-
|
|
2433
|
-
|
|
2434
|
-
|
|
2435
|
-
|
|
2330
|
+
server.watcher.on("change", broadcast);
|
|
2331
|
+
server.watcher.on("add", broadcast);
|
|
2332
|
+
server.watcher.on("unlink", broadcast);
|
|
2333
|
+
server.httpServer?.once("close", () => {
|
|
2334
|
+
server.watcher.off("change", broadcast);
|
|
2335
|
+
server.watcher.off("add", broadcast);
|
|
2336
|
+
server.watcher.off("unlink", broadcast);
|
|
2436
2337
|
});
|
|
2437
|
-
|
|
2438
|
-
|
|
2439
|
-
|
|
2440
|
-
|
|
2441
|
-
|
|
2442
|
-
|
|
2443
|
-
|
|
2444
|
-
|
|
2445
|
-
|
|
2446
|
-
|
|
2447
|
-
|
|
2448
|
-
|
|
2449
|
-
|
|
2450
|
-
|
|
2451
|
-
|
|
2452
|
-
|
|
2453
|
-
|
|
2454
|
-
|
|
2455
|
-
|
|
2456
|
-
|
|
2457
|
-
|
|
2458
|
-
|
|
2459
|
-
const getExposeImportPaths = (options) => {
|
|
2460
|
-
return Object.values(options.exposes).map((value) => {
|
|
2461
|
-
return value.import;
|
|
2462
|
-
}).filter((value) => Boolean(value));
|
|
2463
|
-
};
|
|
2464
|
-
const usesVueSfcExposes = (options) => {
|
|
2465
|
-
return getExposeImportPaths(options).some((value) => value.endsWith(".vue"));
|
|
2466
|
-
};
|
|
2467
|
-
const resolveDtsPluginOptions = (dts, options, context) => {
|
|
2468
|
-
if (dts === false) return false;
|
|
2469
|
-
const inferredGenerateTypesDefaults = { generateAPITypes: true };
|
|
2470
|
-
if (usesVueSfcExposes(options) && hasPackageDependency("vue-tsc", context)) inferredGenerateTypesDefaults.compilerInstance = "vue-tsc";
|
|
2471
|
-
if (dts === true || typeof dts === "undefined") return { generateTypes: inferredGenerateTypesDefaults };
|
|
2472
|
-
const generateTypes = dts.generateTypes;
|
|
2473
|
-
return {
|
|
2474
|
-
...dts,
|
|
2475
|
-
generateTypes: generateTypes === false ? false : {
|
|
2476
|
-
...inferredGenerateTypesDefaults,
|
|
2477
|
-
...generateTypes === true || typeof generateTypes === "undefined" ? {} : generateTypes
|
|
2478
|
-
}
|
|
2479
|
-
};
|
|
2480
|
-
};
|
|
2481
|
-
const getBasePath = (base) => {
|
|
2482
|
-
if (base.startsWith("http://") || base.startsWith("https://")) return new URL(base).pathname.replace(/\/$/, "") || "/";
|
|
2483
|
-
return base.replace(/\/$/, "") || "/";
|
|
2484
|
-
};
|
|
2485
|
-
const joinBaseAndAsset = (base, assetFileName) => {
|
|
2486
|
-
const basePath = getBasePath(base);
|
|
2487
|
-
return `${basePath === "/" ? "" : basePath}/${assetFileName}`.replace(/\/{2,}/g, "/");
|
|
2488
|
-
};
|
|
2489
|
-
const getDevDtsAssetPaths = (options) => {
|
|
2490
|
-
const { outputDir, publicTypesFolder, root, base } = options;
|
|
2491
|
-
return {
|
|
2492
|
-
apiFilePath: path$1.resolve(root, outputDir, `${DEV_TYPES_FOLDER}.d.ts`),
|
|
2493
|
-
apiRequestPath: joinBaseAndAsset(base, `${publicTypesFolder}.d.ts`),
|
|
2494
|
-
zipFilePath: path$1.resolve(root, outputDir, `${DEV_TYPES_FOLDER}.zip`),
|
|
2495
|
-
zipRequestPath: joinBaseAndAsset(base, `${publicTypesFolder}.zip`)
|
|
2338
|
+
}
|
|
2339
|
+
/**
|
|
2340
|
+
* Installs the host-side full-reload relay. For each configured remote:
|
|
2341
|
+
* 1. Fetches the remote's `/__mf_hmr` metadata to get its WS URL.
|
|
2342
|
+
* 2. Opens a Node-to-Node WebSocket to that URL.
|
|
2343
|
+
* 3. On any `mf:remote-update` message, broadcasts `{ type: 'full-reload' }`
|
|
2344
|
+
* to the host's own browser-facing WS.
|
|
2345
|
+
*
|
|
2346
|
+
* Also reloads on local host file changes. Retries failed connections up to
|
|
2347
|
+
* `REMOTE_HMR_CONNECT_MAX_RETRIES` times with a fixed delay.
|
|
2348
|
+
*
|
|
2349
|
+
* Only called under the `'full-reload'` strategy.
|
|
2350
|
+
*/
|
|
2351
|
+
function setupHostFullReloadRelay(server, options) {
|
|
2352
|
+
const connections = [];
|
|
2353
|
+
const reconnectTimers = /* @__PURE__ */ new Map();
|
|
2354
|
+
let isTearingDown = false;
|
|
2355
|
+
const clearReconnectTimer = (remoteName) => {
|
|
2356
|
+
const timer = reconnectTimers.get(remoteName);
|
|
2357
|
+
if (!timer) return;
|
|
2358
|
+
clearTimeout(timer);
|
|
2359
|
+
reconnectTimers.delete(remoteName);
|
|
2496
2360
|
};
|
|
2497
|
-
|
|
2498
|
-
|
|
2499
|
-
|
|
2500
|
-
|
|
2501
|
-
const isZipRequest = requestPath === assetPaths.zipRequestPath;
|
|
2502
|
-
const isApiRequest = requestPath === assetPaths.apiRequestPath;
|
|
2503
|
-
if (!isZipRequest && !isApiRequest) {
|
|
2504
|
-
next();
|
|
2361
|
+
const scheduleReconnect = (remoteName, remote, attempt, reason) => {
|
|
2362
|
+
if (isTearingDown) return;
|
|
2363
|
+
if (attempt >= REMOTE_HMR_CONNECT_MAX_RETRIES) {
|
|
2364
|
+
mfWarn(`Remote "${remoteName}" full HMR reconnect skipped after ${REMOTE_HMR_CONNECT_MAX_RETRIES} attempts: ${reason}`);
|
|
2505
2365
|
return;
|
|
2506
2366
|
}
|
|
2507
|
-
|
|
2508
|
-
|
|
2509
|
-
|
|
2510
|
-
|
|
2367
|
+
clearReconnectTimer(remoteName);
|
|
2368
|
+
const timer = setTimeout(() => {
|
|
2369
|
+
reconnectTimers.delete(remoteName);
|
|
2370
|
+
connectRemote(remoteName, remote, attempt + 1);
|
|
2371
|
+
}, REMOTE_HMR_CONNECT_RETRY_DELAY_MS);
|
|
2372
|
+
reconnectTimers.set(remoteName, timer);
|
|
2373
|
+
};
|
|
2374
|
+
const connectRemote = async (remoteName, remote, attempt = 0) => {
|
|
2375
|
+
if (isTearingDown) return;
|
|
2376
|
+
const endpoint = getRemoteHmrEndpoint(remote.entry, server);
|
|
2377
|
+
if (!endpoint) {
|
|
2378
|
+
mfWarn(`Failed to build HMR endpoint URL for remote "${remoteName}"`);
|
|
2511
2379
|
return;
|
|
2512
2380
|
}
|
|
2513
|
-
|
|
2514
|
-
|
|
2515
|
-
|
|
2516
|
-
|
|
2517
|
-
|
|
2381
|
+
try {
|
|
2382
|
+
const metadataResponse = await fetch(endpoint);
|
|
2383
|
+
if (!metadataResponse.ok) {
|
|
2384
|
+
mfWarn(`Failed to fetch remote HMR metadata from "${remoteName}": ${metadataResponse.status}`);
|
|
2385
|
+
scheduleReconnect(remoteName, remote, attempt, `HTTP ${metadataResponse.status}`);
|
|
2386
|
+
return;
|
|
2387
|
+
}
|
|
2388
|
+
const metadata = await metadataResponse.json();
|
|
2389
|
+
if (metadata.event !== "mf:remote-update" || !metadata.wsUrl) {
|
|
2390
|
+
mfWarn(`Remote "${remoteName}" returned unexpected HMR metadata shape`);
|
|
2391
|
+
return;
|
|
2392
|
+
}
|
|
2393
|
+
const ws = new WebSocket(metadata.wsUrl, "vite-hmr");
|
|
2394
|
+
ws.onmessage = (rawEvent) => {
|
|
2395
|
+
const message = parseRemoteHmrMessage(rawEvent.data);
|
|
2396
|
+
if (!message || message.event !== "mf:remote-update") return;
|
|
2397
|
+
server.ws.send({ type: "full-reload" });
|
|
2398
|
+
};
|
|
2399
|
+
ws.onopen = () => clearReconnectTimer(remoteName);
|
|
2400
|
+
ws.onerror = (error) => mfWarn(`Remote HMR socket error for "${remoteName}":`, error);
|
|
2401
|
+
ws.onclose = () => scheduleReconnect(remoteName, remote, attempt, "socket closed");
|
|
2402
|
+
connections.push(ws);
|
|
2403
|
+
} catch (error) {
|
|
2404
|
+
mfWarn(`Failed to connect remote HMR for "${remoteName}" on attempt ${attempt + 1}: ${getStringPreview(error)}`);
|
|
2405
|
+
scheduleReconnect(remoteName, remote, attempt, getStringPreview(error));
|
|
2518
2406
|
}
|
|
2519
|
-
|
|
2520
|
-
|
|
2521
|
-
|
|
2522
|
-
|
|
2523
|
-
|
|
2524
|
-
|
|
2525
|
-
|
|
2407
|
+
};
|
|
2408
|
+
const teardown = () => {
|
|
2409
|
+
isTearingDown = true;
|
|
2410
|
+
reconnectTimers.forEach((timer) => clearTimeout(timer));
|
|
2411
|
+
reconnectTimers.clear();
|
|
2412
|
+
connections.forEach((connection) => {
|
|
2413
|
+
if (connection.readyState !== connection.CLOSING && connection.readyState !== connection.CLOSED) connection.close();
|
|
2526
2414
|
});
|
|
2527
|
-
|
|
2415
|
+
connections.length = 0;
|
|
2528
2416
|
};
|
|
2529
|
-
|
|
2530
|
-
const
|
|
2531
|
-
|
|
2532
|
-
|
|
2533
|
-
|
|
2534
|
-
|
|
2535
|
-
|
|
2536
|
-
|
|
2537
|
-
|
|
2538
|
-
|
|
2539
|
-
|
|
2540
|
-
|
|
2541
|
-
|
|
2542
|
-
|
|
2543
|
-
|
|
2544
|
-
|
|
2545
|
-
|
|
2546
|
-
|
|
2547
|
-
|
|
2548
|
-
|
|
2417
|
+
for (const [remoteName, remote] of Object.entries(options.remotes)) connectRemote(remoteName, remote);
|
|
2418
|
+
const triggerHostReload = (file) => {
|
|
2419
|
+
if (shouldIgnoreFile(file, options)) return;
|
|
2420
|
+
server.ws.send({ type: "full-reload" });
|
|
2421
|
+
};
|
|
2422
|
+
server.watcher.on("change", triggerHostReload);
|
|
2423
|
+
server.watcher.on("add", triggerHostReload);
|
|
2424
|
+
server.watcher.on("unlink", triggerHostReload);
|
|
2425
|
+
server.httpServer?.once("close", teardown);
|
|
2426
|
+
}
|
|
2427
|
+
//#endregion
|
|
2428
|
+
//#region src/plugins/pluginDevRemoteHmr.ts
|
|
2429
|
+
/**
|
|
2430
|
+
* Clears the federation runtime's `moduleCache` on every Vite `vite:beforeUpdate`
|
|
2431
|
+
* event. Without this, after a remote SFC change Vite would patch the in-memory
|
|
2432
|
+
* component, but `loadRemote()` would still return the cached stale module on
|
|
2433
|
+
* the next call (e.g. after route navigation), making the patched version
|
|
2434
|
+
* effectively unreachable.
|
|
2435
|
+
*/
|
|
2436
|
+
const FEDERATION_MODULE_CACHE_CLEAR_SCRIPT = `
|
|
2437
|
+
if (import.meta.hot) {
|
|
2438
|
+
import.meta.hot.on('vite:beforeUpdate', function () {
|
|
2439
|
+
try {
|
|
2440
|
+
var f = globalThis.__FEDERATION__ || globalThis.__VMOK__;
|
|
2441
|
+
if (!f || !f.__INSTANCES__) return;
|
|
2442
|
+
for (var i = 0; i < f.__INSTANCES__.length; i++) {
|
|
2443
|
+
if (f.__INSTANCES__[i] && f.__INSTANCES__[i].moduleCache)
|
|
2444
|
+
f.__INSTANCES__[i].moduleCache.clear();
|
|
2445
|
+
}
|
|
2446
|
+
} catch (e) {}
|
|
2447
|
+
});
|
|
2448
|
+
}`;
|
|
2449
|
+
const HMR_ADAPTERS = [reactAdapter, vueAdapter];
|
|
2450
|
+
function resolveAdapters(plugins) {
|
|
2451
|
+
const pluginNames = new Set(plugins.map((p) => p.name));
|
|
2452
|
+
return HMR_ADAPTERS.filter((adapter) => adapter.pluginNames.some((name) => pluginNames.has(name)));
|
|
2453
|
+
}
|
|
2454
|
+
function hasCrossFederationHmr(plugins) {
|
|
2455
|
+
return resolveAdapters(plugins).length > 0;
|
|
2456
|
+
}
|
|
2457
|
+
function isRemoteHmrEnabled(dev) {
|
|
2458
|
+
return typeof dev === "object" && dev !== null && !!dev.remoteHmr;
|
|
2459
|
+
}
|
|
2460
|
+
/**
|
|
2461
|
+
* `'native'` — a matched framework adapter owns HMR through Vite's native
|
|
2462
|
+
* channel (e.g. React Fast Refresh via the `/@react-refresh` proxy, Vue's
|
|
2463
|
+
* patched `__VUE_HMR_RUNTIME__`). The broadcast/relay path stays idle.
|
|
2464
|
+
*
|
|
2465
|
+
* `'full-reload'` — no adapter matched, or the user explicitly opted in with
|
|
2466
|
+
* `remoteHmr: 'full-reload'` to bypass adapters: the plugin's broadcast/relay
|
|
2467
|
+
* machinery triggers a page reload on every remote file change.
|
|
2468
|
+
*/
|
|
2469
|
+
function resolveHmrStrategy(dev, plugins) {
|
|
2470
|
+
if (typeof dev === "object" && dev !== null && dev.remoteHmr === "full-reload") return "full-reload";
|
|
2471
|
+
return hasCrossFederationHmr(plugins) ? "native" : "full-reload";
|
|
2472
|
+
}
|
|
2473
|
+
function shouldIgnoreFile(file, options) {
|
|
2474
|
+
return file.includes("/node_modules/") || file.includes("\\node_modules\\") || file.includes(`/${options.virtualModuleDir}/`) || file.includes(`\\${options.virtualModuleDir}\\`) || file.includes("/.vite/") || file.includes("\\.vite\\") || file.includes("/.mf/") || file.includes("\\.mf\\") || file.includes("/mf-manifest.json") || file.includes("\\mf-manifest.json") || file.includes("/mf-stats.json") || file.includes("\\mf-stats.json");
|
|
2475
|
+
}
|
|
2476
|
+
function collectHostTags(server, options, adapters) {
|
|
2477
|
+
const ctx = {
|
|
2478
|
+
server,
|
|
2479
|
+
options
|
|
2480
|
+
};
|
|
2481
|
+
const tags = [];
|
|
2482
|
+
for (const adapter of adapters) {
|
|
2483
|
+
const adapterTags = adapter.host?.transformIndexHtml?.(ctx);
|
|
2484
|
+
if (adapterTags) tags.push(...adapterTags);
|
|
2485
|
+
}
|
|
2486
|
+
tags.push({
|
|
2487
|
+
tag: "script",
|
|
2488
|
+
attrs: { type: "module" },
|
|
2489
|
+
children: FEDERATION_MODULE_CACHE_CLEAR_SCRIPT,
|
|
2490
|
+
injectTo: "head"
|
|
2549
2491
|
});
|
|
2550
|
-
|
|
2551
|
-
|
|
2552
|
-
|
|
2553
|
-
|
|
2554
|
-
|
|
2555
|
-
|
|
2492
|
+
return tags;
|
|
2493
|
+
}
|
|
2494
|
+
function pluginDevRemoteHmr(options) {
|
|
2495
|
+
const isHost = Object.keys(options.remotes).length > 0;
|
|
2496
|
+
const isRemote = Object.keys(options.exposes).length > 0;
|
|
2497
|
+
let adapters = [];
|
|
2498
|
+
let strategy = "full-reload";
|
|
2499
|
+
return {
|
|
2500
|
+
name: "module-federation-dev-remote-hmr",
|
|
2556
2501
|
apply: "serve",
|
|
2557
|
-
config(config) {
|
|
2558
|
-
normalizedDevOptions = normalizeDevOptions(options.dev);
|
|
2559
|
-
if (!normalizedDevOptions) return;
|
|
2560
|
-
if (normalizedDevOptions.disableDynamicRemoteTypeHints) return;
|
|
2561
|
-
ensureRuntimePlugin(options, DYNAMIC_HINTS_PLUGIN);
|
|
2562
|
-
const define = config.define ? { ...config.define } : {};
|
|
2563
|
-
if (!("FEDERATION_IPV4" in define)) define.FEDERATION_IPV4 = JSON.stringify(getIPv4());
|
|
2564
|
-
config.define = define;
|
|
2565
|
-
},
|
|
2566
2502
|
configResolved(config) {
|
|
2567
|
-
|
|
2503
|
+
adapters = resolveAdapters(config.plugins);
|
|
2504
|
+
strategy = resolveHmrStrategy(options.dev, config.plugins);
|
|
2568
2505
|
},
|
|
2569
2506
|
configureServer(server) {
|
|
2570
|
-
if (!
|
|
2571
|
-
|
|
2572
|
-
|
|
2573
|
-
|
|
2574
|
-
|
|
2575
|
-
const dtsModuleFederationConfig = getDtsModuleFederationConfig(resolvedConfig.root);
|
|
2576
|
-
const normalizedDtsOptions = normalizeDevDtsOptions(dtsModuleFederationConfig.dts, resolvedConfig.root);
|
|
2577
|
-
if (typeof normalizedDtsOptions !== "object") return;
|
|
2578
|
-
const normalizedGenerateTypes = normalizeOptions(Boolean(normalizedDtsOptions), { compileInChildProcess: true }, "mfOptions.dts.generateTypes")(normalizedDtsOptions.generateTypes);
|
|
2579
|
-
const remote = normalizedGenerateTypes === false ? void 0 : {
|
|
2580
|
-
implementation: normalizedDtsOptions.implementation,
|
|
2581
|
-
context: resolvedConfig.root,
|
|
2582
|
-
outputDir,
|
|
2583
|
-
moduleFederationConfig: { ...dtsModuleFederationConfig },
|
|
2584
|
-
hostRemoteTypesFolder: normalizedGenerateTypes.typesFolder || DEFAULT_PUBLIC_TYPES_FOLDER,
|
|
2585
|
-
...normalizedGenerateTypes,
|
|
2586
|
-
typesFolder: DEV_TYPES_FOLDER
|
|
2587
|
-
};
|
|
2588
|
-
if (remote) server.middlewares.use(createDevDtsAssetMiddleware(getDevDtsAssetPaths({
|
|
2589
|
-
outputDir,
|
|
2590
|
-
publicTypesFolder: remote.hostRemoteTypesFolder || DEFAULT_PUBLIC_TYPES_FOLDER,
|
|
2591
|
-
root: resolvedConfig.root,
|
|
2592
|
-
base: resolvedConfig.base
|
|
2593
|
-
})));
|
|
2594
|
-
if (remote && !remote.tsConfigPath && normalizedDtsOptions.tsConfigPath) remote.tsConfigPath = normalizedDtsOptions.tsConfigPath;
|
|
2595
|
-
const normalizedConsumeTypes = normalizeOptions(Boolean(normalizedDtsOptions), { consumeAPITypes: true }, "mfOptions.dts.consumeTypes")(normalizedDtsOptions.consumeTypes);
|
|
2596
|
-
const host = normalizedConsumeTypes === false ? void 0 : {
|
|
2597
|
-
implementation: normalizedDtsOptions.implementation,
|
|
2598
|
-
context: resolvedConfig.root,
|
|
2599
|
-
moduleFederationConfig: dtsModuleFederationConfig,
|
|
2600
|
-
typesFolder: normalizedConsumeTypes.typesFolder || "@mf-types",
|
|
2601
|
-
abortOnError: false,
|
|
2602
|
-
...normalizedConsumeTypes
|
|
2603
|
-
};
|
|
2604
|
-
const extraOptions = normalizedDtsOptions.extraOptions || {};
|
|
2605
|
-
if (!remote && !host && devOptions.disableLiveReload) return;
|
|
2606
|
-
const startDevWorker = async () => {
|
|
2607
|
-
let remoteTypeUrls;
|
|
2608
|
-
if (host) remoteTypeUrls = await new Promise((resolve) => {
|
|
2609
|
-
consumeTypesAPI({
|
|
2610
|
-
host,
|
|
2611
|
-
extraOptions,
|
|
2612
|
-
displayErrorInTerminal: normalizedDtsOptions.displayErrorInTerminal
|
|
2613
|
-
}, resolve);
|
|
2614
|
-
});
|
|
2615
|
-
devWorker = new DevWorker({
|
|
2616
|
-
name: options.name,
|
|
2617
|
-
remote,
|
|
2618
|
-
host: host ? {
|
|
2619
|
-
...host,
|
|
2620
|
-
remoteTypeUrls
|
|
2621
|
-
} : void 0,
|
|
2622
|
-
extraOptions,
|
|
2623
|
-
disableLiveReload: devOptions.disableLiveReload,
|
|
2624
|
-
disableHotTypesReload: devOptions.disableHotTypesReload
|
|
2625
|
-
});
|
|
2626
|
-
const update = () => devWorker?.update();
|
|
2627
|
-
server.watcher.on("change", update);
|
|
2628
|
-
server.watcher.on("add", update);
|
|
2629
|
-
server.watcher.on("unlink", update);
|
|
2630
|
-
server.httpServer?.once("close", () => {
|
|
2631
|
-
devWorker?.exit();
|
|
2632
|
-
server.watcher.off("change", update);
|
|
2633
|
-
server.watcher.off("add", update);
|
|
2634
|
-
server.watcher.off("unlink", update);
|
|
2507
|
+
if (!isRemoteHmrEnabled(options.dev)) return;
|
|
2508
|
+
if (isRemote) {
|
|
2509
|
+
for (const adapter of adapters) adapter.remote?.configureServer?.({
|
|
2510
|
+
server,
|
|
2511
|
+
options
|
|
2635
2512
|
});
|
|
2636
|
-
|
|
2637
|
-
|
|
2638
|
-
logDtsError(error, normalizedDtsOptions);
|
|
2639
|
-
});
|
|
2640
|
-
}
|
|
2641
|
-
}, {
|
|
2642
|
-
name: "module-federation-dts-build",
|
|
2643
|
-
apply: "build",
|
|
2644
|
-
configResolved(config) {
|
|
2645
|
-
resolvedConfig = config;
|
|
2646
|
-
},
|
|
2647
|
-
async generateBundle() {
|
|
2648
|
-
if (hasGeneratedBundle) return;
|
|
2649
|
-
hasGeneratedBundle = true;
|
|
2650
|
-
if (!resolvedConfig) return;
|
|
2651
|
-
let normalizedDtsOptions;
|
|
2652
|
-
try {
|
|
2653
|
-
normalizedDtsOptions = normalizeDtsOptions(getDtsModuleFederationConfig(resolvedConfig.root), resolvedConfig.root);
|
|
2654
|
-
} catch (error) {
|
|
2655
|
-
logDtsError(error, options.dts);
|
|
2656
|
-
return;
|
|
2513
|
+
setupRemoteMetadataEndpoint(server, options);
|
|
2514
|
+
if (strategy === "full-reload") setupRemoteBroadcast(server, options);
|
|
2657
2515
|
}
|
|
2658
|
-
if (
|
|
2659
|
-
|
|
2660
|
-
|
|
2661
|
-
|
|
2662
|
-
try {
|
|
2663
|
-
consumeOptions = normalizeConsumeTypesOptions({
|
|
2664
|
-
context,
|
|
2665
|
-
dtsOptions: normalizedDtsOptions,
|
|
2666
|
-
pluginOptions: getDtsModuleFederationConfig(resolvedConfig.root)
|
|
2516
|
+
if (isHost) {
|
|
2517
|
+
for (const adapter of adapters) adapter.host?.configureServer?.({
|
|
2518
|
+
server,
|
|
2519
|
+
options
|
|
2667
2520
|
});
|
|
2668
|
-
|
|
2669
|
-
logDtsError(error, normalizedDtsOptions);
|
|
2670
|
-
return;
|
|
2671
|
-
}
|
|
2672
|
-
if (consumeOptions?.host?.typesOnBuild) try {
|
|
2673
|
-
await consumeTypesAPI(consumeOptions);
|
|
2674
|
-
} catch (error) {
|
|
2675
|
-
logDtsError(error, normalizedDtsOptions);
|
|
2521
|
+
if (strategy === "full-reload") setupHostFullReloadRelay(server, options);
|
|
2676
2522
|
}
|
|
2677
|
-
|
|
2678
|
-
|
|
2679
|
-
|
|
2680
|
-
|
|
2681
|
-
|
|
2682
|
-
|
|
2683
|
-
|
|
2684
|
-
}
|
|
2685
|
-
|
|
2686
|
-
|
|
2687
|
-
|
|
2523
|
+
},
|
|
2524
|
+
transform: {
|
|
2525
|
+
order: "post",
|
|
2526
|
+
handler(code, id) {
|
|
2527
|
+
if (!isRemote || !isRemoteHmrEnabled(options.dev)) return;
|
|
2528
|
+
if (!adapters.length) return;
|
|
2529
|
+
let result = code;
|
|
2530
|
+
const adapterCtx = { options };
|
|
2531
|
+
for (const adapter of adapters) {
|
|
2532
|
+
const next = adapter.remote?.transform?.(result, id, adapterCtx);
|
|
2533
|
+
if (typeof next === "string") result = next;
|
|
2534
|
+
}
|
|
2535
|
+
return result === code ? void 0 : result;
|
|
2688
2536
|
}
|
|
2689
|
-
|
|
2690
|
-
|
|
2691
|
-
|
|
2692
|
-
|
|
2693
|
-
|
|
2537
|
+
},
|
|
2538
|
+
transformIndexHtml: {
|
|
2539
|
+
order: "pre",
|
|
2540
|
+
handler(_html, ctx) {
|
|
2541
|
+
if (!isRemoteHmrEnabled(options.dev)) return;
|
|
2542
|
+
if (!isHost || !ctx.server) return;
|
|
2543
|
+
return collectHostTags(ctx.server, options, adapters);
|
|
2694
2544
|
}
|
|
2695
2545
|
}
|
|
2696
|
-
}
|
|
2546
|
+
};
|
|
2697
2547
|
}
|
|
2698
2548
|
//#endregion
|
|
2699
2549
|
//#region src/virtualModules/index.ts
|
|
2700
|
-
function initVirtualModules(command, remoteEntryId) {
|
|
2550
|
+
function initVirtualModules(command, remoteEntryId, enableSsrInit = false) {
|
|
2701
2551
|
writeLocalSharedImportMap();
|
|
2702
2552
|
writeHostAutoInit(remoteEntryId, command);
|
|
2703
|
-
writeRuntimeInitStatus(command);
|
|
2553
|
+
writeRuntimeInitStatus(command, enableSsrInit);
|
|
2704
2554
|
}
|
|
2705
2555
|
//#endregion
|
|
2706
2556
|
//#region src/utils/bundleHelpers.ts
|
|
@@ -3050,6 +2900,12 @@ function removeTrailingSlash(value) {
|
|
|
3050
2900
|
function ensureTrailingSlash(value) {
|
|
3051
2901
|
return `${removeTrailingSlash(value)}/`;
|
|
3052
2902
|
}
|
|
2903
|
+
function getBasePath(base) {
|
|
2904
|
+
return removeTrailingSlash(base || "/");
|
|
2905
|
+
}
|
|
2906
|
+
function isNuxtClientBase(base) {
|
|
2907
|
+
return getBasePath(base).endsWith("/_nuxt");
|
|
2908
|
+
}
|
|
3053
2909
|
function normalizeNodeModulePath(source) {
|
|
3054
2910
|
return source.replace(/\\/g, "/").replace(/\?.*$/, "");
|
|
3055
2911
|
}
|
|
@@ -3083,6 +2939,116 @@ function resolvePublicPath(options, viteBase, originalBase) {
|
|
|
3083
2939
|
return "auto";
|
|
3084
2940
|
}
|
|
3085
2941
|
//#endregion
|
|
2942
|
+
//#region src/virtualModules/virtualExposesSSR.ts
|
|
2943
|
+
/**
|
|
2944
|
+
* Virtual module ID for the SSR exposes map.
|
|
2945
|
+
* Separate from the browser exposes: no CSS injection, no document references,
|
|
2946
|
+
* and shared packages are imported as bare specifiers (externals in the SSR build).
|
|
2947
|
+
*/
|
|
2948
|
+
function getVirtualExposesSSRId(options) {
|
|
2949
|
+
return `virtual:mf-exposes-ssr:${`${options.internalName}__${options.filename}`.replace(/[^a-zA-Z0-9_-]/g, "_")}`;
|
|
2950
|
+
}
|
|
2951
|
+
/**
|
|
2952
|
+
* Generates the SSR exposes map module.
|
|
2953
|
+
*
|
|
2954
|
+
* Differences from the browser version (virtualExposes.ts):
|
|
2955
|
+
* - No CSS asset injection (document APIs unavailable on Node)
|
|
2956
|
+
* - Shared packages (react, react-dom, etc.) must be externals in the SSR
|
|
2957
|
+
* build so Node resolves them via its own module cache — this is what
|
|
2958
|
+
* guarantees the React singleton is shared with react-dom/server.
|
|
2959
|
+
*/
|
|
2960
|
+
function generateExposesSSR(options) {
|
|
2961
|
+
return `
|
|
2962
|
+
export default {
|
|
2963
|
+
${Object.keys(options.exposes).map((key) => {
|
|
2964
|
+
return `
|
|
2965
|
+
${JSON.stringify(key)}: async () => {
|
|
2966
|
+
const importModule = await import(${JSON.stringify(options.exposes[key].import)})
|
|
2967
|
+
const exportModule = {}
|
|
2968
|
+
Object.assign(exportModule, importModule)
|
|
2969
|
+
Object.defineProperty(exportModule, "__esModule", {
|
|
2970
|
+
value: true,
|
|
2971
|
+
enumerable: false
|
|
2972
|
+
})
|
|
2973
|
+
return exportModule
|
|
2974
|
+
}
|
|
2975
|
+
`;
|
|
2976
|
+
}).join(",")}
|
|
2977
|
+
}
|
|
2978
|
+
`;
|
|
2979
|
+
}
|
|
2980
|
+
//#endregion
|
|
2981
|
+
//#region src/virtualModules/virtualRemoteEntrySSR.ts
|
|
2982
|
+
const REMOTE_ENTRY_SSR_ID = "virtual:mf-REMOTE_ENTRY_SSR_ID";
|
|
2983
|
+
function getRemoteEntrySSRId(options) {
|
|
2984
|
+
return `${REMOTE_ENTRY_SSR_ID}:${`${options.internalName}__${options.filename}`.replace(/[^a-zA-Z0-9_-]/g, "_")}`;
|
|
2985
|
+
}
|
|
2986
|
+
function getSsrRemoteEntryFileName(browserFilename) {
|
|
2987
|
+
const ext = browserFilename.match(/\.[^.]+$/)?.[0] || ".js";
|
|
2988
|
+
return `${browserFilename.slice(0, browserFilename.length - ext.length)}.ssr${ext}`;
|
|
2989
|
+
}
|
|
2990
|
+
/**
|
|
2991
|
+
* Generates the SSR remote entry module.
|
|
2992
|
+
*
|
|
2993
|
+
* This is intentionally minimal — no HMR shim, no loadShare virtual modules,
|
|
2994
|
+
* no browser globals. Shared packages (react, react-dom, etc.) are imported
|
|
2995
|
+
* as externals by the SSR build, so Node's require cache provides the singleton.
|
|
2996
|
+
*
|
|
2997
|
+
* The container API (init / get) mirrors the browser entry so the MF runtime
|
|
2998
|
+
* can call it the same way on the server.
|
|
2999
|
+
*/
|
|
3000
|
+
function generateRemoteEntrySSR(options) {
|
|
3001
|
+
const virtualExposesSSRId = getVirtualExposesSSRId(options);
|
|
3002
|
+
return `
|
|
3003
|
+
import { init as runtimeInit } from "@module-federation/runtime";
|
|
3004
|
+
|
|
3005
|
+
let exposesMapPromise;
|
|
3006
|
+
|
|
3007
|
+
async function getExposesMap() {
|
|
3008
|
+
exposesMapPromise ??= import(${JSON.stringify(virtualExposesSSRId)}).then((mod) => mod.default ?? mod);
|
|
3009
|
+
return exposesMapPromise;
|
|
3010
|
+
}
|
|
3011
|
+
|
|
3012
|
+
/**
|
|
3013
|
+
* Called by the MF runtime on the host to register this remote's share scope.
|
|
3014
|
+
* On the server the host has already initialised the runtime, so we just need
|
|
3015
|
+
* to set up a minimal runtime instance for the remote container.
|
|
3016
|
+
*/
|
|
3017
|
+
async function init(shared = {}, initScope = []) {
|
|
3018
|
+
const initRes = runtimeInit({
|
|
3019
|
+
name: ${JSON.stringify(options.internalName)},
|
|
3020
|
+
remotes: [],
|
|
3021
|
+
shared: {},
|
|
3022
|
+
});
|
|
3023
|
+
const initToken = { from: ${JSON.stringify(options.internalName)} };
|
|
3024
|
+
if (initScope.indexOf(initToken) >= 0) return;
|
|
3025
|
+
initScope.push(initToken);
|
|
3026
|
+
initRes.initShareScopeMap(${JSON.stringify(options.shareScope)}, shared);
|
|
3027
|
+
try {
|
|
3028
|
+
await Promise.all(
|
|
3029
|
+
await initRes.initializeSharing(${JSON.stringify(options.shareScope)}, {
|
|
3030
|
+
strategy: ${JSON.stringify(options.shareStrategy ?? "version-first")},
|
|
3031
|
+
from: 'build',
|
|
3032
|
+
initScope,
|
|
3033
|
+
})
|
|
3034
|
+
);
|
|
3035
|
+
} catch (e) {
|
|
3036
|
+
console.error('[Module Federation SSR]', e);
|
|
3037
|
+
}
|
|
3038
|
+
return initRes;
|
|
3039
|
+
}
|
|
3040
|
+
|
|
3041
|
+
async function getExposes(moduleName) {
|
|
3042
|
+
const exposesMap = await getExposesMap();
|
|
3043
|
+
if (!(moduleName in exposesMap))
|
|
3044
|
+
throw new Error(\`[Module Federation] Module \${moduleName} does not exist in container.\`);
|
|
3045
|
+
return exposesMap[moduleName]().then((res) => () => res);
|
|
3046
|
+
}
|
|
3047
|
+
|
|
3048
|
+
export { init, getExposes as get };
|
|
3049
|
+
`;
|
|
3050
|
+
}
|
|
3051
|
+
//#endregion
|
|
3086
3052
|
//#region src/plugins/pluginMFManifest.ts
|
|
3087
3053
|
/**
|
|
3088
3054
|
* Resolves the build version for the module federation manifest.
|
|
@@ -3113,6 +3079,7 @@ const Manifest = () => {
|
|
|
3113
3079
|
};
|
|
3114
3080
|
let root;
|
|
3115
3081
|
let remoteEntryFile;
|
|
3082
|
+
let ssrRemoteEntryFile;
|
|
3116
3083
|
let publicPath;
|
|
3117
3084
|
let _command;
|
|
3118
3085
|
let _originalConfigBase;
|
|
@@ -3149,8 +3116,8 @@ const Manifest = () => {
|
|
|
3149
3116
|
type: "module"
|
|
3150
3117
|
},
|
|
3151
3118
|
ssrRemoteEntry: {
|
|
3152
|
-
name: filename,
|
|
3153
|
-
path: "",
|
|
3119
|
+
name: getSsrRemoteEntryFileName(filename),
|
|
3120
|
+
path: "/__mf_ssr__/",
|
|
3154
3121
|
type: "module"
|
|
3155
3122
|
},
|
|
3156
3123
|
varRemoteEntry: varFilename ? {
|
|
@@ -3181,6 +3148,7 @@ const Manifest = () => {
|
|
|
3181
3148
|
_originalConfigBase = config.base;
|
|
3182
3149
|
},
|
|
3183
3150
|
configResolved(config) {
|
|
3151
|
+
viteConfig = config;
|
|
3184
3152
|
root = config.root;
|
|
3185
3153
|
let base = config.base;
|
|
3186
3154
|
if (_command === "serve") base = (config.server.origin || "") + config.base;
|
|
@@ -3190,7 +3158,10 @@ const Manifest = () => {
|
|
|
3190
3158
|
if (!mfManifestName) return;
|
|
3191
3159
|
let filesMap = {};
|
|
3192
3160
|
const foundRemoteEntryFile = findRemoteEntryFile(mfOptions.filename, bundle);
|
|
3161
|
+
const expectedSsrRemoteEntryFile = getSsrRemoteEntryFileName(mfOptions.filename);
|
|
3162
|
+
const foundSsrRemoteEntryFile = Object.values(bundle).find((file) => file.fileName === expectedSsrRemoteEntryFile)?.fileName;
|
|
3193
3163
|
if (foundRemoteEntryFile) remoteEntryFile = foundRemoteEntryFile;
|
|
3164
|
+
ssrRemoteEntryFile = foundSsrRemoteEntryFile || expectedSsrRemoteEntryFile;
|
|
3194
3165
|
const allCssAssets = mfOptions.bundleAllCSS && !disableAssetsAnalyze ? collectCssAssets(bundle) : /* @__PURE__ */ new Set();
|
|
3195
3166
|
if (!disableAssetsAnalyze) {
|
|
3196
3167
|
const exposesModules = Object.keys(mfOptions.exposes).map((item) => mfOptions.exposes[item].import);
|
|
@@ -3232,6 +3203,11 @@ const Manifest = () => {
|
|
|
3232
3203
|
path: "",
|
|
3233
3204
|
type: "module"
|
|
3234
3205
|
};
|
|
3206
|
+
const ssrRemoteEntry = {
|
|
3207
|
+
name: ssrRemoteEntryFile || getSsrRemoteEntryFileName(filename),
|
|
3208
|
+
path: _command === "serve" ? "/__mf_ssr__/" : "",
|
|
3209
|
+
type: "module"
|
|
3210
|
+
};
|
|
3235
3211
|
const varRemoteEntry = varFilename ? {
|
|
3236
3212
|
name: varFilename,
|
|
3237
3213
|
path: "",
|
|
@@ -3295,7 +3271,7 @@ const Manifest = () => {
|
|
|
3295
3271
|
buildName: name
|
|
3296
3272
|
},
|
|
3297
3273
|
remoteEntry,
|
|
3298
|
-
ssrRemoteEntry
|
|
3274
|
+
ssrRemoteEntry,
|
|
3299
3275
|
varRemoteEntry,
|
|
3300
3276
|
types: {
|
|
3301
3277
|
path: "",
|
|
@@ -3534,13 +3510,14 @@ function appendAlias(config, alias) {
|
|
|
3534
3510
|
function pluginProxyRemotes_default(options) {
|
|
3535
3511
|
let command;
|
|
3536
3512
|
let root = process.cwd();
|
|
3513
|
+
let enableSsrInit = false;
|
|
3537
3514
|
const { remotes } = options;
|
|
3538
3515
|
function resolveRemoteId(source, importer, remoteName) {
|
|
3539
3516
|
if (source === remoteName) {
|
|
3540
3517
|
const installedPackageEntry = getInstalledPackageEntry(source, { cwd: root });
|
|
3541
3518
|
if (installedPackageEntry && (importer === void 0 || isNodeModulesImporter(importer))) return installedPackageEntry;
|
|
3542
3519
|
}
|
|
3543
|
-
const remoteModule = getRemoteVirtualModule(source, command);
|
|
3520
|
+
const remoteModule = getRemoteVirtualModule(source, command, enableSsrInit);
|
|
3544
3521
|
addUsedRemote(remoteName, source);
|
|
3545
3522
|
refreshHostAutoInit();
|
|
3546
3523
|
return remoteModule.getImportId();
|
|
@@ -3559,6 +3536,9 @@ function pluginProxyRemotes_default(options) {
|
|
|
3559
3536
|
});
|
|
3560
3537
|
});
|
|
3561
3538
|
},
|
|
3539
|
+
configResolved() {
|
|
3540
|
+
enableSsrInit = command === "serve" && parseInt(version, 10) >= 8;
|
|
3541
|
+
},
|
|
3562
3542
|
resolveId(source, importer) {
|
|
3563
3543
|
if (!filterId(source)) return;
|
|
3564
3544
|
for (const remote of Object.values(remotes)) {
|
|
@@ -3609,7 +3589,7 @@ function tryResolveFromProjectRoot(source) {
|
|
|
3609
3589
|
const browserEntry = getInstalledPackageEntry(source, { cwd: getPackageDetectionCwd() });
|
|
3610
3590
|
if (browserEntry) return browserEntry;
|
|
3611
3591
|
try {
|
|
3612
|
-
return createRequire
|
|
3592
|
+
return createRequire(new URL(`file://${path.join(getPackageDetectionCwd(), "package.json")}`)).resolve(source);
|
|
3613
3593
|
} catch {
|
|
3614
3594
|
return;
|
|
3615
3595
|
}
|
|
@@ -3840,9 +3820,37 @@ function applyRewrites(code, imports, id) {
|
|
|
3840
3820
|
const importParts = [];
|
|
3841
3821
|
if (imp.defaultLocal) importParts.push(`default as ${imp.defaultLocal}`);
|
|
3842
3822
|
importParts.push(`__moduleExports as ${nsId}`);
|
|
3843
|
-
const destructParts = imp.named.map((s) => s.imported === s.local ? s.local : `${s.imported}: ${s.local}`);
|
|
3844
3823
|
let rewrite = `import { ${importParts.join(", ")} } from ${src};`;
|
|
3845
|
-
if (
|
|
3824
|
+
if (imp.named.length > 0) {
|
|
3825
|
+
const isProxyId = `__mf_is_proxy_${counter++}`;
|
|
3826
|
+
const namedProxyHelper = `function __mfCreateNamedRemoteProxy(ns, key) {
|
|
3827
|
+
const target = function (...args) {
|
|
3828
|
+
const value = ns[key];
|
|
3829
|
+
return typeof value === "function" ? value.apply(this, args) : value;
|
|
3830
|
+
};
|
|
3831
|
+
return new Proxy(target, {
|
|
3832
|
+
get(_target, prop) {
|
|
3833
|
+
if (prop === "then") return undefined;
|
|
3834
|
+
const value = ns[key];
|
|
3835
|
+
if (prop === Symbol.toPrimitive) return () => value;
|
|
3836
|
+
const item = value == null ? undefined : value[prop];
|
|
3837
|
+
return typeof item === "function" ? item.bind(value) : item;
|
|
3838
|
+
},
|
|
3839
|
+
apply(target, thisArg, args) {
|
|
3840
|
+
return target.apply(thisArg, args);
|
|
3841
|
+
}
|
|
3842
|
+
});
|
|
3843
|
+
}`;
|
|
3844
|
+
const tempNames = imp.named.map((_s) => `__mf_named_${counter++}`);
|
|
3845
|
+
const destructParts = imp.named.map((s, index) => `${s.imported}: ${tempNames[index]}`);
|
|
3846
|
+
const bindingLines = imp.named.map((s, index) => {
|
|
3847
|
+
const temp = tempNames[index];
|
|
3848
|
+
return `const ${s.local} = ${isProxyId} ? __mfCreateNamedRemoteProxy(${nsId}, ${JSON.stringify(s.imported)}) : ${temp};`;
|
|
3849
|
+
});
|
|
3850
|
+
rewrite += `\n${namedProxyHelper}\nconst ${isProxyId} = ${nsId} && ${nsId}.__mf_is_remote_proxy;`;
|
|
3851
|
+
rewrite += `\nconst { ${destructParts.join(", ")} } = ${isProxyId} ? {} : ${nsId};`;
|
|
3852
|
+
rewrite += `\n${bindingLines.join("\n")}`;
|
|
3853
|
+
}
|
|
3846
3854
|
ms.overwrite(imp.start, imp.end, rewrite);
|
|
3847
3855
|
}
|
|
3848
3856
|
changed = true;
|
|
@@ -4118,6 +4126,205 @@ function pluginRemoteNamedExports(options) {
|
|
|
4118
4126
|
};
|
|
4119
4127
|
}
|
|
4120
4128
|
//#endregion
|
|
4129
|
+
//#region src/plugins/pluginSSRRemoteEntry.ts
|
|
4130
|
+
/**
|
|
4131
|
+
* Emits a Node-compatible SSR remote entry alongside the browser entry.
|
|
4132
|
+
*
|
|
4133
|
+
* Format strategy:
|
|
4134
|
+
* - Emit a dedicated ESM SSR entry alongside the browser entry.
|
|
4135
|
+
* - Keep the SSR entry out of the browser remote graph for Rollup builds by
|
|
4136
|
+
* emitting it as a generated asset.
|
|
4137
|
+
*
|
|
4138
|
+
* In both cases shared packages (react, react-dom, etc.) are marked as external
|
|
4139
|
+
* so Node resolves them through its own module cache, guaranteeing the singleton
|
|
4140
|
+
* is shared with react-dom/server.
|
|
4141
|
+
*/
|
|
4142
|
+
function pluginSSRRemoteEntry(options) {
|
|
4143
|
+
const remoteEntrySSRId = getRemoteEntrySSRId(options);
|
|
4144
|
+
const virtualExposesSSRId = getVirtualExposesSSRId(options);
|
|
4145
|
+
let isRolldown = false;
|
|
4146
|
+
let ssrOutputFilename = "";
|
|
4147
|
+
const ssrOnlyExternals = [
|
|
4148
|
+
"@module-federation/runtime",
|
|
4149
|
+
"@module-federation/runtime-core",
|
|
4150
|
+
"@module-federation/sdk",
|
|
4151
|
+
...options.ssrExternals ?? []
|
|
4152
|
+
];
|
|
4153
|
+
const ssrOnlyExternalPattern = new RegExp(`^(${ssrOnlyExternals.map((e) => e.replace(/[/\\^$*+?.()|[\]{}]/g, "\\$&")).join("|")})(\\/.*)?$`);
|
|
4154
|
+
const ssrModuleIds = new Set([remoteEntrySSRId, virtualExposesSSRId]);
|
|
4155
|
+
const resolvedAbsToPackage = /* @__PURE__ */ new Map();
|
|
4156
|
+
let isServe = false;
|
|
4157
|
+
let viteConfig;
|
|
4158
|
+
let isNuxtProject = false;
|
|
4159
|
+
const findNuxtExposesChunk = (bundle) => {
|
|
4160
|
+
const exposeKeys = Object.keys(options.exposes);
|
|
4161
|
+
if (exposeKeys.length === 0) return;
|
|
4162
|
+
return Object.values(bundle).find((file) => {
|
|
4163
|
+
if (file.type !== "chunk" || !file.fileName.startsWith("_nuxt/") || !file.fileName.endsWith(".js")) return false;
|
|
4164
|
+
const code = file.code || "";
|
|
4165
|
+
return exposeKeys.every((key) => code.includes(JSON.stringify(key)));
|
|
4166
|
+
})?.fileName;
|
|
4167
|
+
};
|
|
4168
|
+
return [{
|
|
4169
|
+
name: "mf:ssr-remote-entry:pre",
|
|
4170
|
+
enforce: "pre",
|
|
4171
|
+
configResolved(config) {
|
|
4172
|
+
isServe = config.command === "serve";
|
|
4173
|
+
for (const pkg of ssrOnlyExternals) {
|
|
4174
|
+
const aliasEntry = (config.resolve?.alias)?.find((a) => a.find === pkg || a.find instanceof RegExp && a.find.test(pkg));
|
|
4175
|
+
if (aliasEntry?.replacement) resolvedAbsToPackage.set(aliasEntry.replacement, pkg);
|
|
4176
|
+
}
|
|
4177
|
+
},
|
|
4178
|
+
resolveId(id, importer) {
|
|
4179
|
+
if (id === remoteEntrySSRId || id.startsWith(remoteEntrySSRId)) return id;
|
|
4180
|
+
if (id === virtualExposesSSRId || id.startsWith(virtualExposesSSRId)) return id;
|
|
4181
|
+
if (!importer || !ssrModuleIds.has(importer)) return;
|
|
4182
|
+
if (ssrOnlyExternalPattern.test(id)) return {
|
|
4183
|
+
id,
|
|
4184
|
+
external: true
|
|
4185
|
+
};
|
|
4186
|
+
const pkg = resolvedAbsToPackage.get(id);
|
|
4187
|
+
if (pkg) return {
|
|
4188
|
+
id: pkg,
|
|
4189
|
+
external: true
|
|
4190
|
+
};
|
|
4191
|
+
if (id.startsWith(".") || id.startsWith("/") || id.startsWith("file:")) return this.resolve(id, importer, { skipSelf: true }).then((resolved) => {
|
|
4192
|
+
if (resolved) ssrModuleIds.add(resolved.id);
|
|
4193
|
+
return resolved;
|
|
4194
|
+
});
|
|
4195
|
+
}
|
|
4196
|
+
}, {
|
|
4197
|
+
name: "mf:ssr-remote-entry",
|
|
4198
|
+
configResolved(config) {
|
|
4199
|
+
viteConfig = config;
|
|
4200
|
+
isNuxtProject = isNuxtProjectRoot(config.root);
|
|
4201
|
+
},
|
|
4202
|
+
configureServer(server) {
|
|
4203
|
+
const base = "/__mf_ssr__";
|
|
4204
|
+
const basePath = getBasePath(viteConfig?.base);
|
|
4205
|
+
const ssrEntryFileName = getSsrRemoteEntryFileName(options.filename);
|
|
4206
|
+
if (isNuxtProject || isNuxtClientBase(basePath)) server.middlewares.use((req, _res, next) => {
|
|
4207
|
+
if (req.url?.replace(/\?.*/, "") === `${basePath}/${ssrEntryFileName}`) req.url = `${basePath}/__mf_ssr__/${ssrEntryFileName}`;
|
|
4208
|
+
next();
|
|
4209
|
+
});
|
|
4210
|
+
const ssrEnv = server.environments?.ssr;
|
|
4211
|
+
const clientEnv = server.environments?.client;
|
|
4212
|
+
if (typeof (ssrEnv?.fetchModule ?? clientEnv?.fetchModule) === "function") server.middlewares.use("/__mf_runner__", async (req, res) => {
|
|
4213
|
+
res.setHeader("Access-Control-Allow-Origin", "*");
|
|
4214
|
+
if (req.method === "OPTIONS") {
|
|
4215
|
+
res.setHeader("Access-Control-Allow-Methods", "POST");
|
|
4216
|
+
res.setHeader("Access-Control-Allow-Headers", "Content-Type");
|
|
4217
|
+
res.statusCode = 204;
|
|
4218
|
+
res.end();
|
|
4219
|
+
return;
|
|
4220
|
+
}
|
|
4221
|
+
if (req.method !== "POST") {
|
|
4222
|
+
res.statusCode = 405;
|
|
4223
|
+
res.end("Method not allowed");
|
|
4224
|
+
return;
|
|
4225
|
+
}
|
|
4226
|
+
try {
|
|
4227
|
+
const chunks = [];
|
|
4228
|
+
await new Promise((resolve, reject) => {
|
|
4229
|
+
req.on("data", (chunk) => chunks.push(chunk));
|
|
4230
|
+
req.on("end", resolve);
|
|
4231
|
+
req.on("error", reject);
|
|
4232
|
+
});
|
|
4233
|
+
const body = JSON.parse(Buffer.concat(chunks).toString("utf8"));
|
|
4234
|
+
if (body.name === "getBuiltins") {
|
|
4235
|
+
const builtins = (clientEnv ?? ssrEnv)?.config?.resolve?.builtins ?? [];
|
|
4236
|
+
res.setHeader("Content-Type", "application/json");
|
|
4237
|
+
res.end(JSON.stringify({ result: builtins }));
|
|
4238
|
+
return;
|
|
4239
|
+
}
|
|
4240
|
+
if (body.name !== "fetchModule") {
|
|
4241
|
+
res.statusCode = 400;
|
|
4242
|
+
res.end(JSON.stringify({ error: { message: `Unsupported invoke: ${body.name}` } }));
|
|
4243
|
+
return;
|
|
4244
|
+
}
|
|
4245
|
+
const [id, importer, opts] = body.data;
|
|
4246
|
+
const fetchEnv = ssrEnv ?? clientEnv;
|
|
4247
|
+
const fetchFn = fetchEnv.fetchModule.bind(fetchEnv);
|
|
4248
|
+
let result;
|
|
4249
|
+
try {
|
|
4250
|
+
result = await fetchFn(id, importer, opts);
|
|
4251
|
+
} catch (fetchErr) {
|
|
4252
|
+
const bareId = id.startsWith("/@id/") ? id.slice(5).replace(/^__x00__/, "\0") : id;
|
|
4253
|
+
try {
|
|
4254
|
+
const { createRequire } = await import("module");
|
|
4255
|
+
const resolved = createRequire(new URL(`file://${server.config.root}/package.json`)).resolve(bareId.replace(/^\0/, ""));
|
|
4256
|
+
const { pathToFileURL } = await import("url");
|
|
4257
|
+
result = {
|
|
4258
|
+
externalize: pathToFileURL(resolved).href,
|
|
4259
|
+
type: "module"
|
|
4260
|
+
};
|
|
4261
|
+
} catch {
|
|
4262
|
+
throw fetchErr;
|
|
4263
|
+
}
|
|
4264
|
+
}
|
|
4265
|
+
res.setHeader("Content-Type", "application/json");
|
|
4266
|
+
res.end(JSON.stringify({ result }));
|
|
4267
|
+
} catch (e) {
|
|
4268
|
+
res.setHeader("Content-Type", "application/json");
|
|
4269
|
+
res.end(JSON.stringify({ error: { message: String(e instanceof Error ? e.message : e) } }));
|
|
4270
|
+
}
|
|
4271
|
+
});
|
|
4272
|
+
const ssrPath = `${base}/${ssrEntryFileName}`;
|
|
4273
|
+
server.middlewares.use(ssrPath, (_req, res) => {
|
|
4274
|
+
const exposesUrl = `${base}/${options.filename.replace(/\.[^.]+$/, "")}.exposes.js`;
|
|
4275
|
+
const code = generateRemoteEntrySSR(options).replace(JSON.stringify(virtualExposesSSRId), JSON.stringify(exposesUrl));
|
|
4276
|
+
res.setHeader("Content-Type", "application/javascript");
|
|
4277
|
+
res.setHeader("Access-Control-Allow-Origin", "*");
|
|
4278
|
+
res.end(code);
|
|
4279
|
+
});
|
|
4280
|
+
},
|
|
4281
|
+
resolveId(id) {
|
|
4282
|
+
if (id === remoteEntrySSRId || id.startsWith(remoteEntrySSRId)) return id;
|
|
4283
|
+
if (id === virtualExposesSSRId || id.startsWith(virtualExposesSSRId)) return id;
|
|
4284
|
+
if (id === `/__mf_ssr__/${getSsrRemoteEntryFileName(options.filename)}`) return remoteEntrySSRId;
|
|
4285
|
+
if (id === `/__mf_ssr__/${options.filename.replace(/\.[^.]+$/, "")}.exposes.js`) return virtualExposesSSRId;
|
|
4286
|
+
},
|
|
4287
|
+
load(id) {
|
|
4288
|
+
if (id === remoteEntrySSRId || id.startsWith(remoteEntrySSRId)) return generateRemoteEntrySSR(options);
|
|
4289
|
+
if (id === virtualExposesSSRId || id.startsWith(virtualExposesSSRId)) return generateExposesSSR(options);
|
|
4290
|
+
},
|
|
4291
|
+
buildStart() {
|
|
4292
|
+
if (isServe) return;
|
|
4293
|
+
isRolldown = getIsRolldown(this);
|
|
4294
|
+
ssrOutputFilename = getSsrRemoteEntryFileName(options.filename);
|
|
4295
|
+
const environmentName = this.environment?.name;
|
|
4296
|
+
const hasSsrEnvironment = Boolean(viteConfig?.environments?.ssr);
|
|
4297
|
+
const isLegacySsrBuild = Boolean(this.environment?.config?.build?.ssr);
|
|
4298
|
+
if (hasSsrEnvironment) {
|
|
4299
|
+
if (isNuxtProject) {
|
|
4300
|
+
if (environmentName === "ssr") return;
|
|
4301
|
+
} else if (environmentName !== "ssr") return;
|
|
4302
|
+
} else if (isLegacySsrBuild) {} else if (environmentName && environmentName !== "client") return;
|
|
4303
|
+
if (Object.keys(options.exposes).length === 0) return;
|
|
4304
|
+
if (isRolldown) this.emitFile({
|
|
4305
|
+
type: "chunk",
|
|
4306
|
+
id: remoteEntrySSRId,
|
|
4307
|
+
name: "ssrRemoteEntry",
|
|
4308
|
+
fileName: ssrOutputFilename,
|
|
4309
|
+
preserveSignature: "strict"
|
|
4310
|
+
});
|
|
4311
|
+
else this.emitFile({
|
|
4312
|
+
type: "asset",
|
|
4313
|
+
fileName: ssrOutputFilename,
|
|
4314
|
+
source: generateRemoteEntrySSR(options)
|
|
4315
|
+
});
|
|
4316
|
+
},
|
|
4317
|
+
generateBundle(_options, bundle) {
|
|
4318
|
+
const exposesChunk = findNuxtExposesChunk(bundle);
|
|
4319
|
+
const ssrAsset = bundle[ssrOutputFilename];
|
|
4320
|
+
if (exposesChunk && ssrAsset?.type === "asset" && typeof ssrAsset.source === "string") ssrAsset.source = ssrAsset.source.replace(/import\("virtual:mf-exposes-ssr:[^"]+"\)/g, `import("./${exposesChunk}")`);
|
|
4321
|
+
if (!isRolldown) return;
|
|
4322
|
+
const chunk = bundle[ssrOutputFilename];
|
|
4323
|
+
if (!chunk || chunk.type !== "chunk") return;
|
|
4324
|
+
}
|
|
4325
|
+
}];
|
|
4326
|
+
}
|
|
4327
|
+
//#endregion
|
|
4121
4328
|
//#region src/plugins/pluginVarRemoteEntry.ts
|
|
4122
4329
|
const VarRemoteEntry = () => {
|
|
4123
4330
|
const mfOptions = getNormalizeModuleFederationOptions();
|
|
@@ -4380,6 +4587,11 @@ function createEarlyVirtualModulesPlugin(options) {
|
|
|
4380
4587
|
const root = config.root || process.cwd();
|
|
4381
4588
|
setPackageDetectionCwd(root);
|
|
4382
4589
|
const isVinext = hasPackageDependency("vinext");
|
|
4590
|
+
setSsrRemotes(Object.entries(options.remotes).map(([key, r]) => ({
|
|
4591
|
+
name: key,
|
|
4592
|
+
entry: r.entry,
|
|
4593
|
+
type: r.type ?? "module"
|
|
4594
|
+
})));
|
|
4383
4595
|
initVirtualModules(_command, getRemoteEntryId(options));
|
|
4384
4596
|
const isRolldown = getIsRolldown(this);
|
|
4385
4597
|
if (remotes && Object.keys(remotes).length > 0) {
|
|
@@ -4489,16 +4701,56 @@ export default __mfShared.default ?? __mfShared;`
|
|
|
4489
4701
|
if (isLitShare(key)) optimizeDeps.exclude.push(key);
|
|
4490
4702
|
else optimizeDeps.include.push(key);
|
|
4491
4703
|
for (const subpath of getCommonSharedSubpaths(key)) {
|
|
4704
|
+
getLoadShareModulePath(subpath, isRolldown);
|
|
4705
|
+
writeLoadShareModule(subpath, shareItem, _command, isRolldown);
|
|
4492
4706
|
writePreBuildLibPath(subpath, shareItem);
|
|
4707
|
+
addUsedShares(subpath);
|
|
4493
4708
|
optimizeDeps.include.push(subpath);
|
|
4494
4709
|
}
|
|
4495
4710
|
}
|
|
4496
4711
|
}
|
|
4497
4712
|
writeLocalSharedImportMap();
|
|
4498
4713
|
}
|
|
4714
|
+
},
|
|
4715
|
+
configResolved(config) {
|
|
4716
|
+
if (parseInt(version, 10) < 8) return;
|
|
4717
|
+
if (!(Object.keys(options.exposes).length > 0 || Object.keys(options.remotes).length > 0)) return;
|
|
4718
|
+
if (options.runtimePlugins.some((p) => {
|
|
4719
|
+
return (typeof p === "string" ? p : p[0]) === "@module-federation/vite/ssrEntryLoader";
|
|
4720
|
+
})) return;
|
|
4721
|
+
const pluginRequire = createRequire(import.meta.url);
|
|
4722
|
+
const projectRequire = createRequire(new URL(`file://${config.root}/package.json`));
|
|
4723
|
+
const sharedKeys = Object.keys(options.shared ?? {});
|
|
4724
|
+
const commonSharedPkgs = [
|
|
4725
|
+
"react",
|
|
4726
|
+
"react-dom",
|
|
4727
|
+
"react/jsx-runtime",
|
|
4728
|
+
"react/jsx-dev-runtime",
|
|
4729
|
+
"@module-federation/runtime",
|
|
4730
|
+
"@module-federation/runtime-core",
|
|
4731
|
+
"@module-federation/sdk"
|
|
4732
|
+
];
|
|
4733
|
+
const resolvedShared = {};
|
|
4734
|
+
for (const pkg of [...commonSharedPkgs, ...sharedKeys]) try {
|
|
4735
|
+
resolvedShared[pkg] = projectRequire.resolve(pkg);
|
|
4736
|
+
} catch {
|
|
4737
|
+
try {
|
|
4738
|
+
resolvedShared[pkg] = pluginRequire.resolve(pkg);
|
|
4739
|
+
} catch {}
|
|
4740
|
+
}
|
|
4741
|
+
const ssrEntryLoaderSpecifier = "@module-federation/vite/ssrEntryLoader";
|
|
4742
|
+
try {
|
|
4743
|
+
pluginRequire.resolve(ssrEntryLoaderSpecifier);
|
|
4744
|
+
options.runtimePlugins.push([ssrEntryLoaderSpecifier, { resolvedShared }]);
|
|
4745
|
+
} catch {}
|
|
4499
4746
|
}
|
|
4500
4747
|
};
|
|
4501
4748
|
}
|
|
4749
|
+
const SSR_ONLY_PLUGINS = new Set(["@module-federation/vite/ssrEntryLoader"]);
|
|
4750
|
+
function loadPluginDts(options) {
|
|
4751
|
+
if (options.dts === false) return [];
|
|
4752
|
+
return [import("./pluginDts-B5pcUmam.mjs").then(({ default: pluginDts }) => pluginDts(options))];
|
|
4753
|
+
}
|
|
4502
4754
|
function federation(mfUserOptions) {
|
|
4503
4755
|
if (isTestEnv()) return [];
|
|
4504
4756
|
const options = normalizeModuleFederationOptions(mfUserOptions);
|
|
@@ -4539,7 +4791,7 @@ function federation(mfUserOptions) {
|
|
|
4539
4791
|
const environmentName = this.environment?.name;
|
|
4540
4792
|
if (!environmentName || environmentName === "client") return;
|
|
4541
4793
|
const target = reactServerEntryMap[id];
|
|
4542
|
-
const reactPackageJson = createRequire
|
|
4794
|
+
const reactPackageJson = createRequire(new URL(`file://${process.cwd()}/package.json`)).resolve("react/package.json");
|
|
4543
4795
|
return path.join(path.dirname(reactPackageJson), target.replace(/^react\//, ""));
|
|
4544
4796
|
}
|
|
4545
4797
|
}] : [],
|
|
@@ -4550,14 +4802,26 @@ function federation(mfUserOptions) {
|
|
|
4550
4802
|
command = env.command;
|
|
4551
4803
|
},
|
|
4552
4804
|
configResolved() {
|
|
4553
|
-
initVirtualModules(command, remoteEntryId);
|
|
4805
|
+
initVirtualModules(command, remoteEntryId, parseInt(version, 10) >= 8);
|
|
4554
4806
|
}
|
|
4555
4807
|
},
|
|
4556
4808
|
aliasToArrayPlugin_default,
|
|
4557
4809
|
checkAliasConflicts({ shared }),
|
|
4558
4810
|
normalizeOptimizeDeps_default,
|
|
4559
|
-
...
|
|
4811
|
+
...loadPluginDts(options),
|
|
4560
4812
|
pluginDevRemoteHmr(options),
|
|
4813
|
+
{
|
|
4814
|
+
name: "mf:normalize-entry-chunks",
|
|
4815
|
+
enforce: "pre",
|
|
4816
|
+
apply: "build",
|
|
4817
|
+
generateBundle(_options, bundle) {
|
|
4818
|
+
for (const chunk of Object.values(bundle)) {
|
|
4819
|
+
if (typeof chunk !== "object" || chunk === null || chunk.type !== "chunk" || !chunk.isEntry) continue;
|
|
4820
|
+
const facadeId = chunk.facadeModuleId ?? "";
|
|
4821
|
+
if (facadeId.includes("__mf__virtual") || facadeId.startsWith("virtual:mf-") || facadeId.startsWith("virtual:mf:") || facadeId.startsWith("\0virtual:mf-") || facadeId.startsWith("\0virtual:mf:")) chunk.isEntry = false;
|
|
4822
|
+
}
|
|
4823
|
+
}
|
|
4824
|
+
},
|
|
4561
4825
|
...addEntry({
|
|
4562
4826
|
entryName: "remoteEntry",
|
|
4563
4827
|
entryPath: remoteEntryId,
|
|
@@ -4567,7 +4831,8 @@ function federation(mfUserOptions) {
|
|
|
4567
4831
|
entryName: "hostInit",
|
|
4568
4832
|
entryPath: () => getHostAutoInitPath(),
|
|
4569
4833
|
inject: hostInitInjectLocation,
|
|
4570
|
-
forceClientInjected: Object.keys(options.exposes).length > 0
|
|
4834
|
+
forceClientInjected: Object.keys(options.exposes).length > 0,
|
|
4835
|
+
skipTransformFor: Object.values(options.exposes).map((expose) => expose.import)
|
|
4571
4836
|
}),
|
|
4572
4837
|
...addEntry({
|
|
4573
4838
|
entryName: "virtualExposes",
|
|
@@ -4712,7 +4977,7 @@ function federation(mfUserOptions) {
|
|
|
4712
4977
|
*
|
|
4713
4978
|
* @see https://rollupjs.org/plugin-development/#synthetic-named-exports
|
|
4714
4979
|
*/
|
|
4715
|
-
if (!/\bexport\s+const\s+__moduleExports\b/.test(code)) {
|
|
4980
|
+
if (!/\bexport\s+const\s+__moduleExports\b/.test(code) && !/\bexport\s*\{[^}]*__moduleExports/.test(code)) {
|
|
4716
4981
|
const nextCode = code.replace("export default exportModule", "export const __moduleExports = exportModule;\nexport default exportModule.__esModule ? exportModule.default : exportModule");
|
|
4717
4982
|
code = nextCode === code ? `${code}\nexport const __moduleExports = exportModule;\n` : nextCode;
|
|
4718
4983
|
}
|
|
@@ -4785,6 +5050,7 @@ function federation(mfUserOptions) {
|
|
|
4785
5050
|
config.optimizeDeps.include.push("@module-federation/runtime");
|
|
4786
5051
|
options.runtimePlugins.forEach((p) => {
|
|
4787
5052
|
const pluginPath = typeof p === "string" ? p : p[0];
|
|
5053
|
+
if (SSR_ONLY_PLUGINS.has(pluginPath)) return;
|
|
4788
5054
|
if (pluginPath && !pluginPath.startsWith(".") && !pluginPath.startsWith("/") && !pluginPath.startsWith("\0") && !pluginPath.startsWith("virtual:")) config.optimizeDeps.include.push(pluginPath);
|
|
4789
5055
|
});
|
|
4790
5056
|
if (isRolldown) {
|
|
@@ -4800,6 +5066,7 @@ function federation(mfUserOptions) {
|
|
|
4800
5066
|
}
|
|
4801
5067
|
},
|
|
4802
5068
|
...Manifest(),
|
|
5069
|
+
...pluginSSRRemoteEntry(options),
|
|
4803
5070
|
...VarRemoteEntry(),
|
|
4804
5071
|
{
|
|
4805
5072
|
name: "module-federation-vinext-fix-rsc-preload-as",
|
|
@@ -4873,5 +5140,8 @@ function federation(mfUserOptions) {
|
|
|
4873
5140
|
})()
|
|
4874
5141
|
];
|
|
4875
5142
|
}
|
|
5143
|
+
function createModuleFederationConfig(options) {
|
|
5144
|
+
return options;
|
|
5145
|
+
}
|
|
4876
5146
|
//#endregion
|
|
4877
|
-
export { federation };
|
|
5147
|
+
export { createModuleFederationConfig, federation };
|