@module-federation/vite 1.13.6 → 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/README.md +1 -0
- package/lib/index.cjs +303 -24
- package/lib/index.d.cts +1 -0
- package/lib/index.d.mts +2 -1
- package/lib/index.mjs +304 -25
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -19,6 +19,7 @@ Examples live in [`gioboa/module-federation-vite-examples`](https://github.com/g
|
|
|
19
19
|
| [Angular](https://github.com/gioboa/module-federation-vite-examples/tree/main/angular) | `angular-host` | `angular-remote` | Angular |
|
|
20
20
|
| [Lit](https://github.com/gioboa/module-federation-vite-examples/tree/main/lit) | `lit-host` | `lit-remote` | Lit |
|
|
21
21
|
| [Nuxt](https://github.com/gioboa/module-federation-vite-examples/tree/main/nuxt) | `nuxt-host` | `nuxt-remote` | Nuxt 4 |
|
|
22
|
+
| [Preact](https://github.com/gioboa/module-federation-vite-examples/tree/main/preact) | `preact-host` | `preact-remote` | Preact 10 |
|
|
22
23
|
| [React](https://github.com/gioboa/module-federation-vite-examples/tree/main/react) | `react-host` | `react-remote` | React 19 |
|
|
23
24
|
| [Solid](https://github.com/gioboa/module-federation-vite-examples/tree/main/solid) | `solid-host` | `solid-remote` | Solid |
|
|
24
25
|
| [Svelte](https://github.com/gioboa/module-federation-vite-examples/tree/main/svelte) | `svelte-host` | `svelte-remote` | Svelte 5 |
|
package/lib/index.cjs
CHANGED
|
@@ -28,6 +28,7 @@ fs = __toESM(fs);
|
|
|
28
28
|
let module$1 = require("module");
|
|
29
29
|
let pathe = require("pathe");
|
|
30
30
|
pathe = __toESM(pathe);
|
|
31
|
+
let vite = require("vite");
|
|
31
32
|
let magic_string = require("magic-string");
|
|
32
33
|
magic_string = __toESM(magic_string);
|
|
33
34
|
let _rollup_pluginutils = require("@rollup/pluginutils");
|
|
@@ -227,17 +228,21 @@ const addEntry = ({ entryName, entryPath, fileName, inject = "entry" }) => {
|
|
|
227
228
|
next();
|
|
228
229
|
});
|
|
229
230
|
},
|
|
230
|
-
transformIndexHtml
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
231
|
+
transformIndexHtml: {
|
|
232
|
+
order: "pre",
|
|
233
|
+
handler(c) {
|
|
234
|
+
if (!injectHtml()) return;
|
|
235
|
+
clientInjected = true;
|
|
236
|
+
const base = viteConfig.base.replace(/\/$/, "");
|
|
237
|
+
const stripBase = (p) => base && p.startsWith(base) ? p.slice(base.length) : p;
|
|
238
|
+
const html = rewriteEntryScripts(c, (originalSrc) => {
|
|
239
|
+
return `/@id/${DEV_HTML_PROXY_PREFIX}${new URLSearchParams({
|
|
240
|
+
init: sanitizeDevEntryPath(stripBase(devEntryPath)),
|
|
241
|
+
entry: originalSrc
|
|
242
|
+
}).toString()}`;
|
|
243
|
+
});
|
|
244
|
+
return html === c ? injectEntryScript(c, stripBase(devEntryPath)) : html;
|
|
245
|
+
}
|
|
241
246
|
},
|
|
242
247
|
resolveId(id) {
|
|
243
248
|
if (id.startsWith(DEV_HTML_PROXY_PREFIX)) return id;
|
|
@@ -456,6 +461,191 @@ function PluginDevProxyModuleTopLevelAwait() {
|
|
|
456
461
|
};
|
|
457
462
|
}
|
|
458
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
|
|
459
649
|
//#region src/plugins/pluginDts.ts
|
|
460
650
|
const DEFAULT_DEV_OPTIONS = {
|
|
461
651
|
disableLiveReload: true,
|
|
@@ -1236,6 +1426,10 @@ function escapeGeneratedStringLiteral(value) {
|
|
|
1236
1426
|
function isValidJsIdentifier(name) {
|
|
1237
1427
|
return /^[$_\p{ID_Start}][$_\u200C\u200D\p{ID_Continue}]*$/u.test(name);
|
|
1238
1428
|
}
|
|
1429
|
+
function isValidEsmExportName(name) {
|
|
1430
|
+
return !!name && name !== "default" && name !== "__esModule" && isValidJsIdentifier(name);
|
|
1431
|
+
}
|
|
1432
|
+
const JS_IDENTIFIER_PATTERN = `[\$_\\p{ID_Start}][\$_\\u200C\\u200D\\p{ID_Continue}]*`;
|
|
1239
1433
|
const localRequire = (0, module$1.createRequire)(require("url").pathToFileURL(__filename).href);
|
|
1240
1434
|
function resolvePackageEntryFromProjectRoot(pkg) {
|
|
1241
1435
|
try {
|
|
@@ -1313,21 +1507,89 @@ function getPackageEsmEntryPath(pkg) {
|
|
|
1313
1507
|
}
|
|
1314
1508
|
}
|
|
1315
1509
|
function getEsmNamedExports(pkg) {
|
|
1510
|
+
let source = "";
|
|
1511
|
+
let entryPath;
|
|
1316
1512
|
try {
|
|
1317
|
-
|
|
1513
|
+
entryPath = getPackageEsmEntryPath(pkg);
|
|
1318
1514
|
if (!entryPath) return [];
|
|
1319
1515
|
const { initSync, parse } = localRequire("es-module-lexer");
|
|
1320
1516
|
initSync();
|
|
1321
|
-
|
|
1322
|
-
|
|
1517
|
+
source = (0, fs.readFileSync)(entryPath, "utf-8");
|
|
1518
|
+
const [, exports] = parse(source, entryPath);
|
|
1519
|
+
const names = exports.map((item) => item.n).filter((name) => isValidEsmExportName(name));
|
|
1520
|
+
const regexNames = getNamedExportsViaRegex(source, entryPath);
|
|
1521
|
+
const filteredNames = names.filter((name) => name !== "type" || regexNames.includes(name));
|
|
1522
|
+
if (filteredNames.length > 0) return [...new Set([...filteredNames, ...regexNames])];
|
|
1523
|
+
return regexNames;
|
|
1323
1524
|
} catch {
|
|
1324
|
-
return [];
|
|
1525
|
+
return source ? getNamedExportsViaRegex(source, entryPath) : [];
|
|
1325
1526
|
}
|
|
1326
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;
|
|
1548
|
+
}
|
|
1549
|
+
}
|
|
1550
|
+
function getNamedExportsViaRegex(source, filePath, visited) {
|
|
1551
|
+
const names = /* @__PURE__ */ new Set();
|
|
1552
|
+
visited = visited || /* @__PURE__ */ new Set();
|
|
1553
|
+
if (filePath) visited.add(filePath);
|
|
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");
|
|
1555
|
+
let match;
|
|
1556
|
+
while ((match = declRegex.exec(source)) !== null) {
|
|
1557
|
+
const name = match[1];
|
|
1558
|
+
if (isValidEsmExportName(name)) names.add(name);
|
|
1559
|
+
}
|
|
1560
|
+
const listRegex = /export\s*\{([^}]+)\}/g;
|
|
1561
|
+
const typeOnlySpecifierRegex = new RegExp(`^type\\s+${JS_IDENTIFIER_PATTERN}(?:\\s+as\\s+${JS_IDENTIFIER_PATTERN})?$`, "u");
|
|
1562
|
+
const exportSpecifierRegex = new RegExp(`(?:\\S+\\s+as\\s+)?(${JS_IDENTIFIER_PATTERN})$`, "u");
|
|
1563
|
+
while ((match = listRegex.exec(source)) !== null) {
|
|
1564
|
+
const specifiers = match[1].split(",");
|
|
1565
|
+
for (const specifier of specifiers) {
|
|
1566
|
+
const trimmed = specifier.trim();
|
|
1567
|
+
if (typeOnlySpecifierRegex.test(trimmed)) continue;
|
|
1568
|
+
const asMatch = trimmed.match(exportSpecifierRegex);
|
|
1569
|
+
if (!asMatch) continue;
|
|
1570
|
+
const name = asMatch[1];
|
|
1571
|
+
if (isValidEsmExportName(name)) names.add(name);
|
|
1572
|
+
}
|
|
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
|
+
}
|
|
1587
|
+
return [...names];
|
|
1588
|
+
}
|
|
1327
1589
|
function getPackageNamedExports(pkg) {
|
|
1328
1590
|
try {
|
|
1329
1591
|
const mod = (0, module$1.createRequire)(new URL(`file://${pathe.default.join(getPackageDetectionCwd(), "package.json")}`))(pkg);
|
|
1330
|
-
return Object.keys(mod).filter((k) =>
|
|
1592
|
+
return Object.keys(mod).filter((k) => isValidEsmExportName(k));
|
|
1331
1593
|
} catch {
|
|
1332
1594
|
return getEsmNamedExports(pkg);
|
|
1333
1595
|
}
|
|
@@ -1335,11 +1597,14 @@ function getPackageNamedExports(pkg) {
|
|
|
1335
1597
|
function getLocalProviderImportPath(pkg) {
|
|
1336
1598
|
try {
|
|
1337
1599
|
const resolved = (0, module$1.createRequire)(new URL(`file://${pathe.default.join(getPackageDetectionCwd(), "package.json")}`)).resolve(pkg);
|
|
1338
|
-
return resolved
|
|
1600
|
+
return isWorkspaceFilePath(resolved) ? resolved : void 0;
|
|
1339
1601
|
} catch {
|
|
1340
1602
|
return;
|
|
1341
1603
|
}
|
|
1342
1604
|
}
|
|
1605
|
+
function isWorkspaceFilePath(resolved) {
|
|
1606
|
+
return !!resolved && !resolved.includes("/node_modules/") && !resolved.includes("\\node_modules\\");
|
|
1607
|
+
}
|
|
1343
1608
|
function tryResolveImportFromPackageRoot(pkg, root) {
|
|
1344
1609
|
try {
|
|
1345
1610
|
return (0, module$1.createRequire)(new URL(`file://${pathe.default.join(root, "package.json")}`)).resolve(pkg);
|
|
@@ -1422,7 +1687,9 @@ function writeLoadShareModule(pkg, shareItem, command, isRolldown) {
|
|
|
1422
1687
|
const concreteSharedImportSource = getConcreteSharedImportSource(pkg, shareItem);
|
|
1423
1688
|
const sharedImportSource = concreteSharedImportSource || getPreBuildLibImportId(pkg);
|
|
1424
1689
|
const devImportSource = concreteSharedImportSource || pkg;
|
|
1425
|
-
const
|
|
1690
|
+
const localProviderPath = getLocalProviderImportPath(pkg);
|
|
1691
|
+
const isWorkspacePackage = isWorkspaceFilePath(localProviderPath) || isWorkspaceFilePath(concreteSharedImportSource);
|
|
1692
|
+
const providerImportId = localProviderPath || concreteSharedImportSource || sharedImportSource;
|
|
1426
1693
|
const namedExports = getPackageNamedExports(pkg);
|
|
1427
1694
|
let exportLine;
|
|
1428
1695
|
if (namedExports.length > 0) {
|
|
@@ -1430,9 +1697,11 @@ function writeLoadShareModule(pkg, shareItem, command, isRolldown) {
|
|
|
1430
1697
|
const namedExportLine = `export { ${namedExports.map((name, i) => `__mf_${i} as ${name}`).join(", ")} };`;
|
|
1431
1698
|
exportLine = useESM ? `export default exportModule.default ?? exportModule;\n ${destructure}\n ${namedExportLine}` : `module.exports = exportModule;\n ${destructure}\n Object.assign(module.exports, { ${namedExports.map((name, i) => `"${name}": __mf_${i}`).join(", ")} });`;
|
|
1432
1699
|
} else exportLine = useESM ? `export default exportModule.default ?? exportModule\n export * from ${escapeGeneratedStringLiteral(sharedImportSource)}` : "module.exports = exportModule";
|
|
1700
|
+
const prebuildImportLine = isWorkspacePackage && command !== "build" ? "" : `import ${escapeGeneratedStringLiteral(sharedImportSource)};`;
|
|
1701
|
+
const devDynamicImportLine = isWorkspacePackage ? "" : command !== "build" ? `;() => import(${escapeGeneratedStringLiteral(devImportSource)}).catch(() => {});` : "";
|
|
1433
1702
|
loadShareCacheMap[pkg].writeSync(`
|
|
1434
|
-
|
|
1435
|
-
${
|
|
1703
|
+
${prebuildImportLine}
|
|
1704
|
+
${devDynamicImportLine}
|
|
1436
1705
|
${importLine}
|
|
1437
1706
|
${useSsrProviderFallback ? `const providerModulePromise = typeof window === "undefined"
|
|
1438
1707
|
? import(${escapeGeneratedStringLiteral(providerImportId)})
|
|
@@ -1476,6 +1745,10 @@ function generateLocalSharedImportMap() {
|
|
|
1476
1745
|
const isAstro = hasPackageDependency("astro");
|
|
1477
1746
|
const useDirectReactImport = isVinext || isAstro;
|
|
1478
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
|
+
};
|
|
1479
1752
|
return `
|
|
1480
1753
|
import {loadShare} from "@module-federation/runtime";
|
|
1481
1754
|
const importMap = {
|
|
@@ -1483,8 +1756,7 @@ function generateLocalSharedImportMap() {
|
|
|
1483
1756
|
const shareItem = getNormalizeShareItem(pkg);
|
|
1484
1757
|
return `
|
|
1485
1758
|
${JSON.stringify(pkg)}: async () => {
|
|
1486
|
-
${shareItem?.shareConfig.import === false ? `throw new Error(\`[Module Federation] Shared module '\${${JSON.stringify(pkg)}}' must be provided by host\`);` :
|
|
1487
|
-
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))});
|
|
1488
1760
|
return pkg;`}
|
|
1489
1761
|
}
|
|
1490
1762
|
`;
|
|
@@ -1929,7 +2201,7 @@ const Manifest = () => {
|
|
|
1929
2201
|
root = config.root;
|
|
1930
2202
|
let base = config.base;
|
|
1931
2203
|
if (_command === "serve") base = (config.server.origin || "") + config.base;
|
|
1932
|
-
publicPath = resolvePublicPath(mfOptions, base, _originalConfigBase);
|
|
2204
|
+
publicPath = mfOptions.publicPath === "auto" ? "auto" : resolvePublicPath(mfOptions, base, _originalConfigBase);
|
|
1933
2205
|
},
|
|
1934
2206
|
async generateBundle(options, bundle) {
|
|
1935
2207
|
if (!mfManifestName) return;
|
|
@@ -2991,6 +3263,7 @@ function federation(mfUserOptions) {
|
|
|
2991
3263
|
const remoteEntryId = getRemoteEntryId(options);
|
|
2992
3264
|
const virtualExposesId = getVirtualExposesId(options);
|
|
2993
3265
|
let command;
|
|
3266
|
+
let depsDir = "/node_modules/.vite/deps/";
|
|
2994
3267
|
return [
|
|
2995
3268
|
createEarlyVirtualModulesPlugin(options),
|
|
2996
3269
|
...isVinext ? [{
|
|
@@ -3018,6 +3291,11 @@ function federation(mfUserOptions) {
|
|
|
3018
3291
|
},
|
|
3019
3292
|
configResolved(config) {
|
|
3020
3293
|
VirtualModule.setRoot(config.root);
|
|
3294
|
+
const cacheDir = config.cacheDir;
|
|
3295
|
+
if (cacheDir) {
|
|
3296
|
+
const resolved = pathe.default.isAbsolute(cacheDir) ? cacheDir : pathe.default.resolve(config.root, cacheDir);
|
|
3297
|
+
depsDir = (0, vite.normalizePath)(pathe.default.join(resolved, "deps")) + "/";
|
|
3298
|
+
} else depsDir = (0, vite.normalizePath)(pathe.default.join(config.root, "node_modules", ".vite", "deps")) + "/";
|
|
3021
3299
|
VirtualModule.ensureVirtualPackageExists();
|
|
3022
3300
|
initVirtualModules(command, remoteEntryId);
|
|
3023
3301
|
}
|
|
@@ -3026,6 +3304,7 @@ function federation(mfUserOptions) {
|
|
|
3026
3304
|
checkAliasConflicts({ shared }),
|
|
3027
3305
|
normalizeOptimizeDeps_default,
|
|
3028
3306
|
...pluginDts(options),
|
|
3307
|
+
pluginDevRemoteHmr(options),
|
|
3029
3308
|
...addEntry({
|
|
3030
3309
|
entryName: "remoteEntry",
|
|
3031
3310
|
entryPath: remoteEntryId,
|
|
@@ -3282,7 +3561,7 @@ function federation(mfUserOptions) {
|
|
|
3282
3561
|
apply: "serve",
|
|
3283
3562
|
enforce: "post",
|
|
3284
3563
|
transform(code, id) {
|
|
3285
|
-
if (!id.
|
|
3564
|
+
if (!(0, vite.normalizePath)(id).split("?")[0].startsWith(depsDir)) return;
|
|
3286
3565
|
const initPattern = /\b(init_\w+__loadShare__\w+)\b/g;
|
|
3287
3566
|
const initFns = /* @__PURE__ */ new Set();
|
|
3288
3567
|
let match;
|
package/lib/index.d.cts
CHANGED
package/lib/index.d.mts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { sharePlugin } from "@module-federation/sdk";
|
|
2
1
|
import { Plugin } from "vite";
|
|
2
|
+
import { sharePlugin } from "@module-federation/sdk";
|
|
3
3
|
import { ShareStrategy } from "@module-federation/runtime/types";
|
|
4
4
|
|
|
5
5
|
//#region src/utils/normalizeModuleFederationOptions.d.ts
|
|
@@ -84,6 +84,7 @@ interface PluginDevOptions {
|
|
|
84
84
|
disableLiveReload?: boolean;
|
|
85
85
|
disableHotTypesReload?: boolean;
|
|
86
86
|
disableDynamicRemoteTypeHints?: boolean;
|
|
87
|
+
remoteHmr?: boolean;
|
|
87
88
|
}
|
|
88
89
|
interface RemoteTypeUrl {
|
|
89
90
|
alias?: string;
|
package/lib/index.mjs
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
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";
|
|
8
|
+
import { normalizePath } from "vite";
|
|
8
9
|
import MagicString from "magic-string";
|
|
9
10
|
import { createFilter } from "@rollup/pluginutils";
|
|
10
11
|
import { normalizeOptions } from "@module-federation/sdk";
|
|
@@ -205,17 +206,21 @@ const addEntry = ({ entryName, entryPath, fileName, inject = "entry" }) => {
|
|
|
205
206
|
next();
|
|
206
207
|
});
|
|
207
208
|
},
|
|
208
|
-
transformIndexHtml
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
209
|
+
transformIndexHtml: {
|
|
210
|
+
order: "pre",
|
|
211
|
+
handler(c) {
|
|
212
|
+
if (!injectHtml()) return;
|
|
213
|
+
clientInjected = true;
|
|
214
|
+
const base = viteConfig.base.replace(/\/$/, "");
|
|
215
|
+
const stripBase = (p) => base && p.startsWith(base) ? p.slice(base.length) : p;
|
|
216
|
+
const html = rewriteEntryScripts(c, (originalSrc) => {
|
|
217
|
+
return `/@id/${DEV_HTML_PROXY_PREFIX}${new URLSearchParams({
|
|
218
|
+
init: sanitizeDevEntryPath(stripBase(devEntryPath)),
|
|
219
|
+
entry: originalSrc
|
|
220
|
+
}).toString()}`;
|
|
221
|
+
});
|
|
222
|
+
return html === c ? injectEntryScript(c, stripBase(devEntryPath)) : html;
|
|
223
|
+
}
|
|
219
224
|
},
|
|
220
225
|
resolveId(id) {
|
|
221
226
|
if (id.startsWith(DEV_HTML_PROXY_PREFIX)) return id;
|
|
@@ -434,6 +439,191 @@ function PluginDevProxyModuleTopLevelAwait() {
|
|
|
434
439
|
};
|
|
435
440
|
}
|
|
436
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
|
|
437
627
|
//#region src/plugins/pluginDts.ts
|
|
438
628
|
const DEFAULT_DEV_OPTIONS = {
|
|
439
629
|
disableLiveReload: true,
|
|
@@ -1213,6 +1403,10 @@ function escapeGeneratedStringLiteral(value) {
|
|
|
1213
1403
|
function isValidJsIdentifier(name) {
|
|
1214
1404
|
return /^[$_\p{ID_Start}][$_\u200C\u200D\p{ID_Continue}]*$/u.test(name);
|
|
1215
1405
|
}
|
|
1406
|
+
function isValidEsmExportName(name) {
|
|
1407
|
+
return !!name && name !== "default" && name !== "__esModule" && isValidJsIdentifier(name);
|
|
1408
|
+
}
|
|
1409
|
+
const JS_IDENTIFIER_PATTERN = `[\$_\\p{ID_Start}][\$_\\u200C\\u200D\\p{ID_Continue}]*`;
|
|
1216
1410
|
const localRequire = createRequire$1(import.meta.url);
|
|
1217
1411
|
function resolvePackageEntryFromProjectRoot(pkg) {
|
|
1218
1412
|
try {
|
|
@@ -1290,21 +1484,89 @@ function getPackageEsmEntryPath(pkg) {
|
|
|
1290
1484
|
}
|
|
1291
1485
|
}
|
|
1292
1486
|
function getEsmNamedExports(pkg) {
|
|
1487
|
+
let source = "";
|
|
1488
|
+
let entryPath;
|
|
1293
1489
|
try {
|
|
1294
|
-
|
|
1490
|
+
entryPath = getPackageEsmEntryPath(pkg);
|
|
1295
1491
|
if (!entryPath) return [];
|
|
1296
1492
|
const { initSync, parse } = localRequire("es-module-lexer");
|
|
1297
1493
|
initSync();
|
|
1298
|
-
|
|
1299
|
-
|
|
1494
|
+
source = readFileSync(entryPath, "utf-8");
|
|
1495
|
+
const [, exports] = parse(source, entryPath);
|
|
1496
|
+
const names = exports.map((item) => item.n).filter((name) => isValidEsmExportName(name));
|
|
1497
|
+
const regexNames = getNamedExportsViaRegex(source, entryPath);
|
|
1498
|
+
const filteredNames = names.filter((name) => name !== "type" || regexNames.includes(name));
|
|
1499
|
+
if (filteredNames.length > 0) return [...new Set([...filteredNames, ...regexNames])];
|
|
1500
|
+
return regexNames;
|
|
1300
1501
|
} catch {
|
|
1301
|
-
return [];
|
|
1502
|
+
return source ? getNamedExportsViaRegex(source, entryPath) : [];
|
|
1302
1503
|
}
|
|
1303
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;
|
|
1525
|
+
}
|
|
1526
|
+
}
|
|
1527
|
+
function getNamedExportsViaRegex(source, filePath, visited) {
|
|
1528
|
+
const names = /* @__PURE__ */ new Set();
|
|
1529
|
+
visited = visited || /* @__PURE__ */ new Set();
|
|
1530
|
+
if (filePath) visited.add(filePath);
|
|
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");
|
|
1532
|
+
let match;
|
|
1533
|
+
while ((match = declRegex.exec(source)) !== null) {
|
|
1534
|
+
const name = match[1];
|
|
1535
|
+
if (isValidEsmExportName(name)) names.add(name);
|
|
1536
|
+
}
|
|
1537
|
+
const listRegex = /export\s*\{([^}]+)\}/g;
|
|
1538
|
+
const typeOnlySpecifierRegex = new RegExp(`^type\\s+${JS_IDENTIFIER_PATTERN}(?:\\s+as\\s+${JS_IDENTIFIER_PATTERN})?$`, "u");
|
|
1539
|
+
const exportSpecifierRegex = new RegExp(`(?:\\S+\\s+as\\s+)?(${JS_IDENTIFIER_PATTERN})$`, "u");
|
|
1540
|
+
while ((match = listRegex.exec(source)) !== null) {
|
|
1541
|
+
const specifiers = match[1].split(",");
|
|
1542
|
+
for (const specifier of specifiers) {
|
|
1543
|
+
const trimmed = specifier.trim();
|
|
1544
|
+
if (typeOnlySpecifierRegex.test(trimmed)) continue;
|
|
1545
|
+
const asMatch = trimmed.match(exportSpecifierRegex);
|
|
1546
|
+
if (!asMatch) continue;
|
|
1547
|
+
const name = asMatch[1];
|
|
1548
|
+
if (isValidEsmExportName(name)) names.add(name);
|
|
1549
|
+
}
|
|
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
|
+
}
|
|
1564
|
+
return [...names];
|
|
1565
|
+
}
|
|
1304
1566
|
function getPackageNamedExports(pkg) {
|
|
1305
1567
|
try {
|
|
1306
1568
|
const mod = createRequire$1(new URL(`file://${path.join(getPackageDetectionCwd(), "package.json")}`))(pkg);
|
|
1307
|
-
return Object.keys(mod).filter((k) =>
|
|
1569
|
+
return Object.keys(mod).filter((k) => isValidEsmExportName(k));
|
|
1308
1570
|
} catch {
|
|
1309
1571
|
return getEsmNamedExports(pkg);
|
|
1310
1572
|
}
|
|
@@ -1312,11 +1574,14 @@ function getPackageNamedExports(pkg) {
|
|
|
1312
1574
|
function getLocalProviderImportPath(pkg) {
|
|
1313
1575
|
try {
|
|
1314
1576
|
const resolved = createRequire$1(new URL(`file://${path.join(getPackageDetectionCwd(), "package.json")}`)).resolve(pkg);
|
|
1315
|
-
return resolved
|
|
1577
|
+
return isWorkspaceFilePath(resolved) ? resolved : void 0;
|
|
1316
1578
|
} catch {
|
|
1317
1579
|
return;
|
|
1318
1580
|
}
|
|
1319
1581
|
}
|
|
1582
|
+
function isWorkspaceFilePath(resolved) {
|
|
1583
|
+
return !!resolved && !resolved.includes("/node_modules/") && !resolved.includes("\\node_modules\\");
|
|
1584
|
+
}
|
|
1320
1585
|
function tryResolveImportFromPackageRoot(pkg, root) {
|
|
1321
1586
|
try {
|
|
1322
1587
|
return createRequire$1(new URL(`file://${path.join(root, "package.json")}`)).resolve(pkg);
|
|
@@ -1399,7 +1664,9 @@ function writeLoadShareModule(pkg, shareItem, command, isRolldown) {
|
|
|
1399
1664
|
const concreteSharedImportSource = getConcreteSharedImportSource(pkg, shareItem);
|
|
1400
1665
|
const sharedImportSource = concreteSharedImportSource || getPreBuildLibImportId(pkg);
|
|
1401
1666
|
const devImportSource = concreteSharedImportSource || pkg;
|
|
1402
|
-
const
|
|
1667
|
+
const localProviderPath = getLocalProviderImportPath(pkg);
|
|
1668
|
+
const isWorkspacePackage = isWorkspaceFilePath(localProviderPath) || isWorkspaceFilePath(concreteSharedImportSource);
|
|
1669
|
+
const providerImportId = localProviderPath || concreteSharedImportSource || sharedImportSource;
|
|
1403
1670
|
const namedExports = getPackageNamedExports(pkg);
|
|
1404
1671
|
let exportLine;
|
|
1405
1672
|
if (namedExports.length > 0) {
|
|
@@ -1407,9 +1674,11 @@ function writeLoadShareModule(pkg, shareItem, command, isRolldown) {
|
|
|
1407
1674
|
const namedExportLine = `export { ${namedExports.map((name, i) => `__mf_${i} as ${name}`).join(", ")} };`;
|
|
1408
1675
|
exportLine = useESM ? `export default exportModule.default ?? exportModule;\n ${destructure}\n ${namedExportLine}` : `module.exports = exportModule;\n ${destructure}\n Object.assign(module.exports, { ${namedExports.map((name, i) => `"${name}": __mf_${i}`).join(", ")} });`;
|
|
1409
1676
|
} else exportLine = useESM ? `export default exportModule.default ?? exportModule\n export * from ${escapeGeneratedStringLiteral(sharedImportSource)}` : "module.exports = exportModule";
|
|
1677
|
+
const prebuildImportLine = isWorkspacePackage && command !== "build" ? "" : `import ${escapeGeneratedStringLiteral(sharedImportSource)};`;
|
|
1678
|
+
const devDynamicImportLine = isWorkspacePackage ? "" : command !== "build" ? `;() => import(${escapeGeneratedStringLiteral(devImportSource)}).catch(() => {});` : "";
|
|
1410
1679
|
loadShareCacheMap[pkg].writeSync(`
|
|
1411
|
-
|
|
1412
|
-
${
|
|
1680
|
+
${prebuildImportLine}
|
|
1681
|
+
${devDynamicImportLine}
|
|
1413
1682
|
${importLine}
|
|
1414
1683
|
${useSsrProviderFallback ? `const providerModulePromise = typeof window === "undefined"
|
|
1415
1684
|
? import(${escapeGeneratedStringLiteral(providerImportId)})
|
|
@@ -1453,6 +1722,10 @@ function generateLocalSharedImportMap() {
|
|
|
1453
1722
|
const isAstro = hasPackageDependency("astro");
|
|
1454
1723
|
const useDirectReactImport = isVinext || isAstro;
|
|
1455
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
|
+
};
|
|
1456
1729
|
return `
|
|
1457
1730
|
import {loadShare} from "@module-federation/runtime";
|
|
1458
1731
|
const importMap = {
|
|
@@ -1460,8 +1733,7 @@ function generateLocalSharedImportMap() {
|
|
|
1460
1733
|
const shareItem = getNormalizeShareItem(pkg);
|
|
1461
1734
|
return `
|
|
1462
1735
|
${JSON.stringify(pkg)}: async () => {
|
|
1463
|
-
${shareItem?.shareConfig.import === false ? `throw new Error(\`[Module Federation] Shared module '\${${JSON.stringify(pkg)}}' must be provided by host\`);` :
|
|
1464
|
-
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))});
|
|
1465
1737
|
return pkg;`}
|
|
1466
1738
|
}
|
|
1467
1739
|
`;
|
|
@@ -1906,7 +2178,7 @@ const Manifest = () => {
|
|
|
1906
2178
|
root = config.root;
|
|
1907
2179
|
let base = config.base;
|
|
1908
2180
|
if (_command === "serve") base = (config.server.origin || "") + config.base;
|
|
1909
|
-
publicPath = resolvePublicPath(mfOptions, base, _originalConfigBase);
|
|
2181
|
+
publicPath = mfOptions.publicPath === "auto" ? "auto" : resolvePublicPath(mfOptions, base, _originalConfigBase);
|
|
1910
2182
|
},
|
|
1911
2183
|
async generateBundle(options, bundle) {
|
|
1912
2184
|
if (!mfManifestName) return;
|
|
@@ -2968,6 +3240,7 @@ function federation(mfUserOptions) {
|
|
|
2968
3240
|
const remoteEntryId = getRemoteEntryId(options);
|
|
2969
3241
|
const virtualExposesId = getVirtualExposesId(options);
|
|
2970
3242
|
let command;
|
|
3243
|
+
let depsDir = "/node_modules/.vite/deps/";
|
|
2971
3244
|
return [
|
|
2972
3245
|
createEarlyVirtualModulesPlugin(options),
|
|
2973
3246
|
...isVinext ? [{
|
|
@@ -2995,6 +3268,11 @@ function federation(mfUserOptions) {
|
|
|
2995
3268
|
},
|
|
2996
3269
|
configResolved(config) {
|
|
2997
3270
|
VirtualModule.setRoot(config.root);
|
|
3271
|
+
const cacheDir = config.cacheDir;
|
|
3272
|
+
if (cacheDir) {
|
|
3273
|
+
const resolved = path.isAbsolute(cacheDir) ? cacheDir : path.resolve(config.root, cacheDir);
|
|
3274
|
+
depsDir = normalizePath(path.join(resolved, "deps")) + "/";
|
|
3275
|
+
} else depsDir = normalizePath(path.join(config.root, "node_modules", ".vite", "deps")) + "/";
|
|
2998
3276
|
VirtualModule.ensureVirtualPackageExists();
|
|
2999
3277
|
initVirtualModules(command, remoteEntryId);
|
|
3000
3278
|
}
|
|
@@ -3003,6 +3281,7 @@ function federation(mfUserOptions) {
|
|
|
3003
3281
|
checkAliasConflicts({ shared }),
|
|
3004
3282
|
normalizeOptimizeDeps_default,
|
|
3005
3283
|
...pluginDts(options),
|
|
3284
|
+
pluginDevRemoteHmr(options),
|
|
3006
3285
|
...addEntry({
|
|
3007
3286
|
entryName: "remoteEntry",
|
|
3008
3287
|
entryPath: remoteEntryId,
|
|
@@ -3259,7 +3538,7 @@ function federation(mfUserOptions) {
|
|
|
3259
3538
|
apply: "serve",
|
|
3260
3539
|
enforce: "post",
|
|
3261
3540
|
transform(code, id) {
|
|
3262
|
-
if (!id.
|
|
3541
|
+
if (!normalizePath(id).split("?")[0].startsWith(depsDir)) return;
|
|
3263
3542
|
const initPattern = /\b(init_\w+__loadShare__\w+)\b/g;
|
|
3264
3543
|
const initFns = /* @__PURE__ */ new Set();
|
|
3265
3544
|
let match;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@module-federation/vite",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.14.0",
|
|
4
4
|
"description": "Vite plugin for Module Federation",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./lib/index.cjs",
|
|
@@ -70,9 +70,9 @@
|
|
|
70
70
|
"vite": "^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0"
|
|
71
71
|
},
|
|
72
72
|
"dependencies": {
|
|
73
|
-
"@module-federation/dts-plugin": "2.3.
|
|
74
|
-
"@module-federation/runtime": "2.3.
|
|
75
|
-
"@module-federation/sdk": "2.3.
|
|
73
|
+
"@module-federation/dts-plugin": "2.3.1",
|
|
74
|
+
"@module-federation/runtime": "2.3.1",
|
|
75
|
+
"@module-federation/sdk": "2.3.1",
|
|
76
76
|
"@rollup/pluginutils": "^5.3.0",
|
|
77
77
|
"defu": "^6.1.4",
|
|
78
78
|
"es-module-lexer": "^2.0.0",
|