@module-federation/vite 1.13.7 → 1.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/index.cjs +234 -7
- package/lib/index.d.cts +1 -0
- package/lib/index.d.mts +1 -0
- package/lib/index.mjs +235 -8
- package/package.json +1 -1
package/lib/index.cjs
CHANGED
|
@@ -461,6 +461,191 @@ function PluginDevProxyModuleTopLevelAwait() {
|
|
|
461
461
|
};
|
|
462
462
|
}
|
|
463
463
|
//#endregion
|
|
464
|
+
//#region src/plugins/pluginDevRemoteHmr.ts
|
|
465
|
+
const REMOTE_HMR_ENDPOINT = "__mf_hmr";
|
|
466
|
+
const REMOTE_HMR_EVENT = "mf:remote-update";
|
|
467
|
+
const REMOTE_HMR_CONNECT_RETRY_DELAY_MS = 1e3;
|
|
468
|
+
const REMOTE_HMR_CONNECT_MAX_RETRIES = 10;
|
|
469
|
+
function getBasePath(base) {
|
|
470
|
+
if (!base) return "/";
|
|
471
|
+
if (base.startsWith("http://") || base.startsWith("https://")) try {
|
|
472
|
+
return new URL(base).pathname || "/";
|
|
473
|
+
} catch {
|
|
474
|
+
return "/";
|
|
475
|
+
}
|
|
476
|
+
return base;
|
|
477
|
+
}
|
|
478
|
+
function getRemoteHmrPath(base) {
|
|
479
|
+
return `${getBasePath(base).replace(/\/?$/, "/")}${REMOTE_HMR_ENDPOINT}`.replace(/\/{2,}/g, "/");
|
|
480
|
+
}
|
|
481
|
+
function getHmrWsPath(base, hmrPath) {
|
|
482
|
+
const normalizedBase = getBasePath(base);
|
|
483
|
+
const normalizedPath = getBasePath(hmrPath || "");
|
|
484
|
+
if (!normalizedPath || normalizedPath === "/") return normalizedBase;
|
|
485
|
+
return `${normalizedBase.endsWith("/") ? normalizedBase.slice(0, -1) : normalizedBase}/${normalizedPath.startsWith("/") ? normalizedPath.slice(1) : normalizedPath}`;
|
|
486
|
+
}
|
|
487
|
+
function shouldIgnoreFile(file, options) {
|
|
488
|
+
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__temp/") || file.includes("\\.__mf__temp\\");
|
|
489
|
+
}
|
|
490
|
+
function getRemoteHmrWsUrl(server) {
|
|
491
|
+
const hmr = server.config.server.hmr;
|
|
492
|
+
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}`;
|
|
493
|
+
}
|
|
494
|
+
function getLocalFallbackOrigin(server) {
|
|
495
|
+
return `${server.config.server.https ? "https" : "http"}://${typeof server.config.server.host === "string" && server.config.server.host !== "0.0.0.0" && server.config.server.host !== "::" ? server.config.server.host : "localhost"}:${server.config.server.port || 5173}`;
|
|
496
|
+
}
|
|
497
|
+
function getRemoteHmrEndpoint(remoteEntry, server) {
|
|
498
|
+
try {
|
|
499
|
+
const remoteManifestUrl = new URL(remoteEntry, getLocalFallbackOrigin(server));
|
|
500
|
+
remoteManifestUrl.pathname = `/${remoteManifestUrl.pathname.split("/").filter(Boolean).slice(0, -1).join("/")}`;
|
|
501
|
+
if (!remoteManifestUrl.pathname.endsWith("/")) remoteManifestUrl.pathname += "/";
|
|
502
|
+
remoteManifestUrl.search = "";
|
|
503
|
+
remoteManifestUrl.hash = "";
|
|
504
|
+
return new URL(REMOTE_HMR_ENDPOINT, remoteManifestUrl).toString();
|
|
505
|
+
} catch {
|
|
506
|
+
return null;
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
function parseRemoteHmrMessage(rawData) {
|
|
510
|
+
if (typeof rawData !== "string") return null;
|
|
511
|
+
try {
|
|
512
|
+
const parsed = JSON.parse(rawData);
|
|
513
|
+
if (parsed?.type !== "custom" || typeof parsed?.event !== "string") return null;
|
|
514
|
+
return parsed;
|
|
515
|
+
} catch {
|
|
516
|
+
return null;
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
function getStringPreview(value, max = 180) {
|
|
520
|
+
let rawValue = "";
|
|
521
|
+
if (typeof value === "string") rawValue = value;
|
|
522
|
+
else if (value instanceof Error) rawValue = `${value.name}: ${value.message}`;
|
|
523
|
+
else if (typeof value === "object" && value !== null) try {
|
|
524
|
+
rawValue = JSON.stringify(value);
|
|
525
|
+
} catch {}
|
|
526
|
+
return rawValue.slice(0, max);
|
|
527
|
+
}
|
|
528
|
+
function isRemoteHmrEnabled(dev) {
|
|
529
|
+
return typeof dev === "object" && dev !== null && dev.remoteHmr === true;
|
|
530
|
+
}
|
|
531
|
+
function pluginDevRemoteHmr(options) {
|
|
532
|
+
return {
|
|
533
|
+
name: "module-federation-dev-remote-hmr",
|
|
534
|
+
apply: "serve",
|
|
535
|
+
configureServer(server) {
|
|
536
|
+
if (!isRemoteHmrEnabled(options.dev)) return;
|
|
537
|
+
const isRemote = Object.keys(options.exposes).length > 0;
|
|
538
|
+
const isHost = Object.keys(options.remotes).length > 0;
|
|
539
|
+
if (isRemote) {
|
|
540
|
+
const endpointPath = getRemoteHmrPath(server.config.base);
|
|
541
|
+
const wsUrl = getRemoteHmrWsUrl(server);
|
|
542
|
+
server.middlewares.use((req, res, next) => {
|
|
543
|
+
if (req.url?.replace(/\?.*/, "") !== endpointPath) {
|
|
544
|
+
next();
|
|
545
|
+
return;
|
|
546
|
+
}
|
|
547
|
+
res.setHeader("Content-Type", "application/json");
|
|
548
|
+
res.setHeader("Access-Control-Allow-Origin", "*");
|
|
549
|
+
res.end(JSON.stringify({
|
|
550
|
+
remote: options.name,
|
|
551
|
+
event: REMOTE_HMR_EVENT,
|
|
552
|
+
wsUrl
|
|
553
|
+
}));
|
|
554
|
+
});
|
|
555
|
+
const broadcast = (file) => {
|
|
556
|
+
if (shouldIgnoreFile(file, options)) return;
|
|
557
|
+
server.ws.send({
|
|
558
|
+
type: "custom",
|
|
559
|
+
event: REMOTE_HMR_EVENT,
|
|
560
|
+
data: {
|
|
561
|
+
remote: options.name,
|
|
562
|
+
file,
|
|
563
|
+
ts: Date.now()
|
|
564
|
+
}
|
|
565
|
+
});
|
|
566
|
+
};
|
|
567
|
+
server.watcher.on("change", broadcast);
|
|
568
|
+
server.watcher.on("add", broadcast);
|
|
569
|
+
server.watcher.on("unlink", broadcast);
|
|
570
|
+
server.httpServer?.once("close", () => {
|
|
571
|
+
server.watcher.off("change", broadcast);
|
|
572
|
+
server.watcher.off("add", broadcast);
|
|
573
|
+
server.watcher.off("unlink", broadcast);
|
|
574
|
+
});
|
|
575
|
+
}
|
|
576
|
+
if (isHost) {
|
|
577
|
+
const connections = [];
|
|
578
|
+
const reconnectTimers = /* @__PURE__ */ new Map();
|
|
579
|
+
let isTearingDown = false;
|
|
580
|
+
const clearReconnectTimer = (remoteName) => {
|
|
581
|
+
const timer = reconnectTimers.get(remoteName);
|
|
582
|
+
if (!timer) return;
|
|
583
|
+
clearTimeout(timer);
|
|
584
|
+
reconnectTimers.delete(remoteName);
|
|
585
|
+
};
|
|
586
|
+
const scheduleReconnect = (remoteName, remote, attempt, reason) => {
|
|
587
|
+
if (isTearingDown) return;
|
|
588
|
+
if (attempt >= REMOTE_HMR_CONNECT_MAX_RETRIES) {
|
|
589
|
+
mfWarn(`Remote "${remoteName}" full HMR reconnect skipped after ${REMOTE_HMR_CONNECT_MAX_RETRIES} attempts: ${reason}`);
|
|
590
|
+
return;
|
|
591
|
+
}
|
|
592
|
+
clearReconnectTimer(remoteName);
|
|
593
|
+
const timer = setTimeout(() => {
|
|
594
|
+
reconnectTimers.delete(remoteName);
|
|
595
|
+
connectRemote(remoteName, remote, attempt + 1);
|
|
596
|
+
}, REMOTE_HMR_CONNECT_RETRY_DELAY_MS);
|
|
597
|
+
reconnectTimers.set(remoteName, timer);
|
|
598
|
+
};
|
|
599
|
+
const connectRemote = async (remoteName, remote, attempt = 0) => {
|
|
600
|
+
if (isTearingDown) return;
|
|
601
|
+
const endpoint = getRemoteHmrEndpoint(remote.entry, server);
|
|
602
|
+
if (!endpoint) {
|
|
603
|
+
mfWarn(`Failed to build HMR endpoint URL for remote "${remoteName}"`);
|
|
604
|
+
return;
|
|
605
|
+
}
|
|
606
|
+
try {
|
|
607
|
+
const metadataResponse = await fetch(endpoint);
|
|
608
|
+
if (!metadataResponse.ok) {
|
|
609
|
+
mfWarn(`Failed to fetch remote HMR metadata from "${remoteName}": ${metadataResponse.status}`);
|
|
610
|
+
scheduleReconnect(remoteName, remote, attempt, `HTTP ${metadataResponse.status}`);
|
|
611
|
+
return;
|
|
612
|
+
}
|
|
613
|
+
const metadata = await metadataResponse.json();
|
|
614
|
+
if (metadata.event !== REMOTE_HMR_EVENT || !metadata.wsUrl) {
|
|
615
|
+
mfWarn(`Remote "${remoteName}" returned unexpected HMR metadata shape`);
|
|
616
|
+
return;
|
|
617
|
+
}
|
|
618
|
+
const ws = new WebSocket(metadata.wsUrl, "vite-hmr");
|
|
619
|
+
ws.onmessage = (rawEvent) => {
|
|
620
|
+
const message = parseRemoteHmrMessage(rawEvent.data);
|
|
621
|
+
if (!message || message.event !== REMOTE_HMR_EVENT) return;
|
|
622
|
+
server.ws.send({ type: "full-reload" });
|
|
623
|
+
};
|
|
624
|
+
ws.onopen = () => clearReconnectTimer(remoteName);
|
|
625
|
+
ws.onerror = (error) => mfWarn(`Remote HMR socket error for "${remoteName}":`, error);
|
|
626
|
+
ws.onclose = () => scheduleReconnect(remoteName, remote, attempt, "socket closed");
|
|
627
|
+
connections.push(ws);
|
|
628
|
+
} catch (error) {
|
|
629
|
+
mfWarn(`Failed to connect remote HMR for "${remoteName}" on attempt ${attempt + 1}: ${getStringPreview(error)}`);
|
|
630
|
+
scheduleReconnect(remoteName, remote, attempt, getStringPreview(error));
|
|
631
|
+
}
|
|
632
|
+
};
|
|
633
|
+
const teardown = () => {
|
|
634
|
+
isTearingDown = true;
|
|
635
|
+
reconnectTimers.forEach((timer) => clearTimeout(timer));
|
|
636
|
+
reconnectTimers.clear();
|
|
637
|
+
connections.forEach((connection) => {
|
|
638
|
+
if (connection.readyState !== connection.CLOSING && connection.readyState !== connection.CLOSED) connection.close();
|
|
639
|
+
});
|
|
640
|
+
connections.length = 0;
|
|
641
|
+
};
|
|
642
|
+
for (const [remoteName, remote] of Object.entries(options.remotes)) connectRemote(remoteName, remote);
|
|
643
|
+
server.httpServer?.once("close", teardown);
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
};
|
|
647
|
+
}
|
|
648
|
+
//#endregion
|
|
464
649
|
//#region src/plugins/pluginDts.ts
|
|
465
650
|
const DEFAULT_DEV_OPTIONS = {
|
|
466
651
|
disableLiveReload: true,
|
|
@@ -1323,24 +1508,49 @@ function getPackageEsmEntryPath(pkg) {
|
|
|
1323
1508
|
}
|
|
1324
1509
|
function getEsmNamedExports(pkg) {
|
|
1325
1510
|
let source = "";
|
|
1511
|
+
let entryPath;
|
|
1326
1512
|
try {
|
|
1327
|
-
|
|
1513
|
+
entryPath = getPackageEsmEntryPath(pkg);
|
|
1328
1514
|
if (!entryPath) return [];
|
|
1329
1515
|
const { initSync, parse } = localRequire("es-module-lexer");
|
|
1330
1516
|
initSync();
|
|
1331
1517
|
source = (0, fs.readFileSync)(entryPath, "utf-8");
|
|
1332
1518
|
const [, exports] = parse(source, entryPath);
|
|
1333
1519
|
const names = exports.map((item) => item.n).filter((name) => isValidEsmExportName(name));
|
|
1334
|
-
const regexNames = getNamedExportsViaRegex(source);
|
|
1520
|
+
const regexNames = getNamedExportsViaRegex(source, entryPath);
|
|
1335
1521
|
const filteredNames = names.filter((name) => name !== "type" || regexNames.includes(name));
|
|
1336
1522
|
if (filteredNames.length > 0) return [...new Set([...filteredNames, ...regexNames])];
|
|
1337
1523
|
return regexNames;
|
|
1338
1524
|
} catch {
|
|
1339
|
-
return source ? getNamedExportsViaRegex(source) : [];
|
|
1525
|
+
return source ? getNamedExportsViaRegex(source, entryPath) : [];
|
|
1526
|
+
}
|
|
1527
|
+
}
|
|
1528
|
+
function resolveRelativeModule(filePath, specifier) {
|
|
1529
|
+
const dir = pathe.default.dirname(filePath);
|
|
1530
|
+
const exact = pathe.default.resolve(dir, specifier);
|
|
1531
|
+
if ((0, fs.existsSync)(exact) && !(0, fs.statSync)(exact).isDirectory()) return exact;
|
|
1532
|
+
const extensions = [
|
|
1533
|
+
".ts",
|
|
1534
|
+
".tsx",
|
|
1535
|
+
".js",
|
|
1536
|
+
".jsx",
|
|
1537
|
+
".mjs",
|
|
1538
|
+
".mts"
|
|
1539
|
+
];
|
|
1540
|
+
for (const ext of extensions) {
|
|
1541
|
+
const candidate = pathe.default.resolve(dir, specifier + ext);
|
|
1542
|
+
if ((0, fs.existsSync)(candidate) && !(0, fs.statSync)(candidate).isDirectory()) return candidate;
|
|
1543
|
+
}
|
|
1544
|
+
const resolved = pathe.default.resolve(dir, specifier);
|
|
1545
|
+
for (const ext of extensions) {
|
|
1546
|
+
const candidate = pathe.default.join(resolved, "index" + ext);
|
|
1547
|
+
if ((0, fs.existsSync)(candidate)) return candidate;
|
|
1340
1548
|
}
|
|
1341
1549
|
}
|
|
1342
|
-
function getNamedExportsViaRegex(source) {
|
|
1550
|
+
function getNamedExportsViaRegex(source, filePath, visited) {
|
|
1343
1551
|
const names = /* @__PURE__ */ new Set();
|
|
1552
|
+
visited = visited || /* @__PURE__ */ new Set();
|
|
1553
|
+
if (filePath) visited.add(filePath);
|
|
1344
1554
|
const declRegex = new RegExp(`export\\s+(?:async\\s+)?(?:function(?:\\*\\s*|\\s+\\*?\\s*)|const\\s+|let\\s+|var\\s+|class\\s+)(${JS_IDENTIFIER_PATTERN})`, "gu");
|
|
1345
1555
|
let match;
|
|
1346
1556
|
while ((match = declRegex.exec(source)) !== null) {
|
|
@@ -1361,6 +1571,19 @@ function getNamedExportsViaRegex(source) {
|
|
|
1361
1571
|
if (isValidEsmExportName(name)) names.add(name);
|
|
1362
1572
|
}
|
|
1363
1573
|
}
|
|
1574
|
+
if (filePath) {
|
|
1575
|
+
const starExportRegex = /export\s+\*\s+from\s+['"]([^'"]+)['"]/g;
|
|
1576
|
+
while ((match = starExportRegex.exec(source)) !== null) {
|
|
1577
|
+
const specifier = match[1];
|
|
1578
|
+
if (!specifier.startsWith(".")) continue;
|
|
1579
|
+
const resolvedPath = resolveRelativeModule(filePath, specifier);
|
|
1580
|
+
if (!resolvedPath || visited.has(resolvedPath)) continue;
|
|
1581
|
+
try {
|
|
1582
|
+
const reExportNames = getNamedExportsViaRegex((0, fs.readFileSync)(resolvedPath, "utf-8"), resolvedPath, visited);
|
|
1583
|
+
for (const name of reExportNames) names.add(name);
|
|
1584
|
+
} catch {}
|
|
1585
|
+
}
|
|
1586
|
+
}
|
|
1364
1587
|
return [...names];
|
|
1365
1588
|
}
|
|
1366
1589
|
function getPackageNamedExports(pkg) {
|
|
@@ -1522,6 +1745,10 @@ function generateLocalSharedImportMap() {
|
|
|
1522
1745
|
const isAstro = hasPackageDependency("astro");
|
|
1523
1746
|
const useDirectReactImport = isVinext || isAstro;
|
|
1524
1747
|
const options = getNormalizeModuleFederationOptions();
|
|
1748
|
+
const getPackagePath = (pkg, shareItem) => {
|
|
1749
|
+
if (useDirectReactImport && pkg === "react") return "react";
|
|
1750
|
+
return getConcreteSharedImportSource(pkg, shareItem) || getLocalProviderImportPath(pkg) || getSharedImportSource(pkg, shareItem);
|
|
1751
|
+
};
|
|
1525
1752
|
return `
|
|
1526
1753
|
import {loadShare} from "@module-federation/runtime";
|
|
1527
1754
|
const importMap = {
|
|
@@ -1529,8 +1756,7 @@ function generateLocalSharedImportMap() {
|
|
|
1529
1756
|
const shareItem = getNormalizeShareItem(pkg);
|
|
1530
1757
|
return `
|
|
1531
1758
|
${JSON.stringify(pkg)}: async () => {
|
|
1532
|
-
${shareItem?.shareConfig.import === false ? `throw new Error(\`[Module Federation] Shared module '\${${JSON.stringify(pkg)}}' must be provided by host\`);` :
|
|
1533
|
-
return pkg;` : `let pkg = await import(${JSON.stringify(getSharedImportSource(pkg, shareItem))});
|
|
1759
|
+
${shareItem?.shareConfig.import === false ? `throw new Error(\`[Module Federation] Shared module '\${${JSON.stringify(pkg)}}' must be provided by host\`);` : `let pkg = await import(${JSON.stringify(getPackagePath(pkg, shareItem))});
|
|
1534
1760
|
return pkg;`}
|
|
1535
1761
|
}
|
|
1536
1762
|
`;
|
|
@@ -1975,7 +2201,7 @@ const Manifest = () => {
|
|
|
1975
2201
|
root = config.root;
|
|
1976
2202
|
let base = config.base;
|
|
1977
2203
|
if (_command === "serve") base = (config.server.origin || "") + config.base;
|
|
1978
|
-
publicPath = resolvePublicPath(mfOptions, base, _originalConfigBase);
|
|
2204
|
+
publicPath = mfOptions.publicPath === "auto" ? "auto" : resolvePublicPath(mfOptions, base, _originalConfigBase);
|
|
1979
2205
|
},
|
|
1980
2206
|
async generateBundle(options, bundle) {
|
|
1981
2207
|
if (!mfManifestName) return;
|
|
@@ -3078,6 +3304,7 @@ function federation(mfUserOptions) {
|
|
|
3078
3304
|
checkAliasConflicts({ shared }),
|
|
3079
3305
|
normalizeOptimizeDeps_default,
|
|
3080
3306
|
...pluginDts(options),
|
|
3307
|
+
pluginDevRemoteHmr(options),
|
|
3081
3308
|
...addEntry({
|
|
3082
3309
|
entryName: "remoteEntry",
|
|
3083
3310
|
entryPath: remoteEntryId,
|
package/lib/index.d.cts
CHANGED
package/lib/index.d.mts
CHANGED
package/lib/index.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { createRequire } from "node:module";
|
|
2
2
|
import defu from "defu";
|
|
3
3
|
import * as fs from "fs";
|
|
4
|
-
import { existsSync, mkdirSync, readFileSync, writeFile, writeFileSync } from "fs";
|
|
4
|
+
import { existsSync, mkdirSync, readFileSync, statSync, writeFile, writeFileSync } from "fs";
|
|
5
5
|
import { createRequire as createRequire$1 } from "module";
|
|
6
6
|
import * as path$1 from "pathe";
|
|
7
7
|
import path, { basename, dirname, join, parse, resolve } from "pathe";
|
|
@@ -439,6 +439,191 @@ function PluginDevProxyModuleTopLevelAwait() {
|
|
|
439
439
|
};
|
|
440
440
|
}
|
|
441
441
|
//#endregion
|
|
442
|
+
//#region src/plugins/pluginDevRemoteHmr.ts
|
|
443
|
+
const REMOTE_HMR_ENDPOINT = "__mf_hmr";
|
|
444
|
+
const REMOTE_HMR_EVENT = "mf:remote-update";
|
|
445
|
+
const REMOTE_HMR_CONNECT_RETRY_DELAY_MS = 1e3;
|
|
446
|
+
const REMOTE_HMR_CONNECT_MAX_RETRIES = 10;
|
|
447
|
+
function getBasePath(base) {
|
|
448
|
+
if (!base) return "/";
|
|
449
|
+
if (base.startsWith("http://") || base.startsWith("https://")) try {
|
|
450
|
+
return new URL(base).pathname || "/";
|
|
451
|
+
} catch {
|
|
452
|
+
return "/";
|
|
453
|
+
}
|
|
454
|
+
return base;
|
|
455
|
+
}
|
|
456
|
+
function getRemoteHmrPath(base) {
|
|
457
|
+
return `${getBasePath(base).replace(/\/?$/, "/")}${REMOTE_HMR_ENDPOINT}`.replace(/\/{2,}/g, "/");
|
|
458
|
+
}
|
|
459
|
+
function getHmrWsPath(base, hmrPath) {
|
|
460
|
+
const normalizedBase = getBasePath(base);
|
|
461
|
+
const normalizedPath = getBasePath(hmrPath || "");
|
|
462
|
+
if (!normalizedPath || normalizedPath === "/") return normalizedBase;
|
|
463
|
+
return `${normalizedBase.endsWith("/") ? normalizedBase.slice(0, -1) : normalizedBase}/${normalizedPath.startsWith("/") ? normalizedPath.slice(1) : normalizedPath}`;
|
|
464
|
+
}
|
|
465
|
+
function shouldIgnoreFile(file, options) {
|
|
466
|
+
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__temp/") || file.includes("\\.__mf__temp\\");
|
|
467
|
+
}
|
|
468
|
+
function getRemoteHmrWsUrl(server) {
|
|
469
|
+
const hmr = server.config.server.hmr;
|
|
470
|
+
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}`;
|
|
471
|
+
}
|
|
472
|
+
function getLocalFallbackOrigin(server) {
|
|
473
|
+
return `${server.config.server.https ? "https" : "http"}://${typeof server.config.server.host === "string" && server.config.server.host !== "0.0.0.0" && server.config.server.host !== "::" ? server.config.server.host : "localhost"}:${server.config.server.port || 5173}`;
|
|
474
|
+
}
|
|
475
|
+
function getRemoteHmrEndpoint(remoteEntry, server) {
|
|
476
|
+
try {
|
|
477
|
+
const remoteManifestUrl = new URL(remoteEntry, getLocalFallbackOrigin(server));
|
|
478
|
+
remoteManifestUrl.pathname = `/${remoteManifestUrl.pathname.split("/").filter(Boolean).slice(0, -1).join("/")}`;
|
|
479
|
+
if (!remoteManifestUrl.pathname.endsWith("/")) remoteManifestUrl.pathname += "/";
|
|
480
|
+
remoteManifestUrl.search = "";
|
|
481
|
+
remoteManifestUrl.hash = "";
|
|
482
|
+
return new URL(REMOTE_HMR_ENDPOINT, remoteManifestUrl).toString();
|
|
483
|
+
} catch {
|
|
484
|
+
return null;
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
function parseRemoteHmrMessage(rawData) {
|
|
488
|
+
if (typeof rawData !== "string") return null;
|
|
489
|
+
try {
|
|
490
|
+
const parsed = JSON.parse(rawData);
|
|
491
|
+
if (parsed?.type !== "custom" || typeof parsed?.event !== "string") return null;
|
|
492
|
+
return parsed;
|
|
493
|
+
} catch {
|
|
494
|
+
return null;
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
function getStringPreview(value, max = 180) {
|
|
498
|
+
let rawValue = "";
|
|
499
|
+
if (typeof value === "string") rawValue = value;
|
|
500
|
+
else if (value instanceof Error) rawValue = `${value.name}: ${value.message}`;
|
|
501
|
+
else if (typeof value === "object" && value !== null) try {
|
|
502
|
+
rawValue = JSON.stringify(value);
|
|
503
|
+
} catch {}
|
|
504
|
+
return rawValue.slice(0, max);
|
|
505
|
+
}
|
|
506
|
+
function isRemoteHmrEnabled(dev) {
|
|
507
|
+
return typeof dev === "object" && dev !== null && dev.remoteHmr === true;
|
|
508
|
+
}
|
|
509
|
+
function pluginDevRemoteHmr(options) {
|
|
510
|
+
return {
|
|
511
|
+
name: "module-federation-dev-remote-hmr",
|
|
512
|
+
apply: "serve",
|
|
513
|
+
configureServer(server) {
|
|
514
|
+
if (!isRemoteHmrEnabled(options.dev)) return;
|
|
515
|
+
const isRemote = Object.keys(options.exposes).length > 0;
|
|
516
|
+
const isHost = Object.keys(options.remotes).length > 0;
|
|
517
|
+
if (isRemote) {
|
|
518
|
+
const endpointPath = getRemoteHmrPath(server.config.base);
|
|
519
|
+
const wsUrl = getRemoteHmrWsUrl(server);
|
|
520
|
+
server.middlewares.use((req, res, next) => {
|
|
521
|
+
if (req.url?.replace(/\?.*/, "") !== endpointPath) {
|
|
522
|
+
next();
|
|
523
|
+
return;
|
|
524
|
+
}
|
|
525
|
+
res.setHeader("Content-Type", "application/json");
|
|
526
|
+
res.setHeader("Access-Control-Allow-Origin", "*");
|
|
527
|
+
res.end(JSON.stringify({
|
|
528
|
+
remote: options.name,
|
|
529
|
+
event: REMOTE_HMR_EVENT,
|
|
530
|
+
wsUrl
|
|
531
|
+
}));
|
|
532
|
+
});
|
|
533
|
+
const broadcast = (file) => {
|
|
534
|
+
if (shouldIgnoreFile(file, options)) return;
|
|
535
|
+
server.ws.send({
|
|
536
|
+
type: "custom",
|
|
537
|
+
event: REMOTE_HMR_EVENT,
|
|
538
|
+
data: {
|
|
539
|
+
remote: options.name,
|
|
540
|
+
file,
|
|
541
|
+
ts: Date.now()
|
|
542
|
+
}
|
|
543
|
+
});
|
|
544
|
+
};
|
|
545
|
+
server.watcher.on("change", broadcast);
|
|
546
|
+
server.watcher.on("add", broadcast);
|
|
547
|
+
server.watcher.on("unlink", broadcast);
|
|
548
|
+
server.httpServer?.once("close", () => {
|
|
549
|
+
server.watcher.off("change", broadcast);
|
|
550
|
+
server.watcher.off("add", broadcast);
|
|
551
|
+
server.watcher.off("unlink", broadcast);
|
|
552
|
+
});
|
|
553
|
+
}
|
|
554
|
+
if (isHost) {
|
|
555
|
+
const connections = [];
|
|
556
|
+
const reconnectTimers = /* @__PURE__ */ new Map();
|
|
557
|
+
let isTearingDown = false;
|
|
558
|
+
const clearReconnectTimer = (remoteName) => {
|
|
559
|
+
const timer = reconnectTimers.get(remoteName);
|
|
560
|
+
if (!timer) return;
|
|
561
|
+
clearTimeout(timer);
|
|
562
|
+
reconnectTimers.delete(remoteName);
|
|
563
|
+
};
|
|
564
|
+
const scheduleReconnect = (remoteName, remote, attempt, reason) => {
|
|
565
|
+
if (isTearingDown) return;
|
|
566
|
+
if (attempt >= REMOTE_HMR_CONNECT_MAX_RETRIES) {
|
|
567
|
+
mfWarn(`Remote "${remoteName}" full HMR reconnect skipped after ${REMOTE_HMR_CONNECT_MAX_RETRIES} attempts: ${reason}`);
|
|
568
|
+
return;
|
|
569
|
+
}
|
|
570
|
+
clearReconnectTimer(remoteName);
|
|
571
|
+
const timer = setTimeout(() => {
|
|
572
|
+
reconnectTimers.delete(remoteName);
|
|
573
|
+
connectRemote(remoteName, remote, attempt + 1);
|
|
574
|
+
}, REMOTE_HMR_CONNECT_RETRY_DELAY_MS);
|
|
575
|
+
reconnectTimers.set(remoteName, timer);
|
|
576
|
+
};
|
|
577
|
+
const connectRemote = async (remoteName, remote, attempt = 0) => {
|
|
578
|
+
if (isTearingDown) return;
|
|
579
|
+
const endpoint = getRemoteHmrEndpoint(remote.entry, server);
|
|
580
|
+
if (!endpoint) {
|
|
581
|
+
mfWarn(`Failed to build HMR endpoint URL for remote "${remoteName}"`);
|
|
582
|
+
return;
|
|
583
|
+
}
|
|
584
|
+
try {
|
|
585
|
+
const metadataResponse = await fetch(endpoint);
|
|
586
|
+
if (!metadataResponse.ok) {
|
|
587
|
+
mfWarn(`Failed to fetch remote HMR metadata from "${remoteName}": ${metadataResponse.status}`);
|
|
588
|
+
scheduleReconnect(remoteName, remote, attempt, `HTTP ${metadataResponse.status}`);
|
|
589
|
+
return;
|
|
590
|
+
}
|
|
591
|
+
const metadata = await metadataResponse.json();
|
|
592
|
+
if (metadata.event !== REMOTE_HMR_EVENT || !metadata.wsUrl) {
|
|
593
|
+
mfWarn(`Remote "${remoteName}" returned unexpected HMR metadata shape`);
|
|
594
|
+
return;
|
|
595
|
+
}
|
|
596
|
+
const ws = new WebSocket(metadata.wsUrl, "vite-hmr");
|
|
597
|
+
ws.onmessage = (rawEvent) => {
|
|
598
|
+
const message = parseRemoteHmrMessage(rawEvent.data);
|
|
599
|
+
if (!message || message.event !== REMOTE_HMR_EVENT) return;
|
|
600
|
+
server.ws.send({ type: "full-reload" });
|
|
601
|
+
};
|
|
602
|
+
ws.onopen = () => clearReconnectTimer(remoteName);
|
|
603
|
+
ws.onerror = (error) => mfWarn(`Remote HMR socket error for "${remoteName}":`, error);
|
|
604
|
+
ws.onclose = () => scheduleReconnect(remoteName, remote, attempt, "socket closed");
|
|
605
|
+
connections.push(ws);
|
|
606
|
+
} catch (error) {
|
|
607
|
+
mfWarn(`Failed to connect remote HMR for "${remoteName}" on attempt ${attempt + 1}: ${getStringPreview(error)}`);
|
|
608
|
+
scheduleReconnect(remoteName, remote, attempt, getStringPreview(error));
|
|
609
|
+
}
|
|
610
|
+
};
|
|
611
|
+
const teardown = () => {
|
|
612
|
+
isTearingDown = true;
|
|
613
|
+
reconnectTimers.forEach((timer) => clearTimeout(timer));
|
|
614
|
+
reconnectTimers.clear();
|
|
615
|
+
connections.forEach((connection) => {
|
|
616
|
+
if (connection.readyState !== connection.CLOSING && connection.readyState !== connection.CLOSED) connection.close();
|
|
617
|
+
});
|
|
618
|
+
connections.length = 0;
|
|
619
|
+
};
|
|
620
|
+
for (const [remoteName, remote] of Object.entries(options.remotes)) connectRemote(remoteName, remote);
|
|
621
|
+
server.httpServer?.once("close", teardown);
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
};
|
|
625
|
+
}
|
|
626
|
+
//#endregion
|
|
442
627
|
//#region src/plugins/pluginDts.ts
|
|
443
628
|
const DEFAULT_DEV_OPTIONS = {
|
|
444
629
|
disableLiveReload: true,
|
|
@@ -1300,24 +1485,49 @@ function getPackageEsmEntryPath(pkg) {
|
|
|
1300
1485
|
}
|
|
1301
1486
|
function getEsmNamedExports(pkg) {
|
|
1302
1487
|
let source = "";
|
|
1488
|
+
let entryPath;
|
|
1303
1489
|
try {
|
|
1304
|
-
|
|
1490
|
+
entryPath = getPackageEsmEntryPath(pkg);
|
|
1305
1491
|
if (!entryPath) return [];
|
|
1306
1492
|
const { initSync, parse } = localRequire("es-module-lexer");
|
|
1307
1493
|
initSync();
|
|
1308
1494
|
source = readFileSync(entryPath, "utf-8");
|
|
1309
1495
|
const [, exports] = parse(source, entryPath);
|
|
1310
1496
|
const names = exports.map((item) => item.n).filter((name) => isValidEsmExportName(name));
|
|
1311
|
-
const regexNames = getNamedExportsViaRegex(source);
|
|
1497
|
+
const regexNames = getNamedExportsViaRegex(source, entryPath);
|
|
1312
1498
|
const filteredNames = names.filter((name) => name !== "type" || regexNames.includes(name));
|
|
1313
1499
|
if (filteredNames.length > 0) return [...new Set([...filteredNames, ...regexNames])];
|
|
1314
1500
|
return regexNames;
|
|
1315
1501
|
} catch {
|
|
1316
|
-
return source ? getNamedExportsViaRegex(source) : [];
|
|
1502
|
+
return source ? getNamedExportsViaRegex(source, entryPath) : [];
|
|
1503
|
+
}
|
|
1504
|
+
}
|
|
1505
|
+
function resolveRelativeModule(filePath, specifier) {
|
|
1506
|
+
const dir = path.dirname(filePath);
|
|
1507
|
+
const exact = path.resolve(dir, specifier);
|
|
1508
|
+
if (existsSync(exact) && !statSync(exact).isDirectory()) return exact;
|
|
1509
|
+
const extensions = [
|
|
1510
|
+
".ts",
|
|
1511
|
+
".tsx",
|
|
1512
|
+
".js",
|
|
1513
|
+
".jsx",
|
|
1514
|
+
".mjs",
|
|
1515
|
+
".mts"
|
|
1516
|
+
];
|
|
1517
|
+
for (const ext of extensions) {
|
|
1518
|
+
const candidate = path.resolve(dir, specifier + ext);
|
|
1519
|
+
if (existsSync(candidate) && !statSync(candidate).isDirectory()) return candidate;
|
|
1520
|
+
}
|
|
1521
|
+
const resolved = path.resolve(dir, specifier);
|
|
1522
|
+
for (const ext of extensions) {
|
|
1523
|
+
const candidate = path.join(resolved, "index" + ext);
|
|
1524
|
+
if (existsSync(candidate)) return candidate;
|
|
1317
1525
|
}
|
|
1318
1526
|
}
|
|
1319
|
-
function getNamedExportsViaRegex(source) {
|
|
1527
|
+
function getNamedExportsViaRegex(source, filePath, visited) {
|
|
1320
1528
|
const names = /* @__PURE__ */ new Set();
|
|
1529
|
+
visited = visited || /* @__PURE__ */ new Set();
|
|
1530
|
+
if (filePath) visited.add(filePath);
|
|
1321
1531
|
const declRegex = new RegExp(`export\\s+(?:async\\s+)?(?:function(?:\\*\\s*|\\s+\\*?\\s*)|const\\s+|let\\s+|var\\s+|class\\s+)(${JS_IDENTIFIER_PATTERN})`, "gu");
|
|
1322
1532
|
let match;
|
|
1323
1533
|
while ((match = declRegex.exec(source)) !== null) {
|
|
@@ -1338,6 +1548,19 @@ function getNamedExportsViaRegex(source) {
|
|
|
1338
1548
|
if (isValidEsmExportName(name)) names.add(name);
|
|
1339
1549
|
}
|
|
1340
1550
|
}
|
|
1551
|
+
if (filePath) {
|
|
1552
|
+
const starExportRegex = /export\s+\*\s+from\s+['"]([^'"]+)['"]/g;
|
|
1553
|
+
while ((match = starExportRegex.exec(source)) !== null) {
|
|
1554
|
+
const specifier = match[1];
|
|
1555
|
+
if (!specifier.startsWith(".")) continue;
|
|
1556
|
+
const resolvedPath = resolveRelativeModule(filePath, specifier);
|
|
1557
|
+
if (!resolvedPath || visited.has(resolvedPath)) continue;
|
|
1558
|
+
try {
|
|
1559
|
+
const reExportNames = getNamedExportsViaRegex(readFileSync(resolvedPath, "utf-8"), resolvedPath, visited);
|
|
1560
|
+
for (const name of reExportNames) names.add(name);
|
|
1561
|
+
} catch {}
|
|
1562
|
+
}
|
|
1563
|
+
}
|
|
1341
1564
|
return [...names];
|
|
1342
1565
|
}
|
|
1343
1566
|
function getPackageNamedExports(pkg) {
|
|
@@ -1499,6 +1722,10 @@ function generateLocalSharedImportMap() {
|
|
|
1499
1722
|
const isAstro = hasPackageDependency("astro");
|
|
1500
1723
|
const useDirectReactImport = isVinext || isAstro;
|
|
1501
1724
|
const options = getNormalizeModuleFederationOptions();
|
|
1725
|
+
const getPackagePath = (pkg, shareItem) => {
|
|
1726
|
+
if (useDirectReactImport && pkg === "react") return "react";
|
|
1727
|
+
return getConcreteSharedImportSource(pkg, shareItem) || getLocalProviderImportPath(pkg) || getSharedImportSource(pkg, shareItem);
|
|
1728
|
+
};
|
|
1502
1729
|
return `
|
|
1503
1730
|
import {loadShare} from "@module-federation/runtime";
|
|
1504
1731
|
const importMap = {
|
|
@@ -1506,8 +1733,7 @@ function generateLocalSharedImportMap() {
|
|
|
1506
1733
|
const shareItem = getNormalizeShareItem(pkg);
|
|
1507
1734
|
return `
|
|
1508
1735
|
${JSON.stringify(pkg)}: async () => {
|
|
1509
|
-
${shareItem?.shareConfig.import === false ? `throw new Error(\`[Module Federation] Shared module '\${${JSON.stringify(pkg)}}' must be provided by host\`);` :
|
|
1510
|
-
return pkg;` : `let pkg = await import(${JSON.stringify(getSharedImportSource(pkg, shareItem))});
|
|
1736
|
+
${shareItem?.shareConfig.import === false ? `throw new Error(\`[Module Federation] Shared module '\${${JSON.stringify(pkg)}}' must be provided by host\`);` : `let pkg = await import(${JSON.stringify(getPackagePath(pkg, shareItem))});
|
|
1511
1737
|
return pkg;`}
|
|
1512
1738
|
}
|
|
1513
1739
|
`;
|
|
@@ -1952,7 +2178,7 @@ const Manifest = () => {
|
|
|
1952
2178
|
root = config.root;
|
|
1953
2179
|
let base = config.base;
|
|
1954
2180
|
if (_command === "serve") base = (config.server.origin || "") + config.base;
|
|
1955
|
-
publicPath = resolvePublicPath(mfOptions, base, _originalConfigBase);
|
|
2181
|
+
publicPath = mfOptions.publicPath === "auto" ? "auto" : resolvePublicPath(mfOptions, base, _originalConfigBase);
|
|
1956
2182
|
},
|
|
1957
2183
|
async generateBundle(options, bundle) {
|
|
1958
2184
|
if (!mfManifestName) return;
|
|
@@ -3055,6 +3281,7 @@ function federation(mfUserOptions) {
|
|
|
3055
3281
|
checkAliasConflicts({ shared }),
|
|
3056
3282
|
normalizeOptimizeDeps_default,
|
|
3057
3283
|
...pluginDts(options),
|
|
3284
|
+
pluginDevRemoteHmr(options),
|
|
3058
3285
|
...addEntry({
|
|
3059
3286
|
entryName: "remoteEntry",
|
|
3060
3287
|
entryPath: remoteEntryId,
|