@module-federation/vite 1.15.5 → 1.16.1
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 +27 -0
- package/lib/index.cjs +1130 -887
- package/lib/index.d.cts +22 -6
- package/lib/index.d.mts +22 -6
- package/lib/index.mjs +1074 -813
- 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 +12 -4
package/lib/index.mjs
CHANGED
|
@@ -1,17 +1,13 @@
|
|
|
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, 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 {
|
|
9
|
-
import { rpc } from "@module-federation/dts-plugin/core";
|
|
7
|
+
import { version } from "vite";
|
|
8
|
+
import { createHash } from "node:crypto";
|
|
10
9
|
import { fileURLToPath } from "url";
|
|
11
10
|
import { init, parse } from "es-module-lexer";
|
|
12
|
-
//#region \0rolldown/runtime.js
|
|
13
|
-
var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
14
|
-
//#endregion
|
|
15
11
|
//#region src/utils/codeRewriter.ts
|
|
16
12
|
const BASE64_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
|
17
13
|
var CodeRewriter = class {
|
|
@@ -173,237 +169,6 @@ function injectEntryScript(html, initSrc) {
|
|
|
173
169
|
return html.replace("<head>", `<head><script type="module" src=${JSON.stringify(src)}><\/script>`);
|
|
174
170
|
}
|
|
175
171
|
//#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
172
|
//#region src/utils/normalizeModuleFederationOptions.ts
|
|
408
173
|
const INTERNAL_NAME_PREFIX = "__mfe_internal__";
|
|
409
174
|
function toInternalModuleFederationName(name) {
|
|
@@ -475,7 +240,7 @@ function normalizeRemoteItem(key, remote) {
|
|
|
475
240
|
* @returns {string | undefined}
|
|
476
241
|
*/
|
|
477
242
|
function searchPackageVersion(sharedName) {
|
|
478
|
-
const version = getInstalledPackageJson(sharedName
|
|
243
|
+
const version = getInstalledPackageJson(sharedName)?.packageJson.version;
|
|
479
244
|
return typeof version === "string" ? version : void 0;
|
|
480
245
|
}
|
|
481
246
|
function inferVersionFromRequiredVersion(requiredVersion) {
|
|
@@ -489,22 +254,8 @@ function getLitExportSubpathShares(sharedName) {
|
|
|
489
254
|
return Object.keys(exportsField).filter((key) => key.startsWith("./") && key !== "." && !key.includes("*")).map((key) => `${sharedName}/${key.slice(2)}`);
|
|
490
255
|
}
|
|
491
256
|
function normalizeShareItem(key, shareItem) {
|
|
492
|
-
let version;
|
|
493
257
|
const isImportFalse = typeof shareItem === "object" && shareItem.import === false;
|
|
494
|
-
|
|
495
|
-
try {
|
|
496
|
-
version = __require(path$1.join(getPackageName(key), "package.json")).version;
|
|
497
|
-
} catch (e1) {
|
|
498
|
-
try {
|
|
499
|
-
version = __require(path$1.join(process.cwd(), "node_modules", getPackageName(key), "package.json")).version;
|
|
500
|
-
} catch (e2) {
|
|
501
|
-
version = searchPackageVersion(key);
|
|
502
|
-
if (!version && !isImportFalse) mfError(e1);
|
|
503
|
-
}
|
|
504
|
-
}
|
|
505
|
-
} catch (e) {
|
|
506
|
-
if (!isImportFalse) mfError(`Unexpected error resolving version for ${key}:`, e);
|
|
507
|
-
}
|
|
258
|
+
const version = (typeof shareItem === "object" ? shareItem.version || inferVersionFromRequiredVersion(shareItem.requiredVersion) : void 0) || searchPackageVersion(key);
|
|
508
259
|
if (typeof shareItem === "string") return {
|
|
509
260
|
name: shareItem,
|
|
510
261
|
version,
|
|
@@ -519,12 +270,12 @@ function normalizeShareItem(key, shareItem) {
|
|
|
519
270
|
return {
|
|
520
271
|
name: key,
|
|
521
272
|
from: "",
|
|
522
|
-
version
|
|
273
|
+
version,
|
|
523
274
|
scope: shareItem.shareScope || "default",
|
|
524
275
|
shareConfig: {
|
|
525
276
|
import: shareItem.import,
|
|
526
277
|
singleton: shareItem.singleton || false,
|
|
527
|
-
requiredVersion: shareItem.requiredVersion || (isImportFalse ? "*" : version ? `^${version}` : "*"),
|
|
278
|
+
requiredVersion: shareItem.requiredVersion || (isImportFalse || shareItem.version ? "*" : version ? `^${version}` : "*"),
|
|
528
279
|
strictVersion: !!shareItem.strictVersion
|
|
529
280
|
}
|
|
530
281
|
};
|
|
@@ -552,11 +303,13 @@ function normalizeShared(shared) {
|
|
|
552
303
|
const result = {};
|
|
553
304
|
const sourceEntries = [];
|
|
554
305
|
if (Array.isArray(shared)) shared.forEach((key) => {
|
|
306
|
+
if (isModuleFederationRuntimePackage(key)) return;
|
|
555
307
|
result[key] = normalizeShareItem(key, key);
|
|
556
308
|
explicitSharedKeys.add(key);
|
|
557
309
|
sourceEntries.push([key, key]);
|
|
558
310
|
});
|
|
559
311
|
else if (typeof shared === "object") Object.keys(shared).forEach((key) => {
|
|
312
|
+
if (isModuleFederationRuntimePackage(key)) return;
|
|
560
313
|
const value = shared[key];
|
|
561
314
|
result[key] = normalizeShareItem(key, value);
|
|
562
315
|
explicitSharedKeys.add(key);
|
|
@@ -570,11 +323,14 @@ function normalizeShared(shared) {
|
|
|
570
323
|
});
|
|
571
324
|
return result;
|
|
572
325
|
}
|
|
326
|
+
function isModuleFederationRuntimePackage(key) {
|
|
327
|
+
return key === "@module-federation/runtime" || key === "@module-federation/runtime-core";
|
|
328
|
+
}
|
|
573
329
|
function shouldAutoShareDependency(key) {
|
|
574
330
|
const pkg = getInstalledPackageJson(key)?.packageJson;
|
|
575
331
|
if (!pkg) return false;
|
|
576
332
|
if (!pkg.browser && !pkg.exports && typeof pkg.module !== "string") return false;
|
|
577
|
-
if (key === "@module-federation/vite") return false;
|
|
333
|
+
if (key === "@module-federation/vite" || isModuleFederationRuntimePackage(key)) return false;
|
|
578
334
|
const entry = [
|
|
579
335
|
pkg.module,
|
|
580
336
|
pkg.main,
|
|
@@ -598,6 +354,18 @@ function normalizeManifest(manifest) {
|
|
|
598
354
|
}
|
|
599
355
|
let config;
|
|
600
356
|
let explicitSharedKeys = /* @__PURE__ */ new Set();
|
|
357
|
+
function resolveRuntimeImplementation() {
|
|
358
|
+
const fallback = __require.resolve("@module-federation/runtime");
|
|
359
|
+
try {
|
|
360
|
+
const packageJsonPath = __require.resolve("@module-federation/runtime/package.json");
|
|
361
|
+
const packageJson = JSON.parse(fs$1.readFileSync(packageJsonPath, "utf-8"));
|
|
362
|
+
const importExport = packageJson.exports?.["."];
|
|
363
|
+
const exportImport = typeof importExport === "object" ? typeof importExport.import === "string" ? importExport.import : importExport.import?.default : void 0;
|
|
364
|
+
const esmEntry = packageJson.module || exportImport;
|
|
365
|
+
if (esmEntry) return path$1.join(path$1.dirname(packageJsonPath), esmEntry);
|
|
366
|
+
} catch {}
|
|
367
|
+
return fallback;
|
|
368
|
+
}
|
|
601
369
|
function getNormalizeModuleFederationOptions() {
|
|
602
370
|
return config;
|
|
603
371
|
}
|
|
@@ -622,7 +390,7 @@ function normalizeModuleFederationOptions(options) {
|
|
|
622
390
|
shareScope: options.shareScope || "default",
|
|
623
391
|
shared: normalizeShared(options.shared),
|
|
624
392
|
runtimePlugins: options.runtimePlugins || [],
|
|
625
|
-
implementation: options.implementation ||
|
|
393
|
+
implementation: options.implementation || resolveRuntimeImplementation(),
|
|
626
394
|
manifest: normalizeManifest(options.manifest),
|
|
627
395
|
dev: options.dev,
|
|
628
396
|
dts: options.dts,
|
|
@@ -836,11 +604,24 @@ function getDeferredInitPromiseCode() {
|
|
|
836
604
|
initReject = rj;
|
|
837
605
|
});`;
|
|
838
606
|
}
|
|
839
|
-
|
|
607
|
+
let _ssrRemotes = [];
|
|
608
|
+
function setSsrRemotes(remotes) {
|
|
609
|
+
_ssrRemotes = remotes;
|
|
610
|
+
}
|
|
611
|
+
function getSsrNoopResolveCode(enableSsrInit) {
|
|
612
|
+
if (!enableSsrInit) return "";
|
|
840
613
|
return `if (typeof window === 'undefined') {
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
614
|
+
var _noop = { loadRemote: function() { return Promise.resolve(undefined); }, loadShare: function() { return Promise.resolve(undefined); } };
|
|
615
|
+
import(/* @vite-ignore */ '@module-federation/runtime').then(function(runtimeMod) {
|
|
616
|
+
return import(/* @vite-ignore */ '@module-federation/vite/ssrEntryLoader').then(
|
|
617
|
+
function(loaderMod) { return [runtimeMod, [loaderMod.default()]]; },
|
|
618
|
+
function() { return [runtimeMod, []]; }
|
|
619
|
+
);
|
|
620
|
+
}).then(function(pair) {
|
|
621
|
+
var runtime = pair[0].init({ name: '__mf_ssr_host__', remotes: ${JSON.stringify(_ssrRemotes)}, shared: {}, plugins: pair[1] });
|
|
622
|
+
initResolve(runtime);
|
|
623
|
+
}, function() {
|
|
624
|
+
initResolve(_noop);
|
|
844
625
|
});
|
|
845
626
|
}`;
|
|
846
627
|
}
|
|
@@ -855,12 +636,12 @@ if (!${options.stateVar}) {
|
|
|
855
636
|
initResolve,
|
|
856
637
|
initReject,
|
|
857
638
|
};
|
|
858
|
-
${getSsrNoopResolveCode()}
|
|
639
|
+
${getSsrNoopResolveCode(options.enableSsrInit)}
|
|
859
640
|
}
|
|
860
641
|
const ${options.exposedConst} = ${options.stateVar}.${options.exposedProperty};
|
|
861
642
|
`;
|
|
862
643
|
}
|
|
863
|
-
function getRuntimeInitBootstrapCode() {
|
|
644
|
+
function getRuntimeInitBootstrapCode(enableSsrInit = false) {
|
|
864
645
|
return `
|
|
865
646
|
const globalKey = ${JSON.stringify(getRuntimeInitGlobalKey())};
|
|
866
647
|
const moduleCacheGlobalKey = ${JSON.stringify(MODULE_CACHE_GLOBAL_KEY)};
|
|
@@ -875,7 +656,7 @@ if (!globalThis[globalKey]) {
|
|
|
875
656
|
initReject,
|
|
876
657
|
moduleCache: globalThis[moduleCacheGlobalKey],
|
|
877
658
|
};
|
|
878
|
-
${getSsrNoopResolveCode()}
|
|
659
|
+
${getSsrNoopResolveCode(enableSsrInit)}
|
|
879
660
|
}
|
|
880
661
|
globalThis[globalKey].moduleCache ||= globalThis[moduleCacheGlobalKey];
|
|
881
662
|
globalThis[globalKey].moduleCache.share ||= {};
|
|
@@ -891,27 +672,29 @@ globalThis[__mfCacheGlobalKey].remote ||= {};
|
|
|
891
672
|
const __mfModuleCache = globalThis[__mfCacheGlobalKey];
|
|
892
673
|
`;
|
|
893
674
|
}
|
|
894
|
-
function getRuntimeInitPromiseBootstrapCode() {
|
|
675
|
+
function getRuntimeInitPromiseBootstrapCode(enableSsrInit = false) {
|
|
895
676
|
return getRuntimeInitStateBootstrapCode({
|
|
896
677
|
globalKeyVar: "__mfPromiseGlobalKey",
|
|
897
678
|
stateVar: "__mfPromiseState",
|
|
898
679
|
exposedConst: "initPromise",
|
|
899
|
-
exposedProperty: "initPromise"
|
|
680
|
+
exposedProperty: "initPromise",
|
|
681
|
+
enableSsrInit
|
|
900
682
|
});
|
|
901
683
|
}
|
|
902
|
-
function getRuntimeInitResolveBootstrapCode() {
|
|
684
|
+
function getRuntimeInitResolveBootstrapCode(enableSsrInit = false) {
|
|
903
685
|
return getRuntimeInitStateBootstrapCode({
|
|
904
686
|
globalKeyVar: "__mfResolveGlobalKey",
|
|
905
687
|
stateVar: "__mfResolveState",
|
|
906
688
|
exposedConst: "initResolve",
|
|
907
|
-
exposedProperty: "initResolve"
|
|
689
|
+
exposedProperty: "initResolve",
|
|
690
|
+
enableSsrInit
|
|
908
691
|
});
|
|
909
692
|
}
|
|
910
|
-
function writeRuntimeInitStatus(command) {
|
|
693
|
+
function writeRuntimeInitStatus(command, enableSsrInit = false) {
|
|
911
694
|
const exportStatement = command === "build" ? `const { initPromise, initResolve, initReject, moduleCache } = globalThis[globalKey];
|
|
912
695
|
export { initPromise, initResolve, initReject, moduleCache };` : `module.exports = globalThis[globalKey];`;
|
|
913
696
|
virtualRuntimeInitStatus.writeSync(`
|
|
914
|
-
${getRuntimeInitBootstrapCode()}
|
|
697
|
+
${getRuntimeInitBootstrapCode(enableSsrInit)}
|
|
915
698
|
${exportStatement}
|
|
916
699
|
`);
|
|
917
700
|
}
|
|
@@ -946,10 +729,10 @@ function isValidEsmExportName(name) {
|
|
|
946
729
|
return !!name && name !== "default" && name !== "__esModule" && isValidJsIdentifier(name);
|
|
947
730
|
}
|
|
948
731
|
const JS_IDENTIFIER_PATTERN = `[\$_\\p{ID_Start}][\$_\\u200C\\u200D\\p{ID_Continue}]*`;
|
|
949
|
-
const localRequire = createRequire
|
|
732
|
+
const localRequire = createRequire(import.meta.url);
|
|
950
733
|
function resolvePackageEntryFromProjectRoot(pkg) {
|
|
951
734
|
try {
|
|
952
|
-
return createRequire
|
|
735
|
+
return createRequire(new URL(`file://${path.join(getPackageDetectionCwd(), "package.json")}`)).resolve(pkg);
|
|
953
736
|
} catch {
|
|
954
737
|
return;
|
|
955
738
|
}
|
|
@@ -1047,7 +830,7 @@ function getNamedExportsViaRegex(source, filePath, visited) {
|
|
|
1047
830
|
}
|
|
1048
831
|
function getPackageNamedExports(pkg) {
|
|
1049
832
|
try {
|
|
1050
|
-
const mod = createRequire
|
|
833
|
+
const mod = createRequire(new URL(`file://${path.join(getPackageDetectionCwd(), "package.json")}`))(pkg);
|
|
1051
834
|
return Object.keys(mod).filter((k) => isValidEsmExportName(k));
|
|
1052
835
|
} catch {
|
|
1053
836
|
return getEsmNamedExports(pkg);
|
|
@@ -1055,7 +838,7 @@ function getPackageNamedExports(pkg) {
|
|
|
1055
838
|
}
|
|
1056
839
|
function getLocalProviderImportPath(pkg) {
|
|
1057
840
|
try {
|
|
1058
|
-
const resolved = createRequire
|
|
841
|
+
const resolved = createRequire(new URL(`file://${path.join(getPackageDetectionCwd(), "package.json")}`)).resolve(pkg);
|
|
1059
842
|
return isWorkspaceFilePath(resolved) ? resolved : void 0;
|
|
1060
843
|
} catch {
|
|
1061
844
|
const resolved = getInstalledPackageEntry(pkg, {
|
|
@@ -1076,7 +859,7 @@ function getProjectResolvedImportPath(pkg) {
|
|
|
1076
859
|
if (esmEntry) return esmEntry;
|
|
1077
860
|
}
|
|
1078
861
|
try {
|
|
1079
|
-
return createRequire
|
|
862
|
+
return createRequire(new URL(`file://${path.join(getPackageDetectionCwd(), "package.json")}`)).resolve(pkg);
|
|
1080
863
|
} catch {
|
|
1081
864
|
return;
|
|
1082
865
|
}
|
|
@@ -1098,7 +881,7 @@ function isWorkspacePackageEntry(pkg, resolved) {
|
|
|
1098
881
|
}
|
|
1099
882
|
function tryResolveImportFromPackageRoot(pkg, root) {
|
|
1100
883
|
try {
|
|
1101
|
-
return createRequire
|
|
884
|
+
return createRequire(new URL(`file://${path.join(root, "package.json")}`)).resolve(pkg);
|
|
1102
885
|
} catch {
|
|
1103
886
|
return;
|
|
1104
887
|
}
|
|
@@ -1451,7 +1234,7 @@ const normalizeRuntimeShareCode = `const __mfNormalizeRuntimeShare = (mod) => {
|
|
|
1451
1234
|
let current = mod;
|
|
1452
1235
|
for (let i = 0; i < 5; i++) {
|
|
1453
1236
|
const defaultExport = current?.default;
|
|
1454
|
-
if (!defaultExport || typeof defaultExport !== "object") break;
|
|
1237
|
+
if (!defaultExport || typeof defaultExport !== "object" || Object.keys(defaultExport).length === 0) break;
|
|
1455
1238
|
const namedValues = Object.keys(current).filter((key) => key !== "default").map((key) => current[key]);
|
|
1456
1239
|
if (namedValues.length > 0 && namedValues.some((value) => value !== undefined)) break;
|
|
1457
1240
|
current = defaultExport;
|
|
@@ -1491,6 +1274,9 @@ const REMOTE_ENTRY_ID = "virtual:mf-REMOTE_ENTRY_ID";
|
|
|
1491
1274
|
function getRemoteEntryId(options) {
|
|
1492
1275
|
return `${REMOTE_ENTRY_ID}:${`${options.internalName}__${options.filename}`.replace(/[^a-zA-Z0-9_-]/g, "_")}`;
|
|
1493
1276
|
}
|
|
1277
|
+
const SSR_ONLY_PLUGIN_SPECIFIERS = new Set(["@module-federation/vite/ssrEntryLoader"]);
|
|
1278
|
+
const isSsrOnlyPlugin = (importStatement) => [...SSR_ONLY_PLUGIN_SPECIFIERS].some((s) => importStatement.includes(s));
|
|
1279
|
+
const getSsrOnlyPluginSpecifier = (importStatement) => [...SSR_ONLY_PLUGIN_SPECIFIERS].find((s) => importStatement.includes(s));
|
|
1494
1280
|
function generateRemoteEntry(options, virtualExposesId = getVirtualExposesId(options), command = "build") {
|
|
1495
1281
|
const pluginImportNames = options.runtimePlugins.map((p, i) => {
|
|
1496
1282
|
if (typeof p === "string") return [
|
|
@@ -1514,7 +1300,7 @@ function generateRemoteEntry(options, virtualExposesId = getVirtualExposesId(opt
|
|
|
1514
1300
|
globalThis.__VUE_HMR_RUNTIME__ = { createRecord() {}, rerender() {}, reload() {} };
|
|
1515
1301
|
}
|
|
1516
1302
|
import {init as runtimeInit, loadRemote} from "@module-federation/runtime";
|
|
1517
|
-
${pluginImportNames.map((item) => item[1]).join("\n")}
|
|
1303
|
+
${pluginImportNames.filter((item) => !isSsrOnlyPlugin(item[1])).map((item) => item[1]).join("\n")}
|
|
1518
1304
|
${command === "build" ? getRuntimeInitResolveBootstrapCode() : getRuntimeInitBootstrapCode() + "\n const { initResolve } = globalThis[globalKey];"}
|
|
1519
1305
|
${getRuntimeModuleCacheBootstrapCode()}
|
|
1520
1306
|
const initTokens = {}
|
|
@@ -1562,11 +1348,19 @@ function generateRemoteEntry(options, virtualExposesId = getVirtualExposesId(opt
|
|
|
1562
1348
|
async function init(shared = {}, initScope = []) {
|
|
1563
1349
|
const {usedShared, usedRemotes} = await getLocalSharedImportMap()
|
|
1564
1350
|
${generateDirectSharedCacheSeedCode(command)}
|
|
1351
|
+
const __browserPlugins = [${pluginImportNames.filter((item) => !isSsrOnlyPlugin(item[1])).map((item) => `${item[0]}(${item[2]})`).join(", ")}];
|
|
1352
|
+
const __ssrPlugins = typeof globalThis.window === 'undefined'
|
|
1353
|
+
? await Promise.all([${pluginImportNames.filter((item) => isSsrOnlyPlugin(item[1])).map((item) => {
|
|
1354
|
+
const specifier = getSsrOnlyPluginSpecifier(item[1]);
|
|
1355
|
+
const opts = item[2];
|
|
1356
|
+
return `import(${JSON.stringify(specifier)}).then(m => (m.default ?? m)(${opts}))`;
|
|
1357
|
+
}).join(", ")}])
|
|
1358
|
+
: [];
|
|
1565
1359
|
const initRes = runtimeInit({
|
|
1566
1360
|
name: mfName,
|
|
1567
|
-
remotes: usedRemotes,
|
|
1361
|
+
remotes: ${options.shareStrategy === "loaded-first" ? "[]" : "usedRemotes"},
|
|
1568
1362
|
shared: usedShared,
|
|
1569
|
-
plugins: [
|
|
1363
|
+
plugins: [...__browserPlugins, ...__ssrPlugins],
|
|
1570
1364
|
${options.shareStrategy ? `shareStrategy: '${options.shareStrategy}'` : ""}
|
|
1571
1365
|
});
|
|
1572
1366
|
// handling circular init calls
|
|
@@ -1617,6 +1411,7 @@ const hostAutoInitModule = new VirtualModule("hostAutoInit", "__H_A_I__");
|
|
|
1617
1411
|
let currentHostAutoInitRemoteEntryId = REMOTE_ENTRY_ID;
|
|
1618
1412
|
let currentHostAutoInitCommand = "build";
|
|
1619
1413
|
function generateHostAutoInitCode(remoteEntryImport, _command = "build") {
|
|
1414
|
+
const shouldPreloadShares = getNormalizeModuleFederationOptions().shareStrategy !== "loaded-first";
|
|
1620
1415
|
return `
|
|
1621
1416
|
${getRuntimeModuleCacheBootstrapCode()}
|
|
1622
1417
|
let hostInitPromise;
|
|
@@ -1628,6 +1423,7 @@ function generateHostAutoInitCode(remoteEntryImport, _command = "build") {
|
|
|
1628
1423
|
const runtime = await remoteEntry.init();
|
|
1629
1424
|
const usedShared = ${generateUsedSharedPreloadConfig()};
|
|
1630
1425
|
${normalizeRuntimeShareCode}
|
|
1426
|
+
${shouldPreloadShares ? `
|
|
1631
1427
|
for (const [pkg, share] of Object.entries(usedShared)) {
|
|
1632
1428
|
if (__mfModuleCache.share[pkg] !== undefined) {
|
|
1633
1429
|
continue;
|
|
@@ -1641,6 +1437,7 @@ function generateHostAutoInitCode(remoteEntryImport, _command = "build") {
|
|
|
1641
1437
|
});
|
|
1642
1438
|
});
|
|
1643
1439
|
}
|
|
1440
|
+
` : ""}
|
|
1644
1441
|
const __mfRemotePreloads = [];
|
|
1645
1442
|
await Promise.all(__mfRemotePreloads);
|
|
1646
1443
|
return runtime;
|
|
@@ -1672,12 +1469,13 @@ function getHostAutoInitPath() {
|
|
|
1672
1469
|
//#region src/virtualModules/virtualRemotes.ts
|
|
1673
1470
|
const cacheRemoteMap = {};
|
|
1674
1471
|
const LOAD_REMOTE_TAG = "__loadRemote__";
|
|
1675
|
-
function getRemoteVirtualModule(remote, command) {
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
cacheRemoteMap[
|
|
1472
|
+
function getRemoteVirtualModule(remote, command, enableSsrInit = false) {
|
|
1473
|
+
const cacheKey = `${remote}__${command}__${enableSsrInit ? "ssr" : "no-ssr"}`;
|
|
1474
|
+
if (!cacheRemoteMap[cacheKey]) {
|
|
1475
|
+
cacheRemoteMap[cacheKey] = new VirtualModule(remote, LOAD_REMOTE_TAG, ".mjs");
|
|
1476
|
+
cacheRemoteMap[cacheKey].writeSync(generateRemotes(remote, command, enableSsrInit));
|
|
1679
1477
|
}
|
|
1680
|
-
return cacheRemoteMap[
|
|
1478
|
+
return cacheRemoteMap[cacheKey];
|
|
1681
1479
|
}
|
|
1682
1480
|
const usedRemotesMap = {};
|
|
1683
1481
|
function addUsedRemote(remoteKey, remoteModule) {
|
|
@@ -1687,40 +1485,93 @@ function addUsedRemote(remoteKey, remoteModule) {
|
|
|
1687
1485
|
function getUsedRemotesMap() {
|
|
1688
1486
|
return usedRemotesMap;
|
|
1689
1487
|
}
|
|
1690
|
-
function
|
|
1691
|
-
const
|
|
1488
|
+
function getRemoteFromId(id, remotes) {
|
|
1489
|
+
const remoteName = Object.keys(remotes).filter((name) => id === name || id.startsWith(name + "/")).sort((a, b) => b.length - a.length)[0];
|
|
1490
|
+
return remoteName ? remotes[remoteName] : void 0;
|
|
1491
|
+
}
|
|
1492
|
+
function generateRemotes(id, command, enableSsrInit = false) {
|
|
1493
|
+
const useReactProxy = hasPackageDependency("react");
|
|
1494
|
+
const options = getNormalizeModuleFederationOptions();
|
|
1495
|
+
const isLoadedFirst = options.shareStrategy === "loaded-first";
|
|
1496
|
+
const remote = getRemoteFromId(id, options.remotes);
|
|
1497
|
+
const registerRemoteCode = isLoadedFirst && remote ? `runtime.registerRemotes([${JSON.stringify({
|
|
1498
|
+
entryGlobalName: remote.entryGlobalName,
|
|
1499
|
+
name: remote.name,
|
|
1500
|
+
type: remote.type,
|
|
1501
|
+
entry: remote.entry,
|
|
1502
|
+
shareScope: remote.shareScope ?? "default"
|
|
1503
|
+
})}]);` : "";
|
|
1692
1504
|
const reactImportLine = useReactProxy ? `import __mfReactDefault from "react";
|
|
1693
1505
|
import * as __mfReactNamespace from "react";
|
|
1694
1506
|
const __mfReact = __mfReactDefault ?? __mfReactNamespace.default ?? __mfReactNamespace;` : "";
|
|
1695
1507
|
const importLine = command === "build" ? `${getRuntimeModuleCacheBootstrapCode()}
|
|
1696
|
-
import { hostInitPromise as __mfHostInitPromise } from ${JSON.stringify(getHostAutoInitPath())};` : `${getRuntimeInitBootstrapCode()}
|
|
1697
|
-
const { initPromise, moduleCache: __mfModuleCache } = globalThis[globalKey]
|
|
1508
|
+
import { hostInitPromise as __mfHostInitPromise } from ${JSON.stringify(getHostAutoInitPath())};` : `${getRuntimeInitBootstrapCode(enableSsrInit)}
|
|
1509
|
+
const { initPromise, initResolve, initReject, moduleCache: __mfModuleCache } = globalThis[globalKey];
|
|
1510
|
+
if (typeof window !== "undefined") {
|
|
1511
|
+
import(${JSON.stringify(getHostAutoInitPath())})
|
|
1512
|
+
.then((mod) => mod.hostInitPromise)
|
|
1513
|
+
.then(initResolve, initReject);
|
|
1514
|
+
}`;
|
|
1698
1515
|
const exportLine = command === "serve" ? `if (__mfRemotePending) {
|
|
1699
|
-
|
|
1700
|
-
|
|
1516
|
+
__mfRemotePending = __mfRemotePending.then((mod) => {
|
|
1517
|
+
if (mod !== undefined) exportModule = mod;
|
|
1518
|
+
return exportModule;
|
|
1519
|
+
});
|
|
1701
1520
|
}
|
|
1702
|
-
export
|
|
1703
|
-
export const __mf_remote_pending = Promise.resolve(exportModule);
|
|
1521
|
+
export { exportModule as __moduleExports };
|
|
1522
|
+
export const __mf_remote_pending = __mfRemotePending || Promise.resolve(exportModule);
|
|
1704
1523
|
export default exportModule?.__mf_is_remote_proxy ? exportModule : exportModule?.__esModule ? exportModule.default : exportModule.default ?? exportModule` : command === "build" ? `if (__mfRemotePending) {
|
|
1705
|
-
|
|
1706
|
-
|
|
1524
|
+
__mfRemotePending = __mfRemotePending.then((mod) => {
|
|
1525
|
+
if (mod !== undefined) exportModule = mod;
|
|
1526
|
+
return exportModule;
|
|
1527
|
+
});
|
|
1707
1528
|
}
|
|
1708
|
-
export
|
|
1709
|
-
export const __mf_remote_pending = Promise.resolve(exportModule);
|
|
1529
|
+
export { exportModule as __moduleExports };
|
|
1530
|
+
export const __mf_remote_pending = __mfRemotePending || Promise.resolve(exportModule);
|
|
1710
1531
|
export default exportModule?.__mf_is_remote_proxy ? exportModule : exportModule?.__esModule ? exportModule.default : exportModule.default ?? exportModule` : "export default exportModule";
|
|
1532
|
+
const remoteLoadRuntimePromise = command === "build" ? "__mfHostInitPromise" : "initPromise";
|
|
1533
|
+
const remoteLoadFailureHandler = command === "build" ? `.catch((error) => {
|
|
1534
|
+
delete __mfModuleCache.remote[pendingKey];
|
|
1535
|
+
throw error;
|
|
1536
|
+
})` : `.catch(() => {
|
|
1537
|
+
delete __mfModuleCache.remote[pendingKey];
|
|
1538
|
+
})`;
|
|
1711
1539
|
return `
|
|
1712
1540
|
${reactImportLine}
|
|
1713
1541
|
${importLine}
|
|
1714
|
-
${
|
|
1542
|
+
${`
|
|
1543
|
+
function __mfStartRemoteLoad() {
|
|
1544
|
+
${`
|
|
1545
|
+
const pendingKey = ${JSON.stringify(`__mf_pending__${id}`)};
|
|
1546
|
+
if (!__mfModuleCache.remote[pendingKey]) {
|
|
1547
|
+
__mfModuleCache.remote[pendingKey] = ${remoteLoadRuntimePromise}
|
|
1548
|
+
.then((runtime) => {
|
|
1549
|
+
${registerRemoteCode}
|
|
1550
|
+
return runtime.loadRemote(${JSON.stringify(id)});
|
|
1551
|
+
})
|
|
1552
|
+
.then((mod) => {
|
|
1553
|
+
__mfModuleCache.remote[${JSON.stringify(id)}] = mod;
|
|
1554
|
+
delete __mfModuleCache.remote[pendingKey];
|
|
1555
|
+
return mod;
|
|
1556
|
+
})
|
|
1557
|
+
${remoteLoadFailureHandler};
|
|
1558
|
+
}
|
|
1559
|
+
return __mfModuleCache.remote[pendingKey];`}
|
|
1560
|
+
}
|
|
1715
1561
|
function __mfCreateRemoteProxy(pendingPromise) {
|
|
1716
1562
|
const listeners = new Set();
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1563
|
+
const ensurePending = () => {
|
|
1564
|
+
pendingPromise ||= __mfStartRemoteLoad();
|
|
1565
|
+
pendingPromise?.finally(() => {
|
|
1566
|
+
for (const listener of listeners) listener();
|
|
1567
|
+
});
|
|
1568
|
+
return pendingPromise;
|
|
1569
|
+
};
|
|
1720
1570
|
const getModule = () => __mfModuleCache.remote[${JSON.stringify(id)}];
|
|
1721
1571
|
const proxyTarget = function (...args) {
|
|
1722
1572
|
${useReactProxy ? `const [, setVersion] = __mfReact.useState(0);
|
|
1723
1573
|
__mfReact.useEffect(() => {
|
|
1574
|
+
ensurePending();
|
|
1724
1575
|
const listener = () => setVersion((value) => value + 1);
|
|
1725
1576
|
listeners.add(listener);
|
|
1726
1577
|
if (getModule()) listener();
|
|
@@ -1731,18 +1582,33 @@ export default exportModule?.__mf_is_remote_proxy ? exportModule : exportModule?
|
|
|
1731
1582
|
if (fn !== undefined && fn !== null) {
|
|
1732
1583
|
${useReactProxy ? `return __mfReact.createElement(fn, args[0]);` : `return fn.apply(this, args);`}
|
|
1733
1584
|
}
|
|
1734
|
-
${useReactProxy ? `return null;` : `throw
|
|
1585
|
+
${useReactProxy ? `return null;` : `throw ensurePending();`}
|
|
1735
1586
|
};
|
|
1736
1587
|
return new Proxy(proxyTarget, {
|
|
1737
1588
|
get(_target, prop) {
|
|
1738
1589
|
if (prop === "__mf_is_remote_proxy") return true;
|
|
1739
1590
|
if (prop === "__esModule") return true;
|
|
1740
1591
|
if (prop === "then") return undefined;
|
|
1592
|
+
// Allow React's dev-mode console.warn to stringify the proxy without
|
|
1593
|
+
// throwing "Cannot convert object to primitive value".
|
|
1594
|
+
if (prop === Symbol.toPrimitive || prop === "toString")
|
|
1595
|
+
return () => "[MF remote proxy: pending]";
|
|
1741
1596
|
const mod = getModule();
|
|
1742
1597
|
if (mod) {
|
|
1743
1598
|
return prop in mod ? mod[prop] : mod.default?.[prop];
|
|
1744
1599
|
}
|
|
1745
|
-
|
|
1600
|
+
// When the module is pending and React.lazy() checks for "default",
|
|
1601
|
+
// return the proxy function itself so React renders it (returns null)
|
|
1602
|
+
// rather than crashing on undefined.
|
|
1603
|
+
${useReactProxy ? `if (prop === "default") return proxyTarget;
|
|
1604
|
+
return undefined;` : `throw ensurePending();`}
|
|
1605
|
+
},
|
|
1606
|
+
has(_target, prop) {
|
|
1607
|
+
const mod = getModule();
|
|
1608
|
+
if (mod) return prop in mod;
|
|
1609
|
+
// Tell React that "default" exists when module is pending so it
|
|
1610
|
+
// doesn't warn "lazy: Expected the result of a dynamic import()".
|
|
1611
|
+
${useReactProxy ? `return prop === "default" || prop === "__esModule" || prop === "__mf_is_remote_proxy";` : `return false;`}
|
|
1746
1612
|
},
|
|
1747
1613
|
ownKeys() {
|
|
1748
1614
|
const mod = getModule();
|
|
@@ -1770,40 +1636,12 @@ export default exportModule?.__mf_is_remote_proxy ? exportModule : exportModule?
|
|
|
1770
1636
|
return target.apply(thisArg, args);
|
|
1771
1637
|
}
|
|
1772
1638
|
});
|
|
1773
|
-
}`
|
|
1639
|
+
}`}
|
|
1774
1640
|
let __mfRemotePending;
|
|
1775
1641
|
let exportModule = __mfModuleCache.remote[${JSON.stringify(id)}]
|
|
1776
1642
|
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 = {};`}
|
|
1643
|
+
__mfRemotePending = __mfStartRemoteLoad();
|
|
1644
|
+
exportModule = __mfCreateRemoteProxy(__mfRemotePending);
|
|
1807
1645
|
}
|
|
1808
1646
|
${exportLine}
|
|
1809
1647
|
`;
|
|
@@ -1813,7 +1651,38 @@ export default exportModule?.__mf_is_remote_proxy ? exportModule : exportModule?
|
|
|
1813
1651
|
function getFirstHtmlEntryFile(entryFiles) {
|
|
1814
1652
|
return entryFiles.find((file) => file.endsWith(".html"));
|
|
1815
1653
|
}
|
|
1816
|
-
|
|
1654
|
+
function stripQueryAndHash(file) {
|
|
1655
|
+
return file.split(/[?#]/)[0];
|
|
1656
|
+
}
|
|
1657
|
+
function getBuildInput(config) {
|
|
1658
|
+
return config.build?.rollupOptions?.input ?? config.build?.rolldownOptions?.input;
|
|
1659
|
+
}
|
|
1660
|
+
function patchHashEntryFileName(output, entryName, fileName) {
|
|
1661
|
+
const originalEntryFileNames = output.entryFileNames;
|
|
1662
|
+
output.entryFileNames = (chunkInfo, ...args) => {
|
|
1663
|
+
if (chunkInfo?.name === entryName) return fileName;
|
|
1664
|
+
if (typeof originalEntryFileNames === "function") return originalEntryFileNames(chunkInfo, ...args);
|
|
1665
|
+
return originalEntryFileNames || "assets/[name]-[hash].js";
|
|
1666
|
+
};
|
|
1667
|
+
}
|
|
1668
|
+
function patchHashEntryFileNames(config, entryName, fileName) {
|
|
1669
|
+
if (!fileName?.includes?.("[hash")) return;
|
|
1670
|
+
config.build ??= {};
|
|
1671
|
+
config.build.rollupOptions ??= {};
|
|
1672
|
+
config.build.rolldownOptions ??= {};
|
|
1673
|
+
const patchOutput = (output) => patchHashEntryFileName(output, entryName, fileName);
|
|
1674
|
+
const patchBundlerOutput = (bundlerOptions) => {
|
|
1675
|
+
const output = bundlerOptions.output;
|
|
1676
|
+
if (Array.isArray(output)) {
|
|
1677
|
+
output.forEach(patchOutput);
|
|
1678
|
+
return;
|
|
1679
|
+
}
|
|
1680
|
+
patchOutput(bundlerOptions.output ??= {});
|
|
1681
|
+
};
|
|
1682
|
+
patchBundlerOutput(config.build.rollupOptions);
|
|
1683
|
+
patchBundlerOutput(config.build.rolldownOptions);
|
|
1684
|
+
}
|
|
1685
|
+
const addEntry = ({ entryName, entryPath, fileName, inject = "entry", forceClientInjected, skipTransformFor = [] }) => {
|
|
1817
1686
|
const DEV_HTML_PROXY_PREFIX = "virtual:mf-html-entry-proxy?";
|
|
1818
1687
|
const ENTRY_BOOTSTRAP_QUERY = "?mf-entry-bootstrap";
|
|
1819
1688
|
const waitsForInit = entryName === "hostInit";
|
|
@@ -1826,6 +1695,7 @@ const addEntry = ({ entryName, entryPath, fileName, inject = "entry", forceClien
|
|
|
1826
1695
|
let viteConfig;
|
|
1827
1696
|
let clientInjected = forceClientInjected ?? false;
|
|
1828
1697
|
let emittedFileName;
|
|
1698
|
+
let skipTransformIds = /* @__PURE__ */ new Set();
|
|
1829
1699
|
function skipSvelteKitSsrBuild() {
|
|
1830
1700
|
return (_command === "build" || viteConfig?.command === "build") && viteConfig?.build?.ssr && hasPackageDependency("@sveltejs/kit");
|
|
1831
1701
|
}
|
|
@@ -1881,21 +1751,44 @@ const addEntry = ({ entryName, entryPath, fileName, inject = "entry", forceClien
|
|
|
1881
1751
|
return patched;
|
|
1882
1752
|
}
|
|
1883
1753
|
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
1754
|
const importHelper = useSystemImportFallback ? `const __mfImport = (src) =>
|
|
1886
1755
|
globalThis.System && typeof globalThis.System.import === 'function'
|
|
1887
1756
|
? globalThis.System.import(src)
|
|
1888
1757
|
: import(src);
|
|
1889
1758
|
` : "";
|
|
1890
1759
|
const importExpression = (src) => useSystemImportFallback ? `__mfImport(${JSON.stringify(src)})` : `import(${JSON.stringify(src)})`;
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
const { initHost } = await ${importExpression(initSrc)};
|
|
1760
|
+
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(",") : "";
|
|
1761
|
+
const preloadBlock = remotePreloads ? `
|
|
1894
1762
|
const runtime = await initHost();
|
|
1763
|
+
const __mfPreloadRemote = (remote) => {
|
|
1764
|
+
const pendingKey = "__mf_pending__" + remote;
|
|
1765
|
+
if (!__mfModuleCache.remote[pendingKey]) {
|
|
1766
|
+
__mfModuleCache.remote[pendingKey] = runtime.loadRemote(remote)
|
|
1767
|
+
.then((mod) => {
|
|
1768
|
+
__mfModuleCache.remote[remote] = mod;
|
|
1769
|
+
delete __mfModuleCache.remote[pendingKey];
|
|
1770
|
+
return mod;
|
|
1771
|
+
})
|
|
1772
|
+
.catch((error) => {
|
|
1773
|
+
delete __mfModuleCache.remote[pendingKey];
|
|
1774
|
+
throw error;
|
|
1775
|
+
});
|
|
1776
|
+
}
|
|
1777
|
+
return __mfModuleCache.remote[pendingKey];
|
|
1778
|
+
};
|
|
1895
1779
|
const __mfRemotePreloads = [${remotePreloads}];
|
|
1896
|
-
await Promise.
|
|
1780
|
+
await Promise.allSettled(__mfRemotePreloads);` : `await initHost();`;
|
|
1781
|
+
const importCode = `
|
|
1782
|
+
(async () => {
|
|
1783
|
+
const { initHost } = await ${importExpression(initSrc)};
|
|
1784
|
+
${preloadBlock}
|
|
1897
1785
|
})().then(() => ${importExpression(entrySrc)});
|
|
1898
1786
|
`;
|
|
1787
|
+
return [
|
|
1788
|
+
getRuntimeModuleCacheBootstrapCode(),
|
|
1789
|
+
importHelper,
|
|
1790
|
+
importCode
|
|
1791
|
+
].join("\n");
|
|
1899
1792
|
}
|
|
1900
1793
|
function getSystemBootstrapSource(initSrc, entrySrc) {
|
|
1901
1794
|
return getBootstrapSource(initSrc, entrySrc, true);
|
|
@@ -1910,6 +1803,29 @@ ${importHelper}(async () => {
|
|
|
1910
1803
|
function normalizeDevHtmlProxyId(id) {
|
|
1911
1804
|
return id.replace(/^\0/, "").replace(/^\/@id\//, "").replace(/^__x00__/, "");
|
|
1912
1805
|
}
|
|
1806
|
+
function normalizeModuleId(id) {
|
|
1807
|
+
return id.split("?")[0].replace(/\\/g, "/");
|
|
1808
|
+
}
|
|
1809
|
+
function resolveProjectId(id) {
|
|
1810
|
+
if (id.startsWith("\0") || id.startsWith("virtual:")) return normalizeModuleId(id);
|
|
1811
|
+
return normalizeModuleId(path$1.isAbsolute(id) ? id : path$1.resolve(viteConfig.root, id));
|
|
1812
|
+
}
|
|
1813
|
+
function addEntryFile(file) {
|
|
1814
|
+
const normalized = normalizeModuleId(file);
|
|
1815
|
+
if (!entryFiles.includes(normalized)) entryFiles.push(normalized);
|
|
1816
|
+
}
|
|
1817
|
+
function addHtmlScriptEntries(htmlPath) {
|
|
1818
|
+
if (!fs$1.existsSync(htmlPath)) return;
|
|
1819
|
+
const htmlContent = fs$1.readFileSync(htmlPath, "utf-8");
|
|
1820
|
+
const scriptRegex = /<script\b(?=[^>]*\btype=["']module["'])(?=[^>]*\bsrc=["']([^"']+)["'])[^>]*>/gi;
|
|
1821
|
+
let match;
|
|
1822
|
+
while ((match = scriptRegex.exec(htmlContent)) !== null) {
|
|
1823
|
+
const scriptSrc = stripQueryAndHash(match[1]);
|
|
1824
|
+
if (/^(?:[a-z]+:)?\/\//i.test(scriptSrc)) continue;
|
|
1825
|
+
addEntryFile(scriptSrc);
|
|
1826
|
+
addEntryFile(scriptSrc.startsWith("/") ? path$1.resolve(viteConfig.root, scriptSrc.slice(1)) : path$1.resolve(path$1.dirname(htmlPath), scriptSrc));
|
|
1827
|
+
}
|
|
1828
|
+
}
|
|
1913
1829
|
return [{
|
|
1914
1830
|
name: "add-entry",
|
|
1915
1831
|
apply: "serve",
|
|
@@ -1926,6 +1842,7 @@ ${importHelper}(async () => {
|
|
|
1926
1842
|
const relativePath = normalized.startsWith(root + "/") ? normalized.slice(root.length) : "/" + normalized.replace(/^[A-Za-z]:[\\/]/, "");
|
|
1927
1843
|
devEntryPath = config.base + relativePath.replace(/^\//, "");
|
|
1928
1844
|
}
|
|
1845
|
+
skipTransformIds = new Set(skipTransformFor.map(resolveProjectId));
|
|
1929
1846
|
},
|
|
1930
1847
|
configureServer(server) {
|
|
1931
1848
|
server.middlewares.use((req, res, next) => {
|
|
@@ -1953,7 +1870,8 @@ ${importHelper}(async () => {
|
|
|
1953
1870
|
transformIndexHtml: {
|
|
1954
1871
|
order: "pre",
|
|
1955
1872
|
handler(c) {
|
|
1956
|
-
|
|
1873
|
+
const shouldWrapEntryHtml = _command === "serve" && inject === "entry" && waitsForInit;
|
|
1874
|
+
if (!injectHtml() && !shouldWrapEntryHtml) return;
|
|
1957
1875
|
clientInjected = true;
|
|
1958
1876
|
const base = viteConfig.base.replace(/\/$/, "");
|
|
1959
1877
|
const stripBase = (p) => base && p.startsWith(base + "/") ? p.slice(base.length) : p;
|
|
@@ -1985,24 +1903,30 @@ ${importHelper}(async () => {
|
|
|
1985
1903
|
}, {
|
|
1986
1904
|
name: "add-entry",
|
|
1987
1905
|
enforce: "post",
|
|
1906
|
+
applyToEnvironment() {
|
|
1907
|
+
return true;
|
|
1908
|
+
},
|
|
1909
|
+
config(config) {
|
|
1910
|
+
patchHashEntryFileNames(config, entryName, fileName);
|
|
1911
|
+
},
|
|
1988
1912
|
configResolved(config) {
|
|
1989
1913
|
viteConfig = config;
|
|
1990
|
-
|
|
1914
|
+
skipTransformIds = new Set(skipTransformFor.map(resolveProjectId));
|
|
1915
|
+
const ctx = this;
|
|
1916
|
+
const envName = ctx != null && typeof ctx === "object" ? ctx["environment"] : void 0;
|
|
1917
|
+
if (envName?.name && envName.name !== "client") return;
|
|
1918
|
+
const inputOptions = getBuildInput(config);
|
|
1991
1919
|
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);
|
|
1920
|
+
else if (typeof inputOptions === "string") entryFiles = [normalizeModuleId(inputOptions)];
|
|
1921
|
+
else if (Array.isArray(inputOptions)) entryFiles = inputOptions.map(normalizeModuleId);
|
|
1922
|
+
else if (typeof inputOptions === "object") entryFiles = Object.values(inputOptions).map((input) => normalizeModuleId(String(input)));
|
|
1995
1923
|
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
|
-
}
|
|
1924
|
+
if (htmlFilePath) addHtmlScriptEntries(htmlFilePath);
|
|
2002
1925
|
},
|
|
2003
1926
|
buildStart() {
|
|
2004
1927
|
if (_command === "serve") return;
|
|
2005
1928
|
if (skipSvelteKitSsrBuild()) return;
|
|
1929
|
+
if (this.environment?.name === "ssr") return;
|
|
2006
1930
|
const hasHash = fileName?.includes?.("[hash");
|
|
2007
1931
|
const emitFileOptions = {
|
|
2008
1932
|
name: entryName,
|
|
@@ -2012,16 +1936,14 @@ ${importHelper}(async () => {
|
|
|
2012
1936
|
};
|
|
2013
1937
|
if (!hasHash) emitFileOptions.fileName = fileName;
|
|
2014
1938
|
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
|
-
}
|
|
1939
|
+
if (htmlFilePath) addHtmlScriptEntries(htmlFilePath);
|
|
2021
1940
|
},
|
|
2022
1941
|
generateBundle(_options, bundle) {
|
|
2023
1942
|
if (skipSvelteKitSsrBuild()) return;
|
|
2024
1943
|
if (!injectHtml()) return;
|
|
1944
|
+
if (!emitFileId) return;
|
|
1945
|
+
const htmlFileNames = Object.keys(bundle).filter((fileName) => fileName.endsWith(".html"));
|
|
1946
|
+
if (htmlFileNames.length === 0) return;
|
|
2025
1947
|
const file = this.getFileName(emitFileId);
|
|
2026
1948
|
emittedFileName = file;
|
|
2027
1949
|
const resolvePath = (htmlFileName) => {
|
|
@@ -2043,7 +1965,7 @@ ${importHelper}(async () => {
|
|
|
2043
1965
|
return viteConfig.base + file;
|
|
2044
1966
|
};
|
|
2045
1967
|
let bootstrapIndex = 0;
|
|
2046
|
-
for (const fileName
|
|
1968
|
+
for (const fileName of htmlFileNames) {
|
|
2047
1969
|
let htmlAsset = bundle[fileName];
|
|
2048
1970
|
if (htmlAsset.type === "chunk") return;
|
|
2049
1971
|
let htmlContent = htmlAsset.source.toString() || "";
|
|
@@ -2052,11 +1974,13 @@ ${importHelper}(async () => {
|
|
|
2052
1974
|
let rewritten = false;
|
|
2053
1975
|
htmlContent = htmlContent.replace(scriptRegex, (scriptTag, entrySrc) => {
|
|
2054
1976
|
rewritten = true;
|
|
2055
|
-
const
|
|
1977
|
+
const bootstrapSource = getSystemBootstrapSource(initPath, entrySrc);
|
|
1978
|
+
const bootstrapHash = createHash("sha256").update(bootstrapSource).digest("hex").slice(0, 8);
|
|
1979
|
+
const bootstrapFileName = `mf-entry-bootstrap-${bootstrapIndex++}-${bootstrapHash}.js`;
|
|
2056
1980
|
const bootstrapRef = this.emitFile({
|
|
2057
1981
|
type: "asset",
|
|
2058
1982
|
fileName: bootstrapFileName,
|
|
2059
|
-
source:
|
|
1983
|
+
source: bootstrapSource
|
|
2060
1984
|
});
|
|
2061
1985
|
const bootstrapPath = viteConfig.base + this.getFileName(bootstrapRef);
|
|
2062
1986
|
return scriptTag.replace(entrySrc, bootstrapPath);
|
|
@@ -2088,6 +2012,11 @@ ${importHelper}(async () => {
|
|
|
2088
2012
|
if (skipSvelteKitSsrBuild()) return;
|
|
2089
2013
|
if (isSvelteKitServerModule(id)) return;
|
|
2090
2014
|
if (id.includes(ENTRY_BOOTSTRAP_QUERY)) return;
|
|
2015
|
+
if (normalizeModuleId(id).endsWith(".html")) return;
|
|
2016
|
+
if (skipTransformIds.has(resolveProjectId(id))) return;
|
|
2017
|
+
const transformCtx = this;
|
|
2018
|
+
const transformEnv = transformCtx != null && typeof transformCtx === "object" ? transformCtx["environment"] : void 0;
|
|
2019
|
+
if (transformEnv?.name && transformEnv.name !== "client") return;
|
|
2091
2020
|
const isVinext = hasPackageDependency("vinext");
|
|
2092
2021
|
if (isVinext && inject === "html" && id.includes("virtual:vite-rsc/remove-duplicate-server-css")) {
|
|
2093
2022
|
const namespaceReactImport = `import * as React from 'react';`;
|
|
@@ -2104,9 +2033,16 @@ ${importHelper}(async () => {
|
|
|
2104
2033
|
clientInjected = true;
|
|
2105
2034
|
return mapCodeToCodeWithSourcemap(injection + code);
|
|
2106
2035
|
}
|
|
2107
|
-
if (
|
|
2036
|
+
if (_command === "serve" && inject === "entry" && waitsForInit && !clientInjected && /(?:^|\/)nuxt\/dist\/app\/entry\.js(?:\?|$)/.test(id) && code.includes("vueApp.mount(vueAppRootContainer);")) {
|
|
2108
2037
|
clientInjected = true;
|
|
2109
|
-
|
|
2038
|
+
const injection = `await import(${JSON.stringify(getEntryPath())}).then(({ initHost }) => initHost());\n `;
|
|
2039
|
+
return mapCodeToCodeWithSourcemap(code.replace("vueApp.mount(vueAppRootContainer);", `${injection}vueApp.mount(vueAppRootContainer);`));
|
|
2040
|
+
}
|
|
2041
|
+
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);
|
|
2042
|
+
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();");
|
|
2043
|
+
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) {
|
|
2044
|
+
clientInjected = true;
|
|
2045
|
+
if (!waitsForInit || _command === "serve" && inject === "entry" && isHydrationEntryFallback) return mapCodeToCodeWithSourcemap(`import ${JSON.stringify(getEntryPath())};\n` + code);
|
|
2110
2046
|
const entrySrc = id.includes("?") ? `${id}&${ENTRY_BOOTSTRAP_QUERY.slice(1)}` : `${id}${ENTRY_BOOTSTRAP_QUERY}`;
|
|
2111
2047
|
return mapCodeToCodeWithSourcemap(getBootstrapSource(getEntryPath(), entrySrc));
|
|
2112
2048
|
}
|
|
@@ -2158,11 +2094,7 @@ function checkAliasConflicts(options) {
|
|
|
2158
2094
|
};
|
|
2159
2095
|
}
|
|
2160
2096
|
//#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;
|
|
2097
|
+
//#region src/plugins/hmr/react.ts
|
|
2166
2098
|
/**
|
|
2167
2099
|
* Proxy module served for `/@react-refresh` on MF remote dev servers.
|
|
2168
2100
|
* Delegates to the host page's RefreshRuntime instance via
|
|
@@ -2182,6 +2114,112 @@ const REACT_REFRESH_PROXY_MODULE = [
|
|
|
2182
2114
|
`export const __hmr_import = __rt.__hmr_import;`,
|
|
2183
2115
|
`export default __rt.default || __rt;`
|
|
2184
2116
|
].join("\n");
|
|
2117
|
+
const reactAdapter = {
|
|
2118
|
+
name: "react",
|
|
2119
|
+
pluginNames: ["vite:react-refresh", "vite:react-swc:refresh"],
|
|
2120
|
+
remote: { configureServer({ server }) {
|
|
2121
|
+
server.middlewares.use((req, res, next) => {
|
|
2122
|
+
if (req.url?.replace(/\?.*$/, "") !== "/@react-refresh") return next();
|
|
2123
|
+
res.setHeader("Content-Type", "application/javascript; charset=utf-8");
|
|
2124
|
+
res.setHeader("Access-Control-Allow-Origin", "*");
|
|
2125
|
+
res.end(REACT_REFRESH_PROXY_MODULE);
|
|
2126
|
+
});
|
|
2127
|
+
} }
|
|
2128
|
+
};
|
|
2129
|
+
//#endregion
|
|
2130
|
+
//#region src/plugins/hmr/vue.ts
|
|
2131
|
+
/**
|
|
2132
|
+
* In dev mode each Vite dev server serves its own copy of Vue
|
|
2133
|
+
* (`/node_modules/.vite/deps/vue.js`). When a remote module is loaded into the
|
|
2134
|
+
* host page, the remote's Vue copy evaluates and runs:
|
|
2135
|
+
*
|
|
2136
|
+
* globalThis.__VUE_HMR_RUNTIME__ = createHotReloadAPI()
|
|
2137
|
+
*
|
|
2138
|
+
* which silently overwrites the host's runtime. After that, `createRecord` for
|
|
2139
|
+
* host components lives in the orphaned first runtime, but `reload(hmrId, ...)`
|
|
2140
|
+
* goes through the second runtime — the lookup misses and HMR stops working.
|
|
2141
|
+
*
|
|
2142
|
+
* This guard pins `__VUE_HMR_RUNTIME__` to the first writer (the host's Vue)
|
|
2143
|
+
* via a property trap on `globalThis`. Subsequent writes from remote-side Vue
|
|
2144
|
+
* copies are silently dropped. Must execute before any Vue module loads —
|
|
2145
|
+
* injected as a plain (non-module) script at `head-prepend`.
|
|
2146
|
+
*
|
|
2147
|
+
* `singleton: true` in `shared` is not sufficient: in dev mode MF's share-scope
|
|
2148
|
+
* does not actually dedupe Vue across dev servers, so without this guard the
|
|
2149
|
+
* last-loaded copy wins.
|
|
2150
|
+
*/
|
|
2151
|
+
const VUE_HMR_RUNTIME_GUARD_SCRIPT = `
|
|
2152
|
+
(function () {
|
|
2153
|
+
var h = null;
|
|
2154
|
+
Object.defineProperty(globalThis, '__VUE_HMR_RUNTIME__', {
|
|
2155
|
+
get: function () { return h; },
|
|
2156
|
+
set: function (v) { if (h === null) h = v; },
|
|
2157
|
+
configurable: true,
|
|
2158
|
+
enumerable: true,
|
|
2159
|
+
});
|
|
2160
|
+
})();`;
|
|
2161
|
+
/**
|
|
2162
|
+
* `@vitejs/plugin-vue` derives an SFC's `__hmrId` from a hash of the file path
|
|
2163
|
+
* relative to Vite's `root`. With module federation, host and remote are
|
|
2164
|
+
* separate Vite projects with independent roots — so an SFC at `src/App.vue`
|
|
2165
|
+
* in both will hash to the same id. Once both copies of Vue collapse onto the
|
|
2166
|
+
* shared `__VUE_HMR_RUNTIME__` (see the guard above), the host's instance and
|
|
2167
|
+
* the remote's instance both register under that single id. A remote-only
|
|
2168
|
+
* file change then calls `rerender(id, newRender)`, which iterates *every*
|
|
2169
|
+
* instance under that id — including the host one — and applies the remote's
|
|
2170
|
+
* render function to the host instance. The host's `setupState` doesn't have
|
|
2171
|
+
* the remote's bindings, so the render throws and Vue falls back to a full
|
|
2172
|
+
* reload required warning.
|
|
2173
|
+
*
|
|
2174
|
+
* Fix: rewrite the remote's emitted `__hmrId` literal to be prefixed with the
|
|
2175
|
+
* federation `name`, so the remote's instances live under a distinct key. The
|
|
2176
|
+
* accept callback emitted by plugin-vue reads `_sfc_main.__hmrId` /
|
|
2177
|
+
* `updated.__hmrId` at runtime, so rewriting the literal once is enough.
|
|
2178
|
+
*/
|
|
2179
|
+
const SFC_HMR_ID_LITERAL_RE = /(\.__hmrId\s*=\s*["'`])([^"'`]+)(["'`])/g;
|
|
2180
|
+
function rewriteSfcHmrId(code, federationName) {
|
|
2181
|
+
let matched = false;
|
|
2182
|
+
return {
|
|
2183
|
+
code: code.replace(SFC_HMR_ID_LITERAL_RE, (_match, prefix, id, suffix) => {
|
|
2184
|
+
matched = true;
|
|
2185
|
+
if (id.startsWith(`${federationName}-`)) return `${prefix}${id}${suffix}`;
|
|
2186
|
+
return `${prefix}${federationName}-${id}${suffix}`;
|
|
2187
|
+
}),
|
|
2188
|
+
matched
|
|
2189
|
+
};
|
|
2190
|
+
}
|
|
2191
|
+
let pluginVueRegressionWarned = false;
|
|
2192
|
+
function warnPluginVueRegression() {
|
|
2193
|
+
if (pluginVueRegressionWarned) return;
|
|
2194
|
+
pluginVueRegressionWarned = true;
|
|
2195
|
+
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.");
|
|
2196
|
+
}
|
|
2197
|
+
const vueAdapter = {
|
|
2198
|
+
name: "vue",
|
|
2199
|
+
pluginNames: ["vite:vue", "vite:vue-jsx"],
|
|
2200
|
+
host: { transformIndexHtml() {
|
|
2201
|
+
return [{
|
|
2202
|
+
tag: "script",
|
|
2203
|
+
children: VUE_HMR_RUNTIME_GUARD_SCRIPT,
|
|
2204
|
+
injectTo: "head-prepend"
|
|
2205
|
+
}];
|
|
2206
|
+
} },
|
|
2207
|
+
remote: { transform(code, _id, ctx) {
|
|
2208
|
+
if (!code.includes("__VUE_HMR_RUNTIME__.createRecord(")) return;
|
|
2209
|
+
const { code: rewritten, matched } = rewriteSfcHmrId(code, ctx.options.name);
|
|
2210
|
+
if (!matched) {
|
|
2211
|
+
warnPluginVueRegression();
|
|
2212
|
+
return;
|
|
2213
|
+
}
|
|
2214
|
+
return rewritten === code ? void 0 : rewritten;
|
|
2215
|
+
} }
|
|
2216
|
+
};
|
|
2217
|
+
//#endregion
|
|
2218
|
+
//#region src/plugins/hmr/fullReload.ts
|
|
2219
|
+
const REMOTE_HMR_ENDPOINT = "__mf_hmr";
|
|
2220
|
+
const REMOTE_HMR_EVENT = "mf:remote-update";
|
|
2221
|
+
const REMOTE_HMR_CONNECT_RETRY_DELAY_MS = 1e3;
|
|
2222
|
+
const REMOTE_HMR_CONNECT_MAX_RETRIES = 10;
|
|
2185
2223
|
function getBasePath$1(base) {
|
|
2186
2224
|
if (!base) return "/";
|
|
2187
2225
|
if (base.startsWith("http://") || base.startsWith("https://")) try {
|
|
@@ -2200,9 +2238,6 @@ function getHmrWsPath(base, hmrPath) {
|
|
|
2200
2238
|
if (!normalizedPath || normalizedPath === "/") return normalizedBase;
|
|
2201
2239
|
return `${normalizedBase.endsWith("/") ? normalizedBase.slice(0, -1) : normalizedBase}/${normalizedPath.startsWith("/") ? normalizedPath.slice(1) : normalizedPath}`;
|
|
2202
2240
|
}
|
|
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
2241
|
function getRemoteHmrWsUrl(server) {
|
|
2207
2242
|
const hmr = server.config.server.hmr;
|
|
2208
2243
|
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 +2276,275 @@ function getStringPreview(value, max = 180) {
|
|
|
2241
2276
|
} catch {}
|
|
2242
2277
|
return rawValue.slice(0, max);
|
|
2243
2278
|
}
|
|
2244
|
-
|
|
2245
|
-
|
|
2279
|
+
/**
|
|
2280
|
+
* Installs the `/__mf_hmr` metadata endpoint on a remote dev server. The host
|
|
2281
|
+
* fetches this to discover the remote's HMR WebSocket URL before opening a
|
|
2282
|
+
* Node-to-Node relay socket. Always installed on remotes when `remoteHmr` is
|
|
2283
|
+
* enabled — under `'native'` strategy the endpoint is unused but harmless;
|
|
2284
|
+
* under `'full-reload'` it's the discovery hop for the host relay.
|
|
2285
|
+
*/
|
|
2286
|
+
function setupRemoteMetadataEndpoint(server, options) {
|
|
2287
|
+
const endpointPath = getRemoteHmrPath(server.config.base);
|
|
2288
|
+
const wsUrl = getRemoteHmrWsUrl(server);
|
|
2289
|
+
server.middlewares.use((req, res, next) => {
|
|
2290
|
+
if (req.url?.replace(/\?.*/, "") !== endpointPath) {
|
|
2291
|
+
next();
|
|
2292
|
+
return;
|
|
2293
|
+
}
|
|
2294
|
+
res.setHeader("Content-Type", "application/json");
|
|
2295
|
+
res.setHeader("Access-Control-Allow-Origin", "*");
|
|
2296
|
+
res.end(JSON.stringify({
|
|
2297
|
+
remote: options.name,
|
|
2298
|
+
event: REMOTE_HMR_EVENT,
|
|
2299
|
+
wsUrl
|
|
2300
|
+
}));
|
|
2301
|
+
});
|
|
2246
2302
|
}
|
|
2247
2303
|
/**
|
|
2248
|
-
*
|
|
2249
|
-
*
|
|
2250
|
-
*
|
|
2304
|
+
* Installs file-watcher broadcasts on a remote dev server. Every non-ignored
|
|
2305
|
+
* change/add/unlink emits a `mf:remote-update` custom event on the remote's
|
|
2306
|
+
* own WS channel. The host relay (see `setupHostFullReloadRelay`) listens
|
|
2307
|
+
* for these events and triggers a host-side full reload in response.
|
|
2251
2308
|
*
|
|
2252
|
-
*
|
|
2309
|
+
* Only called under the `'full-reload'` strategy.
|
|
2253
2310
|
*/
|
|
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
|
-
});
|
|
2314
|
-
}
|
|
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);
|
|
2311
|
+
function setupRemoteBroadcast(server, options) {
|
|
2312
|
+
const broadcast = (file) => {
|
|
2313
|
+
if (shouldIgnoreFile(file, options)) return;
|
|
2314
|
+
server.ws.send({
|
|
2315
|
+
type: "custom",
|
|
2316
|
+
event: REMOTE_HMR_EVENT,
|
|
2317
|
+
data: {
|
|
2318
|
+
remote: options.name,
|
|
2319
|
+
file,
|
|
2320
|
+
ts: Date.now()
|
|
2392
2321
|
}
|
|
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
2322
|
});
|
|
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
2323
|
};
|
|
2431
|
-
|
|
2432
|
-
|
|
2433
|
-
|
|
2434
|
-
|
|
2435
|
-
|
|
2436
|
-
|
|
2437
|
-
|
|
2438
|
-
Object.entries(options.remotes).forEach(([key, remote]) => {
|
|
2439
|
-
if (!remote.entry) return;
|
|
2440
|
-
remotes[key] = `${remote.entryGlobalName?.startsWith("http") || remote.entryGlobalName?.includes(".json") ? remote.name || key : remote.entryGlobalName || remote.name || key}@${remote.entry}`;
|
|
2324
|
+
server.watcher.on("change", broadcast);
|
|
2325
|
+
server.watcher.on("add", broadcast);
|
|
2326
|
+
server.watcher.on("unlink", broadcast);
|
|
2327
|
+
server.httpServer?.once("close", () => {
|
|
2328
|
+
server.watcher.off("change", broadcast);
|
|
2329
|
+
server.watcher.off("add", broadcast);
|
|
2330
|
+
server.watcher.off("unlink", broadcast);
|
|
2441
2331
|
});
|
|
2442
|
-
|
|
2443
|
-
|
|
2444
|
-
|
|
2445
|
-
|
|
2446
|
-
|
|
2447
|
-
}
|
|
2448
|
-
|
|
2449
|
-
|
|
2450
|
-
|
|
2451
|
-
|
|
2452
|
-
|
|
2453
|
-
|
|
2454
|
-
|
|
2455
|
-
|
|
2456
|
-
|
|
2457
|
-
|
|
2458
|
-
|
|
2459
|
-
const
|
|
2460
|
-
|
|
2461
|
-
return
|
|
2462
|
-
|
|
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`)
|
|
2332
|
+
}
|
|
2333
|
+
/**
|
|
2334
|
+
* Installs the host-side full-reload relay. For each configured remote:
|
|
2335
|
+
* 1. Fetches the remote's `/__mf_hmr` metadata to get its WS URL.
|
|
2336
|
+
* 2. Opens a Node-to-Node WebSocket to that URL.
|
|
2337
|
+
* 3. On any `mf:remote-update` message, broadcasts `{ type: 'full-reload' }`
|
|
2338
|
+
* to the host's own browser-facing WS.
|
|
2339
|
+
*
|
|
2340
|
+
* Also reloads on local host file changes. Retries failed connections up to
|
|
2341
|
+
* `REMOTE_HMR_CONNECT_MAX_RETRIES` times with a fixed delay.
|
|
2342
|
+
*
|
|
2343
|
+
* Only called under the `'full-reload'` strategy.
|
|
2344
|
+
*/
|
|
2345
|
+
function setupHostFullReloadRelay(server, options) {
|
|
2346
|
+
const connections = [];
|
|
2347
|
+
const reconnectTimers = /* @__PURE__ */ new Map();
|
|
2348
|
+
let isTearingDown = false;
|
|
2349
|
+
const clearReconnectTimer = (remoteName) => {
|
|
2350
|
+
const timer = reconnectTimers.get(remoteName);
|
|
2351
|
+
if (!timer) return;
|
|
2352
|
+
clearTimeout(timer);
|
|
2353
|
+
reconnectTimers.delete(remoteName);
|
|
2496
2354
|
};
|
|
2497
|
-
|
|
2498
|
-
|
|
2499
|
-
|
|
2500
|
-
|
|
2501
|
-
const isZipRequest = requestPath === assetPaths.zipRequestPath;
|
|
2502
|
-
const isApiRequest = requestPath === assetPaths.apiRequestPath;
|
|
2503
|
-
if (!isZipRequest && !isApiRequest) {
|
|
2504
|
-
next();
|
|
2355
|
+
const scheduleReconnect = (remoteName, remote, attempt, reason) => {
|
|
2356
|
+
if (isTearingDown) return;
|
|
2357
|
+
if (attempt >= REMOTE_HMR_CONNECT_MAX_RETRIES) {
|
|
2358
|
+
mfWarn(`Remote "${remoteName}" full HMR reconnect skipped after ${REMOTE_HMR_CONNECT_MAX_RETRIES} attempts: ${reason}`);
|
|
2505
2359
|
return;
|
|
2506
2360
|
}
|
|
2507
|
-
|
|
2508
|
-
|
|
2509
|
-
|
|
2510
|
-
|
|
2361
|
+
clearReconnectTimer(remoteName);
|
|
2362
|
+
const timer = setTimeout(() => {
|
|
2363
|
+
reconnectTimers.delete(remoteName);
|
|
2364
|
+
connectRemote(remoteName, remote, attempt + 1);
|
|
2365
|
+
}, REMOTE_HMR_CONNECT_RETRY_DELAY_MS);
|
|
2366
|
+
reconnectTimers.set(remoteName, timer);
|
|
2367
|
+
};
|
|
2368
|
+
const connectRemote = async (remoteName, remote, attempt = 0) => {
|
|
2369
|
+
if (isTearingDown) return;
|
|
2370
|
+
const endpoint = getRemoteHmrEndpoint(remote.entry, server);
|
|
2371
|
+
if (!endpoint) {
|
|
2372
|
+
mfWarn(`Failed to build HMR endpoint URL for remote "${remoteName}"`);
|
|
2511
2373
|
return;
|
|
2512
2374
|
}
|
|
2513
|
-
|
|
2514
|
-
|
|
2515
|
-
|
|
2516
|
-
|
|
2517
|
-
|
|
2375
|
+
try {
|
|
2376
|
+
const metadataResponse = await fetch(endpoint);
|
|
2377
|
+
if (!metadataResponse.ok) {
|
|
2378
|
+
mfWarn(`Failed to fetch remote HMR metadata from "${remoteName}": ${metadataResponse.status}`);
|
|
2379
|
+
scheduleReconnect(remoteName, remote, attempt, `HTTP ${metadataResponse.status}`);
|
|
2380
|
+
return;
|
|
2381
|
+
}
|
|
2382
|
+
const metadata = await metadataResponse.json();
|
|
2383
|
+
if (metadata.event !== "mf:remote-update" || !metadata.wsUrl) {
|
|
2384
|
+
mfWarn(`Remote "${remoteName}" returned unexpected HMR metadata shape`);
|
|
2385
|
+
return;
|
|
2386
|
+
}
|
|
2387
|
+
const ws = new WebSocket(metadata.wsUrl, "vite-hmr");
|
|
2388
|
+
ws.onmessage = (rawEvent) => {
|
|
2389
|
+
const message = parseRemoteHmrMessage(rawEvent.data);
|
|
2390
|
+
if (!message || message.event !== "mf:remote-update") return;
|
|
2391
|
+
server.ws.send({ type: "full-reload" });
|
|
2392
|
+
};
|
|
2393
|
+
ws.onopen = () => clearReconnectTimer(remoteName);
|
|
2394
|
+
ws.onerror = (error) => mfWarn(`Remote HMR socket error for "${remoteName}":`, error);
|
|
2395
|
+
ws.onclose = () => scheduleReconnect(remoteName, remote, attempt, "socket closed");
|
|
2396
|
+
connections.push(ws);
|
|
2397
|
+
} catch (error) {
|
|
2398
|
+
mfWarn(`Failed to connect remote HMR for "${remoteName}" on attempt ${attempt + 1}: ${getStringPreview(error)}`);
|
|
2399
|
+
scheduleReconnect(remoteName, remote, attempt, getStringPreview(error));
|
|
2518
2400
|
}
|
|
2519
|
-
|
|
2520
|
-
|
|
2521
|
-
|
|
2522
|
-
|
|
2523
|
-
|
|
2524
|
-
|
|
2525
|
-
|
|
2401
|
+
};
|
|
2402
|
+
const teardown = () => {
|
|
2403
|
+
isTearingDown = true;
|
|
2404
|
+
reconnectTimers.forEach((timer) => clearTimeout(timer));
|
|
2405
|
+
reconnectTimers.clear();
|
|
2406
|
+
connections.forEach((connection) => {
|
|
2407
|
+
if (connection.readyState !== connection.CLOSING && connection.readyState !== connection.CLOSED) connection.close();
|
|
2526
2408
|
});
|
|
2527
|
-
|
|
2409
|
+
connections.length = 0;
|
|
2528
2410
|
};
|
|
2529
|
-
|
|
2530
|
-
const
|
|
2531
|
-
|
|
2532
|
-
|
|
2533
|
-
|
|
2534
|
-
|
|
2535
|
-
|
|
2536
|
-
|
|
2537
|
-
|
|
2538
|
-
|
|
2539
|
-
|
|
2540
|
-
|
|
2541
|
-
|
|
2542
|
-
|
|
2543
|
-
|
|
2544
|
-
|
|
2545
|
-
|
|
2546
|
-
|
|
2547
|
-
|
|
2548
|
-
|
|
2411
|
+
for (const [remoteName, remote] of Object.entries(options.remotes)) connectRemote(remoteName, remote);
|
|
2412
|
+
const triggerHostReload = (file) => {
|
|
2413
|
+
if (shouldIgnoreFile(file, options)) return;
|
|
2414
|
+
server.ws.send({ type: "full-reload" });
|
|
2415
|
+
};
|
|
2416
|
+
server.watcher.on("change", triggerHostReload);
|
|
2417
|
+
server.watcher.on("add", triggerHostReload);
|
|
2418
|
+
server.watcher.on("unlink", triggerHostReload);
|
|
2419
|
+
server.httpServer?.once("close", teardown);
|
|
2420
|
+
}
|
|
2421
|
+
//#endregion
|
|
2422
|
+
//#region src/plugins/pluginDevRemoteHmr.ts
|
|
2423
|
+
/**
|
|
2424
|
+
* Clears the federation runtime's `moduleCache` on every Vite `vite:beforeUpdate`
|
|
2425
|
+
* event. Without this, after a remote SFC change Vite would patch the in-memory
|
|
2426
|
+
* component, but `loadRemote()` would still return the cached stale module on
|
|
2427
|
+
* the next call (e.g. after route navigation), making the patched version
|
|
2428
|
+
* effectively unreachable.
|
|
2429
|
+
*/
|
|
2430
|
+
const FEDERATION_MODULE_CACHE_CLEAR_SCRIPT = `
|
|
2431
|
+
if (import.meta.hot) {
|
|
2432
|
+
import.meta.hot.on('vite:beforeUpdate', function () {
|
|
2433
|
+
try {
|
|
2434
|
+
var f = globalThis.__FEDERATION__ || globalThis.__VMOK__;
|
|
2435
|
+
if (!f || !f.__INSTANCES__) return;
|
|
2436
|
+
for (var i = 0; i < f.__INSTANCES__.length; i++) {
|
|
2437
|
+
if (f.__INSTANCES__[i] && f.__INSTANCES__[i].moduleCache)
|
|
2438
|
+
f.__INSTANCES__[i].moduleCache.clear();
|
|
2439
|
+
}
|
|
2440
|
+
} catch (e) {}
|
|
2441
|
+
});
|
|
2442
|
+
}`;
|
|
2443
|
+
const HMR_ADAPTERS = [reactAdapter, vueAdapter];
|
|
2444
|
+
function resolveAdapters(plugins) {
|
|
2445
|
+
const pluginNames = new Set(plugins.map((p) => p.name));
|
|
2446
|
+
return HMR_ADAPTERS.filter((adapter) => adapter.pluginNames.some((name) => pluginNames.has(name)));
|
|
2447
|
+
}
|
|
2448
|
+
function hasCrossFederationHmr(plugins) {
|
|
2449
|
+
return resolveAdapters(plugins).length > 0;
|
|
2450
|
+
}
|
|
2451
|
+
function isRemoteHmrEnabled(dev) {
|
|
2452
|
+
return typeof dev === "object" && dev !== null && !!dev.remoteHmr;
|
|
2453
|
+
}
|
|
2454
|
+
/**
|
|
2455
|
+
* `'native'` — a matched framework adapter owns HMR through Vite's native
|
|
2456
|
+
* channel (e.g. React Fast Refresh via the `/@react-refresh` proxy, Vue's
|
|
2457
|
+
* patched `__VUE_HMR_RUNTIME__`). The broadcast/relay path stays idle.
|
|
2458
|
+
*
|
|
2459
|
+
* `'full-reload'` — no adapter matched, or the user explicitly opted in with
|
|
2460
|
+
* `remoteHmr: 'full-reload'` to bypass adapters: the plugin's broadcast/relay
|
|
2461
|
+
* machinery triggers a page reload on every remote file change.
|
|
2462
|
+
*/
|
|
2463
|
+
function resolveHmrStrategy(dev, plugins) {
|
|
2464
|
+
if (typeof dev === "object" && dev !== null && dev.remoteHmr === "full-reload") return "full-reload";
|
|
2465
|
+
return hasCrossFederationHmr(plugins) ? "native" : "full-reload";
|
|
2466
|
+
}
|
|
2467
|
+
function shouldIgnoreFile(file, options) {
|
|
2468
|
+
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");
|
|
2469
|
+
}
|
|
2470
|
+
function collectHostTags(server, options, adapters) {
|
|
2471
|
+
const ctx = {
|
|
2472
|
+
server,
|
|
2473
|
+
options
|
|
2474
|
+
};
|
|
2475
|
+
const tags = [];
|
|
2476
|
+
for (const adapter of adapters) {
|
|
2477
|
+
const adapterTags = adapter.host?.transformIndexHtml?.(ctx);
|
|
2478
|
+
if (adapterTags) tags.push(...adapterTags);
|
|
2479
|
+
}
|
|
2480
|
+
tags.push({
|
|
2481
|
+
tag: "script",
|
|
2482
|
+
attrs: { type: "module" },
|
|
2483
|
+
children: FEDERATION_MODULE_CACHE_CLEAR_SCRIPT,
|
|
2484
|
+
injectTo: "head"
|
|
2549
2485
|
});
|
|
2550
|
-
|
|
2551
|
-
|
|
2552
|
-
|
|
2553
|
-
|
|
2554
|
-
|
|
2555
|
-
|
|
2486
|
+
return tags;
|
|
2487
|
+
}
|
|
2488
|
+
function pluginDevRemoteHmr(options) {
|
|
2489
|
+
const isHost = Object.keys(options.remotes).length > 0;
|
|
2490
|
+
const isRemote = Object.keys(options.exposes).length > 0;
|
|
2491
|
+
let adapters = [];
|
|
2492
|
+
let strategy = "full-reload";
|
|
2493
|
+
return {
|
|
2494
|
+
name: "module-federation-dev-remote-hmr",
|
|
2556
2495
|
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
2496
|
configResolved(config) {
|
|
2567
|
-
|
|
2497
|
+
adapters = resolveAdapters(config.plugins);
|
|
2498
|
+
strategy = resolveHmrStrategy(options.dev, config.plugins);
|
|
2568
2499
|
},
|
|
2569
2500
|
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);
|
|
2501
|
+
if (!isRemoteHmrEnabled(options.dev)) return;
|
|
2502
|
+
if (isRemote) {
|
|
2503
|
+
for (const adapter of adapters) adapter.remote?.configureServer?.({
|
|
2504
|
+
server,
|
|
2505
|
+
options
|
|
2635
2506
|
});
|
|
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;
|
|
2507
|
+
setupRemoteMetadataEndpoint(server, options);
|
|
2508
|
+
if (strategy === "full-reload") setupRemoteBroadcast(server, options);
|
|
2657
2509
|
}
|
|
2658
|
-
if (
|
|
2659
|
-
|
|
2660
|
-
|
|
2661
|
-
|
|
2662
|
-
try {
|
|
2663
|
-
consumeOptions = normalizeConsumeTypesOptions({
|
|
2664
|
-
context,
|
|
2665
|
-
dtsOptions: normalizedDtsOptions,
|
|
2666
|
-
pluginOptions: getDtsModuleFederationConfig(resolvedConfig.root)
|
|
2510
|
+
if (isHost) {
|
|
2511
|
+
for (const adapter of adapters) adapter.host?.configureServer?.({
|
|
2512
|
+
server,
|
|
2513
|
+
options
|
|
2667
2514
|
});
|
|
2668
|
-
|
|
2669
|
-
logDtsError(error, normalizedDtsOptions);
|
|
2670
|
-
return;
|
|
2515
|
+
if (strategy === "full-reload") setupHostFullReloadRelay(server, options);
|
|
2671
2516
|
}
|
|
2672
|
-
|
|
2673
|
-
|
|
2674
|
-
|
|
2675
|
-
|
|
2676
|
-
|
|
2677
|
-
|
|
2678
|
-
|
|
2679
|
-
|
|
2680
|
-
|
|
2681
|
-
|
|
2682
|
-
|
|
2683
|
-
|
|
2684
|
-
|
|
2685
|
-
} catch (error) {
|
|
2686
|
-
logDtsError(error, normalizedDtsOptions);
|
|
2687
|
-
return;
|
|
2517
|
+
},
|
|
2518
|
+
transform: {
|
|
2519
|
+
order: "post",
|
|
2520
|
+
handler(code, id) {
|
|
2521
|
+
if (!isRemote || !isRemoteHmrEnabled(options.dev)) return;
|
|
2522
|
+
if (!adapters.length) return;
|
|
2523
|
+
let result = code;
|
|
2524
|
+
const adapterCtx = { options };
|
|
2525
|
+
for (const adapter of adapters) {
|
|
2526
|
+
const next = adapter.remote?.transform?.(result, id, adapterCtx);
|
|
2527
|
+
if (typeof next === "string") result = next;
|
|
2528
|
+
}
|
|
2529
|
+
return result === code ? void 0 : result;
|
|
2688
2530
|
}
|
|
2689
|
-
|
|
2690
|
-
|
|
2691
|
-
|
|
2692
|
-
|
|
2693
|
-
|
|
2531
|
+
},
|
|
2532
|
+
transformIndexHtml: {
|
|
2533
|
+
order: "pre",
|
|
2534
|
+
handler(_html, ctx) {
|
|
2535
|
+
if (!isRemoteHmrEnabled(options.dev)) return;
|
|
2536
|
+
if (!isHost || !ctx.server) return;
|
|
2537
|
+
return collectHostTags(ctx.server, options, adapters);
|
|
2694
2538
|
}
|
|
2695
2539
|
}
|
|
2696
|
-
}
|
|
2540
|
+
};
|
|
2697
2541
|
}
|
|
2698
2542
|
//#endregion
|
|
2699
2543
|
//#region src/virtualModules/index.ts
|
|
2700
|
-
function initVirtualModules(command, remoteEntryId) {
|
|
2544
|
+
function initVirtualModules(command, remoteEntryId, enableSsrInit = false) {
|
|
2701
2545
|
writeLocalSharedImportMap();
|
|
2702
2546
|
writeHostAutoInit(remoteEntryId, command);
|
|
2703
|
-
writeRuntimeInitStatus(command);
|
|
2547
|
+
writeRuntimeInitStatus(command, enableSsrInit);
|
|
2704
2548
|
}
|
|
2705
2549
|
//#endregion
|
|
2706
2550
|
//#region src/utils/bundleHelpers.ts
|
|
@@ -3050,6 +2894,12 @@ function removeTrailingSlash(value) {
|
|
|
3050
2894
|
function ensureTrailingSlash(value) {
|
|
3051
2895
|
return `${removeTrailingSlash(value)}/`;
|
|
3052
2896
|
}
|
|
2897
|
+
function getBasePath(base) {
|
|
2898
|
+
return removeTrailingSlash(base || "/");
|
|
2899
|
+
}
|
|
2900
|
+
function isNuxtClientBase(base) {
|
|
2901
|
+
return getBasePath(base).endsWith("/_nuxt");
|
|
2902
|
+
}
|
|
3053
2903
|
function normalizeNodeModulePath(source) {
|
|
3054
2904
|
return source.replace(/\\/g, "/").replace(/\?.*$/, "");
|
|
3055
2905
|
}
|
|
@@ -3083,6 +2933,116 @@ function resolvePublicPath(options, viteBase, originalBase) {
|
|
|
3083
2933
|
return "auto";
|
|
3084
2934
|
}
|
|
3085
2935
|
//#endregion
|
|
2936
|
+
//#region src/virtualModules/virtualExposesSSR.ts
|
|
2937
|
+
/**
|
|
2938
|
+
* Virtual module ID for the SSR exposes map.
|
|
2939
|
+
* Separate from the browser exposes: no CSS injection, no document references,
|
|
2940
|
+
* and shared packages are imported as bare specifiers (externals in the SSR build).
|
|
2941
|
+
*/
|
|
2942
|
+
function getVirtualExposesSSRId(options) {
|
|
2943
|
+
return `virtual:mf-exposes-ssr:${`${options.internalName}__${options.filename}`.replace(/[^a-zA-Z0-9_-]/g, "_")}`;
|
|
2944
|
+
}
|
|
2945
|
+
/**
|
|
2946
|
+
* Generates the SSR exposes map module.
|
|
2947
|
+
*
|
|
2948
|
+
* Differences from the browser version (virtualExposes.ts):
|
|
2949
|
+
* - No CSS asset injection (document APIs unavailable on Node)
|
|
2950
|
+
* - Shared packages (react, react-dom, etc.) must be externals in the SSR
|
|
2951
|
+
* build so Node resolves them via its own module cache — this is what
|
|
2952
|
+
* guarantees the React singleton is shared with react-dom/server.
|
|
2953
|
+
*/
|
|
2954
|
+
function generateExposesSSR(options) {
|
|
2955
|
+
return `
|
|
2956
|
+
export default {
|
|
2957
|
+
${Object.keys(options.exposes).map((key) => {
|
|
2958
|
+
return `
|
|
2959
|
+
${JSON.stringify(key)}: async () => {
|
|
2960
|
+
const importModule = await import(${JSON.stringify(options.exposes[key].import)})
|
|
2961
|
+
const exportModule = {}
|
|
2962
|
+
Object.assign(exportModule, importModule)
|
|
2963
|
+
Object.defineProperty(exportModule, "__esModule", {
|
|
2964
|
+
value: true,
|
|
2965
|
+
enumerable: false
|
|
2966
|
+
})
|
|
2967
|
+
return exportModule
|
|
2968
|
+
}
|
|
2969
|
+
`;
|
|
2970
|
+
}).join(",")}
|
|
2971
|
+
}
|
|
2972
|
+
`;
|
|
2973
|
+
}
|
|
2974
|
+
//#endregion
|
|
2975
|
+
//#region src/virtualModules/virtualRemoteEntrySSR.ts
|
|
2976
|
+
const REMOTE_ENTRY_SSR_ID = "virtual:mf-REMOTE_ENTRY_SSR_ID";
|
|
2977
|
+
function getRemoteEntrySSRId(options) {
|
|
2978
|
+
return `${REMOTE_ENTRY_SSR_ID}:${`${options.internalName}__${options.filename}`.replace(/[^a-zA-Z0-9_-]/g, "_")}`;
|
|
2979
|
+
}
|
|
2980
|
+
function getSsrRemoteEntryFileName(browserFilename) {
|
|
2981
|
+
const ext = browserFilename.match(/\.[^.]+$/)?.[0] || ".js";
|
|
2982
|
+
return `${browserFilename.slice(0, browserFilename.length - ext.length)}.ssr${ext}`;
|
|
2983
|
+
}
|
|
2984
|
+
/**
|
|
2985
|
+
* Generates the SSR remote entry module.
|
|
2986
|
+
*
|
|
2987
|
+
* This is intentionally minimal — no HMR shim, no loadShare virtual modules,
|
|
2988
|
+
* no browser globals. Shared packages (react, react-dom, etc.) are imported
|
|
2989
|
+
* as externals by the SSR build, so Node's require cache provides the singleton.
|
|
2990
|
+
*
|
|
2991
|
+
* The container API (init / get) mirrors the browser entry so the MF runtime
|
|
2992
|
+
* can call it the same way on the server.
|
|
2993
|
+
*/
|
|
2994
|
+
function generateRemoteEntrySSR(options) {
|
|
2995
|
+
const virtualExposesSSRId = getVirtualExposesSSRId(options);
|
|
2996
|
+
return `
|
|
2997
|
+
import { init as runtimeInit } from "@module-federation/runtime";
|
|
2998
|
+
|
|
2999
|
+
let exposesMapPromise;
|
|
3000
|
+
|
|
3001
|
+
async function getExposesMap() {
|
|
3002
|
+
exposesMapPromise ??= import(${JSON.stringify(virtualExposesSSRId)}).then((mod) => mod.default ?? mod);
|
|
3003
|
+
return exposesMapPromise;
|
|
3004
|
+
}
|
|
3005
|
+
|
|
3006
|
+
/**
|
|
3007
|
+
* Called by the MF runtime on the host to register this remote's share scope.
|
|
3008
|
+
* On the server the host has already initialised the runtime, so we just need
|
|
3009
|
+
* to set up a minimal runtime instance for the remote container.
|
|
3010
|
+
*/
|
|
3011
|
+
async function init(shared = {}, initScope = []) {
|
|
3012
|
+
const initRes = runtimeInit({
|
|
3013
|
+
name: ${JSON.stringify(options.internalName)},
|
|
3014
|
+
remotes: [],
|
|
3015
|
+
shared: {},
|
|
3016
|
+
});
|
|
3017
|
+
const initToken = { from: ${JSON.stringify(options.internalName)} };
|
|
3018
|
+
if (initScope.indexOf(initToken) >= 0) return;
|
|
3019
|
+
initScope.push(initToken);
|
|
3020
|
+
initRes.initShareScopeMap(${JSON.stringify(options.shareScope)}, shared);
|
|
3021
|
+
try {
|
|
3022
|
+
await Promise.all(
|
|
3023
|
+
await initRes.initializeSharing(${JSON.stringify(options.shareScope)}, {
|
|
3024
|
+
strategy: ${JSON.stringify(options.shareStrategy ?? "version-first")},
|
|
3025
|
+
from: 'build',
|
|
3026
|
+
initScope,
|
|
3027
|
+
})
|
|
3028
|
+
);
|
|
3029
|
+
} catch (e) {
|
|
3030
|
+
console.error('[Module Federation SSR]', e);
|
|
3031
|
+
}
|
|
3032
|
+
return initRes;
|
|
3033
|
+
}
|
|
3034
|
+
|
|
3035
|
+
async function getExposes(moduleName) {
|
|
3036
|
+
const exposesMap = await getExposesMap();
|
|
3037
|
+
if (!(moduleName in exposesMap))
|
|
3038
|
+
throw new Error(\`[Module Federation] Module \${moduleName} does not exist in container.\`);
|
|
3039
|
+
return exposesMap[moduleName]().then((res) => () => res);
|
|
3040
|
+
}
|
|
3041
|
+
|
|
3042
|
+
export { init, getExposes as get };
|
|
3043
|
+
`;
|
|
3044
|
+
}
|
|
3045
|
+
//#endregion
|
|
3086
3046
|
//#region src/plugins/pluginMFManifest.ts
|
|
3087
3047
|
/**
|
|
3088
3048
|
* Resolves the build version for the module federation manifest.
|
|
@@ -3113,6 +3073,7 @@ const Manifest = () => {
|
|
|
3113
3073
|
};
|
|
3114
3074
|
let root;
|
|
3115
3075
|
let remoteEntryFile;
|
|
3076
|
+
let ssrRemoteEntryFile;
|
|
3116
3077
|
let publicPath;
|
|
3117
3078
|
let _command;
|
|
3118
3079
|
let _originalConfigBase;
|
|
@@ -3149,8 +3110,8 @@ const Manifest = () => {
|
|
|
3149
3110
|
type: "module"
|
|
3150
3111
|
},
|
|
3151
3112
|
ssrRemoteEntry: {
|
|
3152
|
-
name: filename,
|
|
3153
|
-
path: "",
|
|
3113
|
+
name: getSsrRemoteEntryFileName(filename),
|
|
3114
|
+
path: "/__mf_ssr__/",
|
|
3154
3115
|
type: "module"
|
|
3155
3116
|
},
|
|
3156
3117
|
varRemoteEntry: varFilename ? {
|
|
@@ -3181,6 +3142,7 @@ const Manifest = () => {
|
|
|
3181
3142
|
_originalConfigBase = config.base;
|
|
3182
3143
|
},
|
|
3183
3144
|
configResolved(config) {
|
|
3145
|
+
viteConfig = config;
|
|
3184
3146
|
root = config.root;
|
|
3185
3147
|
let base = config.base;
|
|
3186
3148
|
if (_command === "serve") base = (config.server.origin || "") + config.base;
|
|
@@ -3190,7 +3152,10 @@ const Manifest = () => {
|
|
|
3190
3152
|
if (!mfManifestName) return;
|
|
3191
3153
|
let filesMap = {};
|
|
3192
3154
|
const foundRemoteEntryFile = findRemoteEntryFile(mfOptions.filename, bundle);
|
|
3155
|
+
const expectedSsrRemoteEntryFile = getSsrRemoteEntryFileName(mfOptions.filename);
|
|
3156
|
+
const foundSsrRemoteEntryFile = Object.values(bundle).find((file) => file.fileName === expectedSsrRemoteEntryFile)?.fileName;
|
|
3193
3157
|
if (foundRemoteEntryFile) remoteEntryFile = foundRemoteEntryFile;
|
|
3158
|
+
ssrRemoteEntryFile = foundSsrRemoteEntryFile || expectedSsrRemoteEntryFile;
|
|
3194
3159
|
const allCssAssets = mfOptions.bundleAllCSS && !disableAssetsAnalyze ? collectCssAssets(bundle) : /* @__PURE__ */ new Set();
|
|
3195
3160
|
if (!disableAssetsAnalyze) {
|
|
3196
3161
|
const exposesModules = Object.keys(mfOptions.exposes).map((item) => mfOptions.exposes[item].import);
|
|
@@ -3232,6 +3197,11 @@ const Manifest = () => {
|
|
|
3232
3197
|
path: "",
|
|
3233
3198
|
type: "module"
|
|
3234
3199
|
};
|
|
3200
|
+
const ssrRemoteEntry = {
|
|
3201
|
+
name: ssrRemoteEntryFile || getSsrRemoteEntryFileName(filename),
|
|
3202
|
+
path: _command === "serve" ? "/__mf_ssr__/" : "",
|
|
3203
|
+
type: "module"
|
|
3204
|
+
};
|
|
3235
3205
|
const varRemoteEntry = varFilename ? {
|
|
3236
3206
|
name: varFilename,
|
|
3237
3207
|
path: "",
|
|
@@ -3295,7 +3265,7 @@ const Manifest = () => {
|
|
|
3295
3265
|
buildName: name
|
|
3296
3266
|
},
|
|
3297
3267
|
remoteEntry,
|
|
3298
|
-
ssrRemoteEntry
|
|
3268
|
+
ssrRemoteEntry,
|
|
3299
3269
|
varRemoteEntry,
|
|
3300
3270
|
types: {
|
|
3301
3271
|
path: "",
|
|
@@ -3534,13 +3504,14 @@ function appendAlias(config, alias) {
|
|
|
3534
3504
|
function pluginProxyRemotes_default(options) {
|
|
3535
3505
|
let command;
|
|
3536
3506
|
let root = process.cwd();
|
|
3507
|
+
let enableSsrInit = false;
|
|
3537
3508
|
const { remotes } = options;
|
|
3538
3509
|
function resolveRemoteId(source, importer, remoteName) {
|
|
3539
3510
|
if (source === remoteName) {
|
|
3540
3511
|
const installedPackageEntry = getInstalledPackageEntry(source, { cwd: root });
|
|
3541
3512
|
if (installedPackageEntry && (importer === void 0 || isNodeModulesImporter(importer))) return installedPackageEntry;
|
|
3542
3513
|
}
|
|
3543
|
-
const remoteModule = getRemoteVirtualModule(source, command);
|
|
3514
|
+
const remoteModule = getRemoteVirtualModule(source, command, enableSsrInit);
|
|
3544
3515
|
addUsedRemote(remoteName, source);
|
|
3545
3516
|
refreshHostAutoInit();
|
|
3546
3517
|
return remoteModule.getImportId();
|
|
@@ -3559,6 +3530,9 @@ function pluginProxyRemotes_default(options) {
|
|
|
3559
3530
|
});
|
|
3560
3531
|
});
|
|
3561
3532
|
},
|
|
3533
|
+
configResolved() {
|
|
3534
|
+
enableSsrInit = command === "serve" && parseInt(version, 10) >= 8;
|
|
3535
|
+
},
|
|
3562
3536
|
resolveId(source, importer) {
|
|
3563
3537
|
if (!filterId(source)) return;
|
|
3564
3538
|
for (const remote of Object.values(remotes)) {
|
|
@@ -3609,7 +3583,7 @@ function tryResolveFromProjectRoot(source) {
|
|
|
3609
3583
|
const browserEntry = getInstalledPackageEntry(source, { cwd: getPackageDetectionCwd() });
|
|
3610
3584
|
if (browserEntry) return browserEntry;
|
|
3611
3585
|
try {
|
|
3612
|
-
return createRequire
|
|
3586
|
+
return createRequire(new URL(`file://${path.join(getPackageDetectionCwd(), "package.json")}`)).resolve(source);
|
|
3613
3587
|
} catch {
|
|
3614
3588
|
return;
|
|
3615
3589
|
}
|
|
@@ -3840,9 +3814,37 @@ function applyRewrites(code, imports, id) {
|
|
|
3840
3814
|
const importParts = [];
|
|
3841
3815
|
if (imp.defaultLocal) importParts.push(`default as ${imp.defaultLocal}`);
|
|
3842
3816
|
importParts.push(`__moduleExports as ${nsId}`);
|
|
3843
|
-
const destructParts = imp.named.map((s) => s.imported === s.local ? s.local : `${s.imported}: ${s.local}`);
|
|
3844
3817
|
let rewrite = `import { ${importParts.join(", ")} } from ${src};`;
|
|
3845
|
-
if (
|
|
3818
|
+
if (imp.named.length > 0) {
|
|
3819
|
+
const isProxyId = `__mf_is_proxy_${counter++}`;
|
|
3820
|
+
const namedProxyHelper = `function __mfCreateNamedRemoteProxy(ns, key) {
|
|
3821
|
+
const target = function (...args) {
|
|
3822
|
+
const value = ns[key];
|
|
3823
|
+
return typeof value === "function" ? value.apply(this, args) : value;
|
|
3824
|
+
};
|
|
3825
|
+
return new Proxy(target, {
|
|
3826
|
+
get(_target, prop) {
|
|
3827
|
+
if (prop === "then") return undefined;
|
|
3828
|
+
const value = ns[key];
|
|
3829
|
+
if (prop === Symbol.toPrimitive) return () => value;
|
|
3830
|
+
const item = value == null ? undefined : value[prop];
|
|
3831
|
+
return typeof item === "function" ? item.bind(value) : item;
|
|
3832
|
+
},
|
|
3833
|
+
apply(target, thisArg, args) {
|
|
3834
|
+
return target.apply(thisArg, args);
|
|
3835
|
+
}
|
|
3836
|
+
});
|
|
3837
|
+
}`;
|
|
3838
|
+
const tempNames = imp.named.map((_s) => `__mf_named_${counter++}`);
|
|
3839
|
+
const destructParts = imp.named.map((s, index) => `${s.imported}: ${tempNames[index]}`);
|
|
3840
|
+
const bindingLines = imp.named.map((s, index) => {
|
|
3841
|
+
const temp = tempNames[index];
|
|
3842
|
+
return `const ${s.local} = ${isProxyId} ? __mfCreateNamedRemoteProxy(${nsId}, ${JSON.stringify(s.imported)}) : ${temp};`;
|
|
3843
|
+
});
|
|
3844
|
+
rewrite += `\n${namedProxyHelper}\nconst ${isProxyId} = ${nsId} && ${nsId}.__mf_is_remote_proxy;`;
|
|
3845
|
+
rewrite += `\nconst { ${destructParts.join(", ")} } = ${isProxyId} ? {} : ${nsId};`;
|
|
3846
|
+
rewrite += `\n${bindingLines.join("\n")}`;
|
|
3847
|
+
}
|
|
3846
3848
|
ms.overwrite(imp.start, imp.end, rewrite);
|
|
3847
3849
|
}
|
|
3848
3850
|
changed = true;
|
|
@@ -4118,6 +4120,205 @@ function pluginRemoteNamedExports(options) {
|
|
|
4118
4120
|
};
|
|
4119
4121
|
}
|
|
4120
4122
|
//#endregion
|
|
4123
|
+
//#region src/plugins/pluginSSRRemoteEntry.ts
|
|
4124
|
+
/**
|
|
4125
|
+
* Emits a Node-compatible SSR remote entry alongside the browser entry.
|
|
4126
|
+
*
|
|
4127
|
+
* Format strategy:
|
|
4128
|
+
* - Emit a dedicated ESM SSR entry alongside the browser entry.
|
|
4129
|
+
* - Keep the SSR entry out of the browser remote graph for Rollup builds by
|
|
4130
|
+
* emitting it as a generated asset.
|
|
4131
|
+
*
|
|
4132
|
+
* In both cases shared packages (react, react-dom, etc.) are marked as external
|
|
4133
|
+
* so Node resolves them through its own module cache, guaranteeing the singleton
|
|
4134
|
+
* is shared with react-dom/server.
|
|
4135
|
+
*/
|
|
4136
|
+
function pluginSSRRemoteEntry(options) {
|
|
4137
|
+
const remoteEntrySSRId = getRemoteEntrySSRId(options);
|
|
4138
|
+
const virtualExposesSSRId = getVirtualExposesSSRId(options);
|
|
4139
|
+
let isRolldown = false;
|
|
4140
|
+
let ssrOutputFilename = "";
|
|
4141
|
+
const ssrOnlyExternals = [
|
|
4142
|
+
"@module-federation/runtime",
|
|
4143
|
+
"@module-federation/runtime-core",
|
|
4144
|
+
"@module-federation/sdk",
|
|
4145
|
+
...options.ssrExternals ?? []
|
|
4146
|
+
];
|
|
4147
|
+
const ssrOnlyExternalPattern = new RegExp(`^(${ssrOnlyExternals.map((e) => e.replace(/[/\\^$*+?.()|[\]{}]/g, "\\$&")).join("|")})(\\/.*)?$`);
|
|
4148
|
+
const ssrModuleIds = new Set([remoteEntrySSRId, virtualExposesSSRId]);
|
|
4149
|
+
const resolvedAbsToPackage = /* @__PURE__ */ new Map();
|
|
4150
|
+
let isServe = false;
|
|
4151
|
+
let viteConfig;
|
|
4152
|
+
let isNuxtProject = false;
|
|
4153
|
+
const findNuxtExposesChunk = (bundle) => {
|
|
4154
|
+
const exposeKeys = Object.keys(options.exposes);
|
|
4155
|
+
if (exposeKeys.length === 0) return;
|
|
4156
|
+
return Object.values(bundle).find((file) => {
|
|
4157
|
+
if (file.type !== "chunk" || !file.fileName.startsWith("_nuxt/") || !file.fileName.endsWith(".js")) return false;
|
|
4158
|
+
const code = file.code || "";
|
|
4159
|
+
return exposeKeys.every((key) => code.includes(JSON.stringify(key)));
|
|
4160
|
+
})?.fileName;
|
|
4161
|
+
};
|
|
4162
|
+
return [{
|
|
4163
|
+
name: "mf:ssr-remote-entry:pre",
|
|
4164
|
+
enforce: "pre",
|
|
4165
|
+
configResolved(config) {
|
|
4166
|
+
isServe = config.command === "serve";
|
|
4167
|
+
for (const pkg of ssrOnlyExternals) {
|
|
4168
|
+
const aliasEntry = (config.resolve?.alias)?.find((a) => a.find === pkg || a.find instanceof RegExp && a.find.test(pkg));
|
|
4169
|
+
if (aliasEntry?.replacement) resolvedAbsToPackage.set(aliasEntry.replacement, pkg);
|
|
4170
|
+
}
|
|
4171
|
+
},
|
|
4172
|
+
resolveId(id, importer) {
|
|
4173
|
+
if (id === remoteEntrySSRId || id.startsWith(remoteEntrySSRId)) return id;
|
|
4174
|
+
if (id === virtualExposesSSRId || id.startsWith(virtualExposesSSRId)) return id;
|
|
4175
|
+
if (!importer || !ssrModuleIds.has(importer)) return;
|
|
4176
|
+
if (ssrOnlyExternalPattern.test(id)) return {
|
|
4177
|
+
id,
|
|
4178
|
+
external: true
|
|
4179
|
+
};
|
|
4180
|
+
const pkg = resolvedAbsToPackage.get(id);
|
|
4181
|
+
if (pkg) return {
|
|
4182
|
+
id: pkg,
|
|
4183
|
+
external: true
|
|
4184
|
+
};
|
|
4185
|
+
if (id.startsWith(".") || id.startsWith("/") || id.startsWith("file:")) return this.resolve(id, importer, { skipSelf: true }).then((resolved) => {
|
|
4186
|
+
if (resolved) ssrModuleIds.add(resolved.id);
|
|
4187
|
+
return resolved;
|
|
4188
|
+
});
|
|
4189
|
+
}
|
|
4190
|
+
}, {
|
|
4191
|
+
name: "mf:ssr-remote-entry",
|
|
4192
|
+
configResolved(config) {
|
|
4193
|
+
viteConfig = config;
|
|
4194
|
+
isNuxtProject = isNuxtProjectRoot(config.root);
|
|
4195
|
+
},
|
|
4196
|
+
configureServer(server) {
|
|
4197
|
+
const base = "/__mf_ssr__";
|
|
4198
|
+
const basePath = getBasePath(viteConfig?.base);
|
|
4199
|
+
const ssrEntryFileName = getSsrRemoteEntryFileName(options.filename);
|
|
4200
|
+
if (isNuxtProject || isNuxtClientBase(basePath)) server.middlewares.use((req, _res, next) => {
|
|
4201
|
+
if (req.url?.replace(/\?.*/, "") === `${basePath}/${ssrEntryFileName}`) req.url = `${basePath}/__mf_ssr__/${ssrEntryFileName}`;
|
|
4202
|
+
next();
|
|
4203
|
+
});
|
|
4204
|
+
const ssrEnv = server.environments?.ssr;
|
|
4205
|
+
const clientEnv = server.environments?.client;
|
|
4206
|
+
if (typeof (ssrEnv?.fetchModule ?? clientEnv?.fetchModule) === "function") server.middlewares.use("/__mf_runner__", async (req, res) => {
|
|
4207
|
+
res.setHeader("Access-Control-Allow-Origin", "*");
|
|
4208
|
+
if (req.method === "OPTIONS") {
|
|
4209
|
+
res.setHeader("Access-Control-Allow-Methods", "POST");
|
|
4210
|
+
res.setHeader("Access-Control-Allow-Headers", "Content-Type");
|
|
4211
|
+
res.statusCode = 204;
|
|
4212
|
+
res.end();
|
|
4213
|
+
return;
|
|
4214
|
+
}
|
|
4215
|
+
if (req.method !== "POST") {
|
|
4216
|
+
res.statusCode = 405;
|
|
4217
|
+
res.end("Method not allowed");
|
|
4218
|
+
return;
|
|
4219
|
+
}
|
|
4220
|
+
try {
|
|
4221
|
+
const chunks = [];
|
|
4222
|
+
await new Promise((resolve, reject) => {
|
|
4223
|
+
req.on("data", (chunk) => chunks.push(chunk));
|
|
4224
|
+
req.on("end", resolve);
|
|
4225
|
+
req.on("error", reject);
|
|
4226
|
+
});
|
|
4227
|
+
const body = JSON.parse(Buffer.concat(chunks).toString("utf8"));
|
|
4228
|
+
if (body.name === "getBuiltins") {
|
|
4229
|
+
const builtins = (clientEnv ?? ssrEnv)?.config?.resolve?.builtins ?? [];
|
|
4230
|
+
res.setHeader("Content-Type", "application/json");
|
|
4231
|
+
res.end(JSON.stringify({ result: builtins }));
|
|
4232
|
+
return;
|
|
4233
|
+
}
|
|
4234
|
+
if (body.name !== "fetchModule") {
|
|
4235
|
+
res.statusCode = 400;
|
|
4236
|
+
res.end(JSON.stringify({ error: { message: `Unsupported invoke: ${body.name}` } }));
|
|
4237
|
+
return;
|
|
4238
|
+
}
|
|
4239
|
+
const [id, importer, opts] = body.data;
|
|
4240
|
+
const fetchEnv = ssrEnv ?? clientEnv;
|
|
4241
|
+
const fetchFn = fetchEnv.fetchModule.bind(fetchEnv);
|
|
4242
|
+
let result;
|
|
4243
|
+
try {
|
|
4244
|
+
result = await fetchFn(id, importer, opts);
|
|
4245
|
+
} catch (fetchErr) {
|
|
4246
|
+
const bareId = id.startsWith("/@id/") ? id.slice(5).replace(/^__x00__/, "\0") : id;
|
|
4247
|
+
try {
|
|
4248
|
+
const { createRequire } = await import("module");
|
|
4249
|
+
const resolved = createRequire(new URL(`file://${server.config.root}/package.json`)).resolve(bareId.replace(/^\0/, ""));
|
|
4250
|
+
const { pathToFileURL } = await import("url");
|
|
4251
|
+
result = {
|
|
4252
|
+
externalize: pathToFileURL(resolved).href,
|
|
4253
|
+
type: "module"
|
|
4254
|
+
};
|
|
4255
|
+
} catch {
|
|
4256
|
+
throw fetchErr;
|
|
4257
|
+
}
|
|
4258
|
+
}
|
|
4259
|
+
res.setHeader("Content-Type", "application/json");
|
|
4260
|
+
res.end(JSON.stringify({ result }));
|
|
4261
|
+
} catch (e) {
|
|
4262
|
+
res.setHeader("Content-Type", "application/json");
|
|
4263
|
+
res.end(JSON.stringify({ error: { message: String(e instanceof Error ? e.message : e) } }));
|
|
4264
|
+
}
|
|
4265
|
+
});
|
|
4266
|
+
const ssrPath = `${base}/${ssrEntryFileName}`;
|
|
4267
|
+
server.middlewares.use(ssrPath, (_req, res) => {
|
|
4268
|
+
const exposesUrl = `${base}/${options.filename.replace(/\.[^.]+$/, "")}.exposes.js`;
|
|
4269
|
+
const code = generateRemoteEntrySSR(options).replace(JSON.stringify(virtualExposesSSRId), JSON.stringify(exposesUrl));
|
|
4270
|
+
res.setHeader("Content-Type", "application/javascript");
|
|
4271
|
+
res.setHeader("Access-Control-Allow-Origin", "*");
|
|
4272
|
+
res.end(code);
|
|
4273
|
+
});
|
|
4274
|
+
},
|
|
4275
|
+
resolveId(id) {
|
|
4276
|
+
if (id === remoteEntrySSRId || id.startsWith(remoteEntrySSRId)) return id;
|
|
4277
|
+
if (id === virtualExposesSSRId || id.startsWith(virtualExposesSSRId)) return id;
|
|
4278
|
+
if (id === `/__mf_ssr__/${getSsrRemoteEntryFileName(options.filename)}`) return remoteEntrySSRId;
|
|
4279
|
+
if (id === `/__mf_ssr__/${options.filename.replace(/\.[^.]+$/, "")}.exposes.js`) return virtualExposesSSRId;
|
|
4280
|
+
},
|
|
4281
|
+
load(id) {
|
|
4282
|
+
if (id === remoteEntrySSRId || id.startsWith(remoteEntrySSRId)) return generateRemoteEntrySSR(options);
|
|
4283
|
+
if (id === virtualExposesSSRId || id.startsWith(virtualExposesSSRId)) return generateExposesSSR(options);
|
|
4284
|
+
},
|
|
4285
|
+
buildStart() {
|
|
4286
|
+
if (isServe) return;
|
|
4287
|
+
isRolldown = getIsRolldown(this);
|
|
4288
|
+
ssrOutputFilename = getSsrRemoteEntryFileName(options.filename);
|
|
4289
|
+
const environmentName = this.environment?.name;
|
|
4290
|
+
const hasSsrEnvironment = Boolean(viteConfig?.environments?.ssr);
|
|
4291
|
+
const isLegacySsrBuild = Boolean(this.environment?.config?.build?.ssr);
|
|
4292
|
+
if (hasSsrEnvironment) {
|
|
4293
|
+
if (isNuxtProject) {
|
|
4294
|
+
if (environmentName === "ssr") return;
|
|
4295
|
+
} else if (environmentName !== "ssr") return;
|
|
4296
|
+
} else if (isLegacySsrBuild) {} else if (environmentName && environmentName !== "client") return;
|
|
4297
|
+
if (Object.keys(options.exposes).length === 0) return;
|
|
4298
|
+
if (isRolldown) this.emitFile({
|
|
4299
|
+
type: "chunk",
|
|
4300
|
+
id: remoteEntrySSRId,
|
|
4301
|
+
name: "ssrRemoteEntry",
|
|
4302
|
+
fileName: ssrOutputFilename,
|
|
4303
|
+
preserveSignature: "strict"
|
|
4304
|
+
});
|
|
4305
|
+
else this.emitFile({
|
|
4306
|
+
type: "asset",
|
|
4307
|
+
fileName: ssrOutputFilename,
|
|
4308
|
+
source: generateRemoteEntrySSR(options)
|
|
4309
|
+
});
|
|
4310
|
+
},
|
|
4311
|
+
generateBundle(_options, bundle) {
|
|
4312
|
+
const exposesChunk = findNuxtExposesChunk(bundle);
|
|
4313
|
+
const ssrAsset = bundle[ssrOutputFilename];
|
|
4314
|
+
if (exposesChunk && ssrAsset?.type === "asset" && typeof ssrAsset.source === "string") ssrAsset.source = ssrAsset.source.replace(/import\("virtual:mf-exposes-ssr:[^"]+"\)/g, `import("./${exposesChunk}")`);
|
|
4315
|
+
if (!isRolldown) return;
|
|
4316
|
+
const chunk = bundle[ssrOutputFilename];
|
|
4317
|
+
if (!chunk || chunk.type !== "chunk") return;
|
|
4318
|
+
}
|
|
4319
|
+
}];
|
|
4320
|
+
}
|
|
4321
|
+
//#endregion
|
|
4121
4322
|
//#region src/plugins/pluginVarRemoteEntry.ts
|
|
4122
4323
|
const VarRemoteEntry = () => {
|
|
4123
4324
|
const mfOptions = getNormalizeModuleFederationOptions();
|
|
@@ -4380,6 +4581,11 @@ function createEarlyVirtualModulesPlugin(options) {
|
|
|
4380
4581
|
const root = config.root || process.cwd();
|
|
4381
4582
|
setPackageDetectionCwd(root);
|
|
4382
4583
|
const isVinext = hasPackageDependency("vinext");
|
|
4584
|
+
setSsrRemotes(Object.entries(options.remotes).map(([key, r]) => ({
|
|
4585
|
+
name: key,
|
|
4586
|
+
entry: r.entry,
|
|
4587
|
+
type: r.type ?? "module"
|
|
4588
|
+
})));
|
|
4383
4589
|
initVirtualModules(_command, getRemoteEntryId(options));
|
|
4384
4590
|
const isRolldown = getIsRolldown(this);
|
|
4385
4591
|
if (remotes && Object.keys(remotes).length > 0) {
|
|
@@ -4499,9 +4705,46 @@ export default __mfShared.default ?? __mfShared;`
|
|
|
4499
4705
|
}
|
|
4500
4706
|
writeLocalSharedImportMap();
|
|
4501
4707
|
}
|
|
4708
|
+
},
|
|
4709
|
+
configResolved(config) {
|
|
4710
|
+
if (parseInt(version, 10) < 8) return;
|
|
4711
|
+
if (!(Object.keys(options.exposes).length > 0 || Object.keys(options.remotes).length > 0)) return;
|
|
4712
|
+
if (options.runtimePlugins.some((p) => {
|
|
4713
|
+
return (typeof p === "string" ? p : p[0]) === "@module-federation/vite/ssrEntryLoader";
|
|
4714
|
+
})) return;
|
|
4715
|
+
const pluginRequire = createRequire(import.meta.url);
|
|
4716
|
+
const projectRequire = createRequire(new URL(`file://${config.root}/package.json`));
|
|
4717
|
+
const sharedKeys = Object.keys(options.shared ?? {});
|
|
4718
|
+
const commonSharedPkgs = [
|
|
4719
|
+
"react",
|
|
4720
|
+
"react-dom",
|
|
4721
|
+
"react/jsx-runtime",
|
|
4722
|
+
"react/jsx-dev-runtime",
|
|
4723
|
+
"@module-federation/runtime",
|
|
4724
|
+
"@module-federation/runtime-core",
|
|
4725
|
+
"@module-federation/sdk"
|
|
4726
|
+
];
|
|
4727
|
+
const resolvedShared = {};
|
|
4728
|
+
for (const pkg of [...commonSharedPkgs, ...sharedKeys]) try {
|
|
4729
|
+
resolvedShared[pkg] = projectRequire.resolve(pkg);
|
|
4730
|
+
} catch {
|
|
4731
|
+
try {
|
|
4732
|
+
resolvedShared[pkg] = pluginRequire.resolve(pkg);
|
|
4733
|
+
} catch {}
|
|
4734
|
+
}
|
|
4735
|
+
const ssrEntryLoaderSpecifier = "@module-federation/vite/ssrEntryLoader";
|
|
4736
|
+
try {
|
|
4737
|
+
pluginRequire.resolve(ssrEntryLoaderSpecifier);
|
|
4738
|
+
options.runtimePlugins.push([ssrEntryLoaderSpecifier, { resolvedShared }]);
|
|
4739
|
+
} catch {}
|
|
4502
4740
|
}
|
|
4503
4741
|
};
|
|
4504
4742
|
}
|
|
4743
|
+
const SSR_ONLY_PLUGINS = new Set(["@module-federation/vite/ssrEntryLoader"]);
|
|
4744
|
+
function loadPluginDts(options) {
|
|
4745
|
+
if (options.dts === false) return [];
|
|
4746
|
+
return [import("./pluginDts-B5pcUmam.mjs").then(({ default: pluginDts }) => pluginDts(options))];
|
|
4747
|
+
}
|
|
4505
4748
|
function federation(mfUserOptions) {
|
|
4506
4749
|
if (isTestEnv()) return [];
|
|
4507
4750
|
const options = normalizeModuleFederationOptions(mfUserOptions);
|
|
@@ -4542,7 +4785,7 @@ function federation(mfUserOptions) {
|
|
|
4542
4785
|
const environmentName = this.environment?.name;
|
|
4543
4786
|
if (!environmentName || environmentName === "client") return;
|
|
4544
4787
|
const target = reactServerEntryMap[id];
|
|
4545
|
-
const reactPackageJson = createRequire
|
|
4788
|
+
const reactPackageJson = createRequire(new URL(`file://${process.cwd()}/package.json`)).resolve("react/package.json");
|
|
4546
4789
|
return path.join(path.dirname(reactPackageJson), target.replace(/^react\//, ""));
|
|
4547
4790
|
}
|
|
4548
4791
|
}] : [],
|
|
@@ -4553,14 +4796,26 @@ function federation(mfUserOptions) {
|
|
|
4553
4796
|
command = env.command;
|
|
4554
4797
|
},
|
|
4555
4798
|
configResolved() {
|
|
4556
|
-
initVirtualModules(command, remoteEntryId);
|
|
4799
|
+
initVirtualModules(command, remoteEntryId, parseInt(version, 10) >= 8);
|
|
4557
4800
|
}
|
|
4558
4801
|
},
|
|
4559
4802
|
aliasToArrayPlugin_default,
|
|
4560
4803
|
checkAliasConflicts({ shared }),
|
|
4561
4804
|
normalizeOptimizeDeps_default,
|
|
4562
|
-
...
|
|
4805
|
+
...loadPluginDts(options),
|
|
4563
4806
|
pluginDevRemoteHmr(options),
|
|
4807
|
+
{
|
|
4808
|
+
name: "mf:normalize-entry-chunks",
|
|
4809
|
+
enforce: "pre",
|
|
4810
|
+
apply: "build",
|
|
4811
|
+
generateBundle(_options, bundle) {
|
|
4812
|
+
for (const chunk of Object.values(bundle)) {
|
|
4813
|
+
if (typeof chunk !== "object" || chunk === null || chunk.type !== "chunk" || !chunk.isEntry) continue;
|
|
4814
|
+
const facadeId = chunk.facadeModuleId ?? "";
|
|
4815
|
+
if (facadeId.includes("__mf__virtual") || facadeId.startsWith("virtual:mf-") || facadeId.startsWith("virtual:mf:") || facadeId.startsWith("\0virtual:mf-") || facadeId.startsWith("\0virtual:mf:")) chunk.isEntry = false;
|
|
4816
|
+
}
|
|
4817
|
+
}
|
|
4818
|
+
},
|
|
4564
4819
|
...addEntry({
|
|
4565
4820
|
entryName: "remoteEntry",
|
|
4566
4821
|
entryPath: remoteEntryId,
|
|
@@ -4570,7 +4825,8 @@ function federation(mfUserOptions) {
|
|
|
4570
4825
|
entryName: "hostInit",
|
|
4571
4826
|
entryPath: () => getHostAutoInitPath(),
|
|
4572
4827
|
inject: hostInitInjectLocation,
|
|
4573
|
-
forceClientInjected: Object.keys(options.exposes).length > 0
|
|
4828
|
+
forceClientInjected: Object.keys(options.exposes).length > 0,
|
|
4829
|
+
skipTransformFor: Object.values(options.exposes).map((expose) => expose.import)
|
|
4574
4830
|
}),
|
|
4575
4831
|
...addEntry({
|
|
4576
4832
|
entryName: "virtualExposes",
|
|
@@ -4715,7 +4971,7 @@ function federation(mfUserOptions) {
|
|
|
4715
4971
|
*
|
|
4716
4972
|
* @see https://rollupjs.org/plugin-development/#synthetic-named-exports
|
|
4717
4973
|
*/
|
|
4718
|
-
if (!/\bexport\s+const\s+__moduleExports\b/.test(code)) {
|
|
4974
|
+
if (!/\bexport\s+const\s+__moduleExports\b/.test(code) && !/\bexport\s*\{[^}]*__moduleExports/.test(code)) {
|
|
4719
4975
|
const nextCode = code.replace("export default exportModule", "export const __moduleExports = exportModule;\nexport default exportModule.__esModule ? exportModule.default : exportModule");
|
|
4720
4976
|
code = nextCode === code ? `${code}\nexport const __moduleExports = exportModule;\n` : nextCode;
|
|
4721
4977
|
}
|
|
@@ -4788,6 +5044,7 @@ function federation(mfUserOptions) {
|
|
|
4788
5044
|
config.optimizeDeps.include.push("@module-federation/runtime");
|
|
4789
5045
|
options.runtimePlugins.forEach((p) => {
|
|
4790
5046
|
const pluginPath = typeof p === "string" ? p : p[0];
|
|
5047
|
+
if (SSR_ONLY_PLUGINS.has(pluginPath)) return;
|
|
4791
5048
|
if (pluginPath && !pluginPath.startsWith(".") && !pluginPath.startsWith("/") && !pluginPath.startsWith("\0") && !pluginPath.startsWith("virtual:")) config.optimizeDeps.include.push(pluginPath);
|
|
4792
5049
|
});
|
|
4793
5050
|
if (isRolldown) {
|
|
@@ -4803,6 +5060,7 @@ function federation(mfUserOptions) {
|
|
|
4803
5060
|
}
|
|
4804
5061
|
},
|
|
4805
5062
|
...Manifest(),
|
|
5063
|
+
...pluginSSRRemoteEntry(options),
|
|
4806
5064
|
...VarRemoteEntry(),
|
|
4807
5065
|
{
|
|
4808
5066
|
name: "module-federation-vinext-fix-rsc-preload-as",
|
|
@@ -4876,5 +5134,8 @@ function federation(mfUserOptions) {
|
|
|
4876
5134
|
})()
|
|
4877
5135
|
];
|
|
4878
5136
|
}
|
|
5137
|
+
function createModuleFederationConfig(options) {
|
|
5138
|
+
return options;
|
|
5139
|
+
}
|
|
4879
5140
|
//#endregion
|
|
4880
|
-
export { federation };
|
|
5141
|
+
export { createModuleFederationConfig, federation };
|