@module-federation/vite 1.23.0 → 1.23.2
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 +36 -0
- package/lib/{dtsConstants-DEMpRfBz.js → dtsConstants-r5OfegUr.js} +2 -32
- package/lib/index.d.ts +17 -0
- package/lib/index.js +605 -414
- package/lib/logger-DOwfTlw5.js +366 -0
- package/lib/{pluginDts-VSLhAx6d.js → pluginDts-DnEX3cp_.js} +3 -3
- package/lib/{ssrEntryLoader-BvAkpCyi.js → ssrEntryLoader-DEH3eV1e.js} +238 -54
- package/lib/{ssrVmStrategy-CDbcwMnf.js → ssrVmStrategy-COMHOCN2.js} +69 -5
- package/lib/utils/ssrEntryLoader.js +1 -1
- package/package.json +4 -4
- package/lib/buildPaths-BoaQTkxt.js +0 -66
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { a as CodeRewriter, i as createCodePositionMap, r as mfWarn, s as EXTERNAL_URL_RE } from "./logger-DOwfTlw5.js";
|
|
2
2
|
//#region src/utils/fetchWithTimeout.ts
|
|
3
3
|
const DEFAULT_SSR_FETCH_TIMEOUT_MS = 1e4;
|
|
4
4
|
const DEFAULT_SSR_FETCH_MAX_BYTES = 10 * 1024 * 1024;
|
|
@@ -98,6 +98,15 @@ async function readResponseTextBounded(res, maxBytes = DEFAULT_SSR_FETCH_MAX_BYT
|
|
|
98
98
|
return new TextDecoder().decode(merged);
|
|
99
99
|
}
|
|
100
100
|
//#endregion
|
|
101
|
+
//#region src/utils/url.ts
|
|
102
|
+
function getUrlOrigin(value) {
|
|
103
|
+
try {
|
|
104
|
+
return new URL(value).origin;
|
|
105
|
+
} catch {
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
//#endregion
|
|
101
110
|
//#region src/utils/ssrEntryLoader.ts
|
|
102
111
|
/**
|
|
103
112
|
* MF runtime plugin that intercepts the `loadEntry` lifecycle hook on the
|
|
@@ -470,35 +479,55 @@ async function getSSREntry(remoteEntryUrl, maxAgeMs, fetchTimeoutMs, fetchMaxByt
|
|
|
470
479
|
manifestFetchCache.delete(makeUrlCacheKey(getManifestUrl(remoteEntryUrl), fetchTimeoutMs, fetchMaxBytes));
|
|
471
480
|
const record = setSsrEntryCache(remoteEntryUrl, fetchTimeoutMs, fetchMaxBytes);
|
|
472
481
|
const next = await record.promise.catch(() => null);
|
|
473
|
-
if (previous && next && previous.versionKey !== next.versionKey)
|
|
474
|
-
dropRemoteCaches(remoteEntryUrl);
|
|
475
|
-
await clearRunnerCaches(remoteEntryUrl);
|
|
476
|
-
}
|
|
482
|
+
if (previous && next && previous.versionKey !== next.versionKey) await invalidateRemoteCaches(remoteEntryUrl);
|
|
477
483
|
return record.promise;
|
|
478
484
|
}
|
|
479
485
|
/**
|
|
480
|
-
* Drop
|
|
481
|
-
* reused.
|
|
482
|
-
*
|
|
486
|
+
* Drop a remote graph's temp-file caches after a version change so old
|
|
487
|
+
* artifacts stop being reused. A generated module can be shared by multiple
|
|
488
|
+
* remote graphs, so cleanup is scoped by graph owner rather than URL origin.
|
|
483
489
|
*/
|
|
484
|
-
function
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
} catch {
|
|
489
|
-
return;
|
|
490
|
-
}
|
|
491
|
-
for (const [key] of tempFileCache) if (JSON.parse(key).find((part) => typeof part === "string" && /^https?:\/\//.test(part))?.startsWith(origin)) {
|
|
490
|
+
function dropRemoteTempFileCaches(remoteEntryUrl) {
|
|
491
|
+
const staleRecords = [];
|
|
492
|
+
for (const [key, record] of tempFileRecords) {
|
|
493
|
+
if (!record.owners.delete(remoteEntryUrl) || record.owners.size > 0) continue;
|
|
492
494
|
tempFileCache.delete(key);
|
|
493
495
|
tempFilePathCache.delete(key);
|
|
496
|
+
staleRecords.push(record);
|
|
494
497
|
}
|
|
498
|
+
scheduleTempFileCleanup(staleRecords);
|
|
495
499
|
}
|
|
500
|
+
/**
|
|
501
|
+
* Invalidate all SSR loader caches owned by a remote. The runtime loading map
|
|
502
|
+
* is cleared synchronously so `revalidate()` can safely clear module caches
|
|
503
|
+
* immediately after invoking this function.
|
|
504
|
+
*/
|
|
505
|
+
function invalidateRemoteCaches(remoteEntryUrl) {
|
|
506
|
+
invalidateRuntimeRemoteEntry(remoteEntryUrl);
|
|
507
|
+
dropRemoteTempFileCaches(remoteEntryUrl);
|
|
508
|
+
scheduleVmCacheCleanup(remoteEntryUrl);
|
|
509
|
+
return clearRunnerCaches(remoteEntryUrl);
|
|
510
|
+
}
|
|
511
|
+
/**
|
|
512
|
+
* The VM strategy owns separate in-memory module-graph caches. Keep their
|
|
513
|
+
* invalidation aligned with the temp-file and ModuleRunner caches without a
|
|
514
|
+
* static import cycle between the two SSR strategies.
|
|
515
|
+
*/
|
|
516
|
+
function scheduleVmCacheCleanup(remoteEntryUrl) {
|
|
517
|
+
if (!vmStrategyModulePromise) return;
|
|
518
|
+
vmStrategyModulePromise.then(({ clearVmStrategyCaches }) => clearVmStrategyCaches(remoteEntryUrl)).catch(() => {});
|
|
519
|
+
}
|
|
520
|
+
/**
|
|
521
|
+
* A dev-mode ModuleRunner is created per origin (one Vite dev server) and is
|
|
522
|
+
* shared by every remote that server hosts, so its module cache can only be
|
|
523
|
+
* cleared as a whole. Origin scoping is therefore the finest granularity here;
|
|
524
|
+
* clearing only costs a re-evaluation, no generated files are removed.
|
|
525
|
+
*/
|
|
496
526
|
async function clearRunnerCaches(remoteEntryUrl) {
|
|
497
527
|
let remoteOrigin;
|
|
498
|
-
if (remoteEntryUrl)
|
|
499
|
-
remoteOrigin =
|
|
500
|
-
|
|
501
|
-
return;
|
|
528
|
+
if (remoteEntryUrl) {
|
|
529
|
+
remoteOrigin = getUrlOrigin(remoteEntryUrl);
|
|
530
|
+
if (!remoteOrigin) return;
|
|
502
531
|
}
|
|
503
532
|
await Promise.all([...runnerCache.values()].filter((cached) => !remoteOrigin || cached.remoteOrigin === remoteOrigin).map(async (cached) => {
|
|
504
533
|
try {
|
|
@@ -506,6 +535,28 @@ async function clearRunnerCaches(remoteEntryUrl) {
|
|
|
506
535
|
} catch {}
|
|
507
536
|
}));
|
|
508
537
|
}
|
|
538
|
+
/** Invalidate all SSR loader caches across every remote. */
|
|
539
|
+
function invalidateAllRemoteCaches() {
|
|
540
|
+
invalidateRuntimeRemoteEntry();
|
|
541
|
+
const staleRecords = [...tempFileRecords.values()];
|
|
542
|
+
for (const record of staleRecords) record.owners.clear();
|
|
543
|
+
tempFileCache.clear();
|
|
544
|
+
tempFilePathCache.clear();
|
|
545
|
+
scheduleTempFileCleanup(staleRecords);
|
|
546
|
+
scheduleVmCacheCleanup();
|
|
547
|
+
clearRunnerCaches();
|
|
548
|
+
}
|
|
549
|
+
/**
|
|
550
|
+
* runtime-core deduplicates remote entry loads in a process-global map. The
|
|
551
|
+
* SSR loader's module cache invalidation must evict the matching runtime entry
|
|
552
|
+
* too, otherwise the next loadRemote() returns the pre-revalidation container
|
|
553
|
+
* without invoking this loader again.
|
|
554
|
+
*/
|
|
555
|
+
function invalidateRuntimeRemoteEntry(remoteEntryUrl) {
|
|
556
|
+
const globalLoading = globalThis.__GLOBAL_LOADING_REMOTE_ENTRY__;
|
|
557
|
+
if (!globalLoading) return;
|
|
558
|
+
for (const key of Object.keys(globalLoading)) if (remoteEntryUrl === void 0 || key === remoteEntryUrl || key.endsWith(`:${remoteEntryUrl}`)) delete globalLoading[key];
|
|
559
|
+
}
|
|
509
560
|
/**
|
|
510
561
|
* Drop the loader's caches so the next `loadEntry` re-resolves and re-fetches
|
|
511
562
|
* remote SSR entries. Pass a remote entry URL to scope the invalidation to one
|
|
@@ -522,15 +573,13 @@ function revalidate(remoteEntryUrl) {
|
|
|
522
573
|
for (const key of ssrEntryCache.keys()) if (key.endsWith(`::${remoteEntryUrl}`)) ssrEntryCache.delete(key);
|
|
523
574
|
const manifestUrl = getManifestUrl(remoteEntryUrl);
|
|
524
575
|
for (const key of manifestFetchCache.keys()) if (key.endsWith(`::${manifestUrl}`)) manifestFetchCache.delete(key);
|
|
525
|
-
|
|
576
|
+
invalidateRemoteCaches(remoteEntryUrl);
|
|
526
577
|
} else {
|
|
527
578
|
unversionedGlobalGeneration += 1;
|
|
528
579
|
ssrEntryCache.clear();
|
|
529
580
|
manifestFetchCache.clear();
|
|
530
|
-
|
|
531
|
-
tempFilePathCache.clear();
|
|
581
|
+
invalidateAllRemoteCaches();
|
|
532
582
|
}
|
|
533
|
-
clearRunnerCaches(remoteEntryUrl);
|
|
534
583
|
const federation = globalThis.__FEDERATION__;
|
|
535
584
|
for (const instance of federation?.__INSTANCES__ ?? []) try {
|
|
536
585
|
instance?.moduleCache?.clear?.();
|
|
@@ -538,6 +587,74 @@ function revalidate(remoteEntryUrl) {
|
|
|
538
587
|
}
|
|
539
588
|
const tempFileCache = /* @__PURE__ */ new Map();
|
|
540
589
|
const tempFilePathCache = /* @__PURE__ */ new Map();
|
|
590
|
+
const TEMP_FILE_CLEANUP_DELAY_MS = 3e4;
|
|
591
|
+
const tempFileRecords = /* @__PURE__ */ new Map();
|
|
592
|
+
const scheduledTempFileCleanup = /* @__PURE__ */ new Set();
|
|
593
|
+
function scheduleTempFileCleanup(records) {
|
|
594
|
+
for (const record of records) {
|
|
595
|
+
if (scheduledTempFileCleanup.has(record.cacheKey)) continue;
|
|
596
|
+
scheduledTempFileCleanup.add(record.cacheKey);
|
|
597
|
+
const timer = setTimeout(() => {
|
|
598
|
+
cleanupTempFileRecord(record);
|
|
599
|
+
}, TEMP_FILE_CLEANUP_DELAY_MS);
|
|
600
|
+
if (typeof timer === "object" && timer && "unref" in timer) timer.unref?.();
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
async function cleanupTempFileRecord(record) {
|
|
604
|
+
try {
|
|
605
|
+
await record.promise.catch(() => void 0);
|
|
606
|
+
if (record.owners.size > 0) return;
|
|
607
|
+
if (tempFileRecords.get(record.cacheKey) !== record) return;
|
|
608
|
+
const filePath = await record.filePathPromise;
|
|
609
|
+
const { rmSync } = await _fs();
|
|
610
|
+
rmSync(filePath, { force: true });
|
|
611
|
+
} catch {} finally {
|
|
612
|
+
if (tempFileRecords.get(record.cacheKey) === record) tempFileRecords.delete(record.cacheKey);
|
|
613
|
+
scheduledTempFileCleanup.delete(record.cacheKey);
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
const requireShimCache = /* @__PURE__ */ new Map();
|
|
617
|
+
/**
|
|
618
|
+
* `resolvedShared` rewrites import/export specifiers only. Bundled CommonJS in a
|
|
619
|
+
* remote requires shared packages through `createRequire(import.meta.url)` —
|
|
620
|
+
* Rolldown's `__require("react")` — and Node resolves those from the temp dir,
|
|
621
|
+
* which can reach another copy than the pinned one. Remote modules importing
|
|
622
|
+
* `node:module` get this shim instead: its `createRequire` resolves pinned
|
|
623
|
+
* specifiers to the same files the rewritten imports load.
|
|
624
|
+
*/
|
|
625
|
+
function createRequireShimSource(sharedPkgMap) {
|
|
626
|
+
return [
|
|
627
|
+
"import * as nodeModule from 'node:module';",
|
|
628
|
+
"export * from 'node:module';",
|
|
629
|
+
"export default nodeModule.default;",
|
|
630
|
+
`const pinned = new Map(${JSON.stringify([...sharedPkgMap])});`,
|
|
631
|
+
"const pin = (id) => (typeof id === 'string' && pinned.get(id)) || id;",
|
|
632
|
+
"export function createRequire(filename) {",
|
|
633
|
+
" const require = nodeModule.createRequire(filename);",
|
|
634
|
+
" const resolve = (id, options) => require.resolve(pin(id), options);",
|
|
635
|
+
" resolve.paths = require.resolve.paths;",
|
|
636
|
+
" return Object.assign((id) => require(pin(id)), require, { resolve });",
|
|
637
|
+
"}",
|
|
638
|
+
""
|
|
639
|
+
].join("\n");
|
|
640
|
+
}
|
|
641
|
+
function getRequireShimUrl(tmpDir, sharedPkgMap, contextKey) {
|
|
642
|
+
const cacheKey = JSON.stringify([tmpDir, contextKey]);
|
|
643
|
+
const cached = requireShimCache.get(cacheKey);
|
|
644
|
+
if (cached) return cached;
|
|
645
|
+
const promise = (async () => {
|
|
646
|
+
const { join } = await _path();
|
|
647
|
+
const { writeFileSync } = await _fs();
|
|
648
|
+
const file = join(tmpDir, `node-module-${hashString(contextKey)}.mjs`);
|
|
649
|
+
writeFileSync(file, createRequireShimSource(sharedPkgMap), "utf8");
|
|
650
|
+
return `file://${file}`;
|
|
651
|
+
})();
|
|
652
|
+
requireShimCache.set(cacheKey, promise);
|
|
653
|
+
promise.catch(() => {
|
|
654
|
+
if (requireShimCache.get(cacheKey) === promise) requireShimCache.delete(cacheKey);
|
|
655
|
+
});
|
|
656
|
+
return promise;
|
|
657
|
+
}
|
|
541
658
|
function getSsrTransformContextKey(resolvedShared, shareScopeName) {
|
|
542
659
|
return JSON.stringify([shareScopeName, getSortedRecordEntries(resolvedShared)]);
|
|
543
660
|
}
|
|
@@ -574,15 +691,57 @@ function neutralizeBrowserPreloadHelpers(code) {
|
|
|
574
691
|
code = code.replace(/\b([A-Za-z_$][\w$]*)\s*\(\s*\(\s*\)\s*=>\s*import\(([^)]*)\)\s*,\s*\[\]\s*\)/g, "import($2)");
|
|
575
692
|
return code;
|
|
576
693
|
}
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
694
|
+
const SSR_MODULE_SPECIFIER_RE = /(?:from|export\s*\*\s*from|import\s*\(?\s*)\s*(["'`])([^"'`\s]+)\1/g;
|
|
695
|
+
function isRelativeSpecifier(specifier) {
|
|
696
|
+
return specifier.startsWith("./") || specifier.startsWith("../");
|
|
697
|
+
}
|
|
698
|
+
/**
|
|
699
|
+
* Module specifiers used by the import/export syntax of `code`. Import-like
|
|
700
|
+
* text inside comments and string literals (JSDoc such as
|
|
701
|
+
* `@type {import('./x')}`, docs, error messages) is skipped, and a comment
|
|
702
|
+
* between `import(` and the specifier (webpack-style magic comments) does not
|
|
703
|
+
* hide a real dynamic import. Discovery and rewriting both read this list, so
|
|
704
|
+
* they always agree on which specifiers exist.
|
|
705
|
+
*/
|
|
706
|
+
function findSsrModuleSpecifiers(code) {
|
|
707
|
+
let blanked = "";
|
|
708
|
+
let blankedUntil = 0;
|
|
709
|
+
const isCode = createCodePositionMap(code, (start, end) => {
|
|
710
|
+
blanked += code.slice(blankedUntil, start) + " ".repeat(end - start);
|
|
711
|
+
blankedUntil = end;
|
|
584
712
|
});
|
|
585
|
-
|
|
713
|
+
blanked += code.slice(blankedUntil);
|
|
714
|
+
const specifiers = [];
|
|
715
|
+
for (const match of blanked.matchAll(SSR_MODULE_SPECIFIER_RE)) {
|
|
716
|
+
if (!isCode[match.index]) continue;
|
|
717
|
+
const end = match.index + match[0].length;
|
|
718
|
+
const value = match[2];
|
|
719
|
+
specifiers.push({
|
|
720
|
+
start: end - value.length - 2,
|
|
721
|
+
end,
|
|
722
|
+
value
|
|
723
|
+
});
|
|
724
|
+
}
|
|
725
|
+
return specifiers;
|
|
726
|
+
}
|
|
727
|
+
function isNodeModuleSpecifier(specifier) {
|
|
728
|
+
return specifier === "node:module" || specifier === "module";
|
|
729
|
+
}
|
|
730
|
+
function transformSsrCode(code, base, specifiers, sharedPkgMap, requireShimUrl, tempFileUrlMap) {
|
|
731
|
+
const rewriter = new CodeRewriter(code);
|
|
732
|
+
for (const { start, end, value } of specifiers) {
|
|
733
|
+
if (requireShimUrl && isNodeModuleSpecifier(value)) {
|
|
734
|
+
rewriter.overwrite(start, end, `"${requireShimUrl}"`);
|
|
735
|
+
continue;
|
|
736
|
+
}
|
|
737
|
+
const resolvedShared = sharedPkgMap?.get(value);
|
|
738
|
+
const relativeUrl = isRelativeSpecifier(value) ? new URL(value, base).href : void 0;
|
|
739
|
+
const absoluteHttpUrl = value.startsWith("http://") || value.startsWith("https://") ? value : void 0;
|
|
740
|
+
const httpUrl = relativeUrl ?? absoluteHttpUrl;
|
|
741
|
+
const replacement = (httpUrl ? tempFileUrlMap?.get(httpUrl) : void 0) ?? relativeUrl ?? (resolvedShared && `file://${resolvedShared}`);
|
|
742
|
+
if (replacement) rewriter.overwrite(start, end, `"${replacement}"`);
|
|
743
|
+
}
|
|
744
|
+
return neutralizeBrowserPreloadHelpers(rewriter.toString());
|
|
586
745
|
}
|
|
587
746
|
function isVitePreloadHelperSpecifier(specifier) {
|
|
588
747
|
return specifier.includes("preload-helper");
|
|
@@ -591,25 +750,27 @@ function getTempFileImportUrl(filePath, versionKey) {
|
|
|
591
750
|
return `file://${filePath}?v=${encodeURIComponent(versionKey)}`;
|
|
592
751
|
}
|
|
593
752
|
/**
|
|
594
|
-
* Fetch an HTTP ESM module, transform it, write it to a temp
|
|
753
|
+
* Fetch an HTTP ESM module, transform it, write it to a temp file and
|
|
595
754
|
* return the file path. Recursively does the same for HTTP transitive imports
|
|
596
|
-
* so that `import('file:///...temp.
|
|
755
|
+
* so that `import('file:///...temp.mjs')` can resolve them.
|
|
597
756
|
*
|
|
598
757
|
* `versionKey` participates in both the cache key and the temp file name, so
|
|
599
758
|
* a remote redeploy (new manifest → new key) produces new files and bypasses
|
|
600
759
|
* Node's ESM module cache instead of serving the stale build.
|
|
601
760
|
*/
|
|
602
|
-
async function fetchEsmToTempFile(url, tmpDir, visited, pending, sharedPkgMap, versionKey = UNVERSIONED, fetchTimeoutMs = DEFAULT_SSR_FETCH_TIMEOUT_MS, contextKey = "default", fetchMaxBytes = DEFAULT_SSR_FETCH_MAX_BYTES) {
|
|
761
|
+
async function fetchEsmToTempFile(url, tmpDir, visited, pending, sharedPkgMap, versionKey = UNVERSIONED, fetchTimeoutMs = DEFAULT_SSR_FETCH_TIMEOUT_MS, contextKey = "default", fetchMaxBytes = DEFAULT_SSR_FETCH_MAX_BYTES, extension = ".mjs", owner) {
|
|
603
762
|
const cacheKey = JSON.stringify([
|
|
604
763
|
fetchTimeoutMs,
|
|
605
764
|
fetchMaxBytes,
|
|
606
765
|
versionKey,
|
|
607
766
|
url,
|
|
608
|
-
contextKey
|
|
767
|
+
contextKey,
|
|
768
|
+
extension
|
|
609
769
|
]);
|
|
610
770
|
if (visited.has(url)) return visited.get(url);
|
|
611
771
|
const cached = tempFileCache.get(cacheKey);
|
|
612
772
|
if (cached) {
|
|
773
|
+
if (owner) tempFileRecords.get(cacheKey)?.owners.add(owner);
|
|
613
774
|
pending.add(cached);
|
|
614
775
|
const reserved = tempFilePathCache.get(cacheKey);
|
|
615
776
|
const tmpFile = reserved ? await reserved : await cached;
|
|
@@ -619,7 +780,7 @@ async function fetchEsmToTempFile(url, tmpDir, visited, pending, sharedPkgMap, v
|
|
|
619
780
|
const tmpFilePromise = (async () => {
|
|
620
781
|
const { createHash } = await _crypto();
|
|
621
782
|
const { join } = await _path();
|
|
622
|
-
return join(tmpDir, `${createHash("sha1").update(cacheKey).digest("hex").slice(0, 12)}
|
|
783
|
+
return join(tmpDir, `${createHash("sha1").update(cacheKey).digest("hex").slice(0, 12)}${extension}`);
|
|
623
784
|
})();
|
|
624
785
|
tempFilePathCache.set(cacheKey, tmpFilePromise);
|
|
625
786
|
const promise = (async () => {
|
|
@@ -629,32 +790,38 @@ async function fetchEsmToTempFile(url, tmpDir, visited, pending, sharedPkgMap, v
|
|
|
629
790
|
let code = await readResponseTextBounded(res, fetchMaxBytes, url);
|
|
630
791
|
if (!res.ok) throw new SsrEntryHttpError(url, res.status, res.statusText, getBodyPreview(code));
|
|
631
792
|
const base = url.replace(/\/[^/]*$/, "/");
|
|
632
|
-
const
|
|
633
|
-
const
|
|
634
|
-
let m;
|
|
635
|
-
while ((m = relRegex.exec(code)) !== null) if ((m[1].startsWith("./") || m[1].startsWith("../")) && !isVitePreloadHelperSpecifier(m[1])) relImports.push(new URL(m[1], base).href);
|
|
793
|
+
const specifiers = findSsrModuleSpecifiers(code);
|
|
794
|
+
const relImports = specifiers.map(({ value }) => value).filter((value) => isRelativeSpecifier(value) && !isVitePreloadHelperSpecifier(value)).map((value) => new URL(value, base).href);
|
|
636
795
|
const subMap = /* @__PURE__ */ new Map();
|
|
637
796
|
await Promise.all([...new Set(relImports)].filter((u) => u.startsWith("http://") || u.startsWith("https://")).map(async (u) => {
|
|
638
|
-
const tmpPath = await fetchEsmToTempFile(u, tmpDir, visited, pending, sharedPkgMap, versionKey, fetchTimeoutMs, contextKey, fetchMaxBytes);
|
|
797
|
+
const tmpPath = await fetchEsmToTempFile(u, tmpDir, visited, pending, sharedPkgMap, versionKey, fetchTimeoutMs, contextKey, fetchMaxBytes, extension, owner);
|
|
639
798
|
subMap.set(u, getTempFileImportUrl(tmpPath, versionKey));
|
|
640
799
|
}));
|
|
641
|
-
|
|
642
|
-
|
|
800
|
+
const requireShimUrl = sharedPkgMap?.size && specifiers.some(({ value }) => isNodeModuleSpecifier(value)) ? await getRequireShimUrl(tmpDir, sharedPkgMap, contextKey) : void 0;
|
|
801
|
+
code = transformSsrCode(code, base, specifiers, sharedPkgMap, requireShimUrl, subMap);
|
|
643
802
|
const { writeFileSync } = await _fs();
|
|
644
803
|
writeFileSync(tmpFile, code, "utf8");
|
|
645
804
|
return tmpFile;
|
|
646
805
|
})();
|
|
806
|
+
const record = {
|
|
807
|
+
cacheKey,
|
|
808
|
+
filePathPromise: tmpFilePromise,
|
|
809
|
+
promise,
|
|
810
|
+
owners: owner ? /* @__PURE__ */ new Set([owner]) : /* @__PURE__ */ new Set()
|
|
811
|
+
};
|
|
812
|
+
tempFileRecords.set(cacheKey, record);
|
|
647
813
|
tempFileCache.set(cacheKey, promise);
|
|
648
814
|
pending.add(promise);
|
|
649
815
|
promise.catch(() => {
|
|
650
816
|
if (tempFileCache.get(cacheKey) === promise) tempFileCache.delete(cacheKey);
|
|
651
817
|
if (tempFilePathCache.get(cacheKey) === tmpFilePromise) tempFilePathCache.delete(cacheKey);
|
|
818
|
+
if (tempFileRecords.get(cacheKey) === record && !scheduledTempFileCleanup.has(cacheKey)) tempFileRecords.delete(cacheKey);
|
|
652
819
|
});
|
|
653
820
|
return promise;
|
|
654
821
|
}
|
|
655
|
-
async function fetchEsmGraphToTempFile(url, tmpDir, sharedPkgMap, versionKey = UNVERSIONED, fetchTimeoutMs = DEFAULT_SSR_FETCH_TIMEOUT_MS, contextKey = "default", fetchMaxBytes = DEFAULT_SSR_FETCH_MAX_BYTES) {
|
|
822
|
+
async function fetchEsmGraphToTempFile(url, tmpDir, sharedPkgMap, versionKey = UNVERSIONED, fetchTimeoutMs = DEFAULT_SSR_FETCH_TIMEOUT_MS, contextKey = "default", fetchMaxBytes = DEFAULT_SSR_FETCH_MAX_BYTES, extension = ".mjs", owner) {
|
|
656
823
|
const pending = /* @__PURE__ */ new Set();
|
|
657
|
-
const rootFile = await fetchEsmToTempFile(url, tmpDir, /* @__PURE__ */ new Map(), pending, sharedPkgMap, versionKey, fetchTimeoutMs, contextKey, fetchMaxBytes);
|
|
824
|
+
const rootFile = await fetchEsmToTempFile(url, tmpDir, /* @__PURE__ */ new Map(), pending, sharedPkgMap, versionKey, fetchTimeoutMs, contextKey, fetchMaxBytes, extension, owner);
|
|
658
825
|
await Promise.all(pending);
|
|
659
826
|
return rootFile;
|
|
660
827
|
}
|
|
@@ -665,12 +832,25 @@ async function importTempModule(filePath, versionKey) {
|
|
|
665
832
|
);
|
|
666
833
|
}
|
|
667
834
|
let warnedVmUnavailable = false;
|
|
668
|
-
|
|
669
|
-
|
|
835
|
+
let vmStrategyModulePromise;
|
|
836
|
+
function getVmStrategyModule() {
|
|
837
|
+
if (!vmStrategyModulePromise) vmStrategyModulePromise = import("./ssrVmStrategy-COMHOCN2.js");
|
|
838
|
+
return vmStrategyModulePromise;
|
|
839
|
+
}
|
|
840
|
+
/**
|
|
841
|
+
* Non-HTTP failures keep falling back (to temp-file after vm, to the federation
|
|
842
|
+
* runtime's own Node loader after temp-file), but whatever the fallback throws
|
|
843
|
+
* then hides the real cause, so report it first.
|
|
844
|
+
*/
|
|
845
|
+
function warnStrategyFallback(strategy, url, fallback, error) {
|
|
846
|
+
mfWarn(`SSR entry loader: strategy "${strategy}" failed to load ${url}; falling back to ${fallback}.`, error);
|
|
847
|
+
}
|
|
848
|
+
async function tryVmStrategy(ssrEntry, options, rootEntryUrl) {
|
|
849
|
+
const { loadViaVmStrategy, isVmStrategyAvailable } = await getVmStrategyModule();
|
|
670
850
|
if (!await isVmStrategyAvailable()) {
|
|
671
851
|
if (!warnedVmUnavailable) {
|
|
672
852
|
warnedVmUnavailable = true;
|
|
673
|
-
|
|
853
|
+
mfWarn("SSR entry loader: strategy \"vm\" requires vm.SourceTextModule (run Node with --experimental-vm-modules); falling back to the temp-file strategy.");
|
|
674
854
|
}
|
|
675
855
|
return null;
|
|
676
856
|
}
|
|
@@ -681,10 +861,11 @@ async function tryVmStrategy(ssrEntry, options) {
|
|
|
681
861
|
fetchTimeoutMs: options.fetchTimeoutMs,
|
|
682
862
|
fetchMaxBytes: options.fetchMaxBytes,
|
|
683
863
|
cacheContext: options.cacheContext,
|
|
684
|
-
federationInstance: options.federationInstance
|
|
864
|
+
federationInstance: options.federationInstance,
|
|
865
|
+
rootEntryUrl
|
|
685
866
|
});
|
|
686
867
|
}
|
|
687
|
-
async function loadSSRRemoteEntry(ssrEntry, options) {
|
|
868
|
+
async function loadSSRRemoteEntry(ssrEntry, options, rootEntryUrl) {
|
|
688
869
|
const { url, type, versionKey } = ssrEntry;
|
|
689
870
|
const { resolvedShared } = options;
|
|
690
871
|
if (type === "commonjs-module" || type === "commonjs") {
|
|
@@ -711,19 +892,22 @@ async function loadSSRRemoteEntry(ssrEntry, options) {
|
|
|
711
892
|
}
|
|
712
893
|
}
|
|
713
894
|
if (options.strategy === "vm") try {
|
|
714
|
-
const fromVm = await tryVmStrategy(ssrEntry, options);
|
|
895
|
+
const fromVm = await tryVmStrategy(ssrEntry, options, rootEntryUrl);
|
|
715
896
|
if (fromVm) return fromVm;
|
|
716
897
|
} catch (error) {
|
|
717
898
|
if (isSsrEntryHttpError(error) || isSsrFetchBodyTooLargeError(error)) throw error;
|
|
899
|
+
warnStrategyFallback("vm", url, "the temp-file strategy", error);
|
|
718
900
|
}
|
|
719
901
|
const { mkdirSync } = await _fs();
|
|
720
902
|
const cacheDir = await getSSRCacheDir();
|
|
721
903
|
mkdirSync(cacheDir, { recursive: true });
|
|
722
904
|
const sharedPkgMap = new Map(Object.entries(resolvedShared));
|
|
905
|
+
const extension = type === "commonjs-module" || type === "commonjs" ? ".js" : ".mjs";
|
|
723
906
|
try {
|
|
724
|
-
return await importTempModule(await fetchEsmGraphToTempFile(url, cacheDir, sharedPkgMap, versionKey, options.fetchTimeoutMs, getSsrTransformContextKey(resolvedShared, options.shareScopeName), options.fetchMaxBytes), versionKey);
|
|
907
|
+
return await importTempModule(await fetchEsmGraphToTempFile(url, cacheDir, sharedPkgMap, versionKey, options.fetchTimeoutMs, getSsrTransformContextKey(resolvedShared, options.shareScopeName), options.fetchMaxBytes, extension, rootEntryUrl), versionKey);
|
|
725
908
|
} catch (error) {
|
|
726
909
|
if (isSsrEntryHttpError(error) || isSsrFetchBodyTooLargeError(error)) throw error;
|
|
910
|
+
warnStrategyFallback("temp-file", url, "the federation runtime's loader", error);
|
|
727
911
|
return null;
|
|
728
912
|
}
|
|
729
913
|
}
|
|
@@ -757,7 +941,7 @@ function ssrEntryLoaderPlugin(options = {}) {
|
|
|
757
941
|
} : resolved;
|
|
758
942
|
const ssrEntry = await getSSREntry(remoteInfo.entry, loadOptions.maxAgeMs, loadOptions.fetchTimeoutMs, loadOptions.fetchMaxBytes);
|
|
759
943
|
if (!ssrEntry) return;
|
|
760
|
-
const mod = await loadSSRRemoteEntry(ssrEntry, loadOptions);
|
|
944
|
+
const mod = await loadSSRRemoteEntry(ssrEntry, loadOptions, remoteInfo.entry);
|
|
761
945
|
if (!mod) return;
|
|
762
946
|
return mod;
|
|
763
947
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { t as findSharedKey } from "./sharedKeyMatcher-Od_nYxJA.js";
|
|
2
|
-
import { c as readResponseTextBounded, n as neutralizeBrowserPreloadHelpers, s as fetchWithTimeout, t as SsrEntryHttpError } from "./ssrEntryLoader-
|
|
2
|
+
import { c as readResponseTextBounded, n as neutralizeBrowserPreloadHelpers, s as fetchWithTimeout, t as SsrEntryHttpError } from "./ssrEntryLoader-DEH3eV1e.js";
|
|
3
3
|
//#region src/utils/ssrVmStrategy.ts
|
|
4
4
|
/**
|
|
5
5
|
* vm.SourceTextModule strategy for loading remote SSR entries.
|
|
@@ -73,10 +73,38 @@ async function loadBareModule(specifier, options) {
|
|
|
73
73
|
/* @vite-ignore */
|
|
74
74
|
`file://${resolvedPath}`
|
|
75
75
|
);
|
|
76
|
-
|
|
76
|
+
const namespace = await import(
|
|
77
77
|
/* @vite-ignore */
|
|
78
78
|
specifier
|
|
79
79
|
);
|
|
80
|
+
return specifier === "module" || specifier === "node:module" ? withRemoteCreateRequire(namespace, options.resolvedShared) : namespace;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Remote modules keep their http(s) URL as `import.meta.url`, which Node's
|
|
84
|
+
* `createRequire` rejects. Rolldown's runtime for Node-targeted output calls
|
|
85
|
+
* `createRequire(import.meta.url)` as soon as a bundled CommonJS module
|
|
86
|
+
* requires an external, so hand remote modules a `createRequire` that resolves
|
|
87
|
+
* those URLs from the app root — the base the temp-file strategy's temp files
|
|
88
|
+
* resolve from. Its `require` also pins `resolvedShared` specifiers to the
|
|
89
|
+
* same files the linker loads for the matching imports, so a bundled
|
|
90
|
+
* `__require("react")` cannot reach a second copy of a shared package.
|
|
91
|
+
*/
|
|
92
|
+
async function withRemoteCreateRequire(nodeModule, resolvedShared) {
|
|
93
|
+
const { pathToFileURL } = await import(
|
|
94
|
+
/* @vite-ignore */
|
|
95
|
+
"url"
|
|
96
|
+
);
|
|
97
|
+
const appRoot = pathToFileURL(`${process.cwd()}/`);
|
|
98
|
+
const pin = (id) => resolvedShared[id] ?? id;
|
|
99
|
+
return {
|
|
100
|
+
...nodeModule,
|
|
101
|
+
createRequire: (filename) => {
|
|
102
|
+
const require = nodeModule.createRequire(isHttpUrl(String(filename)) ? appRoot : filename);
|
|
103
|
+
const resolve = ((id, options) => require.resolve(pin(id), options));
|
|
104
|
+
resolve.paths = require.resolve.paths;
|
|
105
|
+
return Object.assign((id) => require(pin(id)), require, { resolve });
|
|
106
|
+
}
|
|
107
|
+
};
|
|
80
108
|
}
|
|
81
109
|
function createSyntheticModule(vm, specifier, namespace) {
|
|
82
110
|
const source = namespace && typeof namespace === "object" ? namespace : { default: namespace };
|
|
@@ -90,6 +118,8 @@ function createSyntheticModule(vm, specifier, namespace) {
|
|
|
90
118
|
}
|
|
91
119
|
const httpModuleCache = /* @__PURE__ */ new Map();
|
|
92
120
|
const namespaceCache = /* @__PURE__ */ new Map();
|
|
121
|
+
const httpModuleCacheOwners = /* @__PURE__ */ new Map();
|
|
122
|
+
const namespaceCacheOwners = /* @__PURE__ */ new Map();
|
|
93
123
|
const linkQueues = /* @__PURE__ */ new WeakMap();
|
|
94
124
|
const contextIds = /* @__PURE__ */ new WeakMap();
|
|
95
125
|
let nextContextId = 1;
|
|
@@ -108,6 +138,31 @@ function getVmCacheContextKey(options) {
|
|
|
108
138
|
Object.entries(options.resolvedShared).sort(([left], [right]) => left.localeCompare(right))
|
|
109
139
|
]);
|
|
110
140
|
}
|
|
141
|
+
function addVmCacheOwner(ownersByCacheKey, cacheKey, rootEntryUrl) {
|
|
142
|
+
if (!rootEntryUrl) return;
|
|
143
|
+
const owners = ownersByCacheKey.get(cacheKey) ?? /* @__PURE__ */ new Set();
|
|
144
|
+
owners.add(rootEntryUrl);
|
|
145
|
+
ownersByCacheKey.set(cacheKey, owners);
|
|
146
|
+
}
|
|
147
|
+
function clearOwnedVmCache(cache, ownersByCacheKey, remoteEntryUrl) {
|
|
148
|
+
for (const [cacheKey, owners] of ownersByCacheKey) {
|
|
149
|
+
if (remoteEntryUrl !== void 0 && !owners.delete(remoteEntryUrl)) continue;
|
|
150
|
+
if (remoteEntryUrl !== void 0 && owners.size > 0) continue;
|
|
151
|
+
cache.delete(cacheKey);
|
|
152
|
+
ownersByCacheKey.delete(cacheKey);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Drop cached VM module graphs after a remote revalidation. A module graph can
|
|
157
|
+
* contain URLs from another origin, and one origin can serve several remotes,
|
|
158
|
+
* so ownership is tracked by the owning remote entry URL rather than inferred
|
|
159
|
+
* from each module URL. A module shared by several graphs stays cached while
|
|
160
|
+
* another remote still owns it.
|
|
161
|
+
*/
|
|
162
|
+
function clearVmStrategyCaches(remoteEntryUrl) {
|
|
163
|
+
clearOwnedVmCache(httpModuleCache, httpModuleCacheOwners, remoteEntryUrl);
|
|
164
|
+
clearOwnedVmCache(namespaceCache, namespaceCacheOwners, remoteEntryUrl);
|
|
165
|
+
}
|
|
111
166
|
function getBodyPreview(body) {
|
|
112
167
|
return body.slice(0, 240).replace(/\s+/g, " ").trim();
|
|
113
168
|
}
|
|
@@ -134,6 +189,7 @@ function getHttpModule(vm, url, options) {
|
|
|
134
189
|
options.versionKey,
|
|
135
190
|
url
|
|
136
191
|
]);
|
|
192
|
+
addVmCacheOwner(httpModuleCacheOwners, cacheKey, options.rootEntryUrl);
|
|
137
193
|
if (!httpModuleCache.has(cacheKey)) httpModuleCache.set(cacheKey, (async () => {
|
|
138
194
|
const code = await fetchModuleSource(url, options.fetchTimeoutMs, options.fetchMaxBytes);
|
|
139
195
|
return new vm.SourceTextModule(code, {
|
|
@@ -145,6 +201,7 @@ function getHttpModule(vm, url, options) {
|
|
|
145
201
|
});
|
|
146
202
|
})().catch((error) => {
|
|
147
203
|
httpModuleCache.delete(cacheKey);
|
|
204
|
+
httpModuleCacheOwners.delete(cacheKey);
|
|
148
205
|
throw error;
|
|
149
206
|
}));
|
|
150
207
|
return httpModuleCache.get(cacheKey);
|
|
@@ -180,18 +237,25 @@ async function importDynamically(vm, specifier, referencingModule, options) {
|
|
|
180
237
|
async function loadViaVmStrategy(entryUrl, options) {
|
|
181
238
|
const vm = await getVmApi();
|
|
182
239
|
if (!vm) return null;
|
|
240
|
+
const rootEntryUrl = options.rootEntryUrl ?? entryUrl;
|
|
241
|
+
const graphOptions = {
|
|
242
|
+
...options,
|
|
243
|
+
rootEntryUrl
|
|
244
|
+
};
|
|
183
245
|
const cacheKey = `${getVmCacheContextKey(options)}::${options.versionKey}::${entryUrl}`;
|
|
246
|
+
addVmCacheOwner(namespaceCacheOwners, cacheKey, rootEntryUrl);
|
|
184
247
|
if (!namespaceCache.has(cacheKey)) namespaceCache.set(cacheKey, (async () => {
|
|
185
|
-
const entryModule = await getHttpModule(vm, entryUrl,
|
|
186
|
-
const linker = (specifier, referencingModule) => linkModule(vm, specifier, referencingModule,
|
|
248
|
+
const entryModule = await getHttpModule(vm, entryUrl, graphOptions);
|
|
249
|
+
const linker = (specifier, referencingModule) => linkModule(vm, specifier, referencingModule, graphOptions);
|
|
187
250
|
await linkModuleGraph(entryModule, linker, options.cacheContext);
|
|
188
251
|
if (entryModule.status === "linked") await entryModule.evaluate();
|
|
189
252
|
return entryModule.namespace;
|
|
190
253
|
})().catch((error) => {
|
|
191
254
|
namespaceCache.delete(cacheKey);
|
|
255
|
+
namespaceCacheOwners.delete(cacheKey);
|
|
192
256
|
throw error;
|
|
193
257
|
}));
|
|
194
258
|
return namespaceCache.get(cacheKey);
|
|
195
259
|
}
|
|
196
260
|
//#endregion
|
|
197
|
-
export { isVmStrategyAvailable, loadViaVmStrategy };
|
|
261
|
+
export { clearVmStrategyCaches, findVmSharedKey, isVmStrategyAvailable, loadViaVmStrategy };
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { i as ssrEntryLoaderPlugin, n as neutralizeBrowserPreloadHelpers, r as revalidate, t as SsrEntryHttpError } from "../ssrEntryLoader-
|
|
1
|
+
import { i as ssrEntryLoaderPlugin, n as neutralizeBrowserPreloadHelpers, r as revalidate, t as SsrEntryHttpError } from "../ssrEntryLoader-DEH3eV1e.js";
|
|
2
2
|
export { SsrEntryHttpError, ssrEntryLoaderPlugin as default, neutralizeBrowserPreloadHelpers, revalidate };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@module-federation/vite",
|
|
3
|
-
"version": "1.23.
|
|
3
|
+
"version": "1.23.2",
|
|
4
4
|
"description": "Vite plugin for Module Federation",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./lib/index.js",
|
|
@@ -77,9 +77,9 @@
|
|
|
77
77
|
"vite": "^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0"
|
|
78
78
|
},
|
|
79
79
|
"dependencies": {
|
|
80
|
-
"@module-federation/dts-plugin": "2.9.
|
|
81
|
-
"@module-federation/runtime": "2.9.
|
|
82
|
-
"@module-federation/sdk": "2.9.
|
|
80
|
+
"@module-federation/dts-plugin": "2.9.2",
|
|
81
|
+
"@module-federation/runtime": "2.9.2",
|
|
82
|
+
"@module-federation/sdk": "2.9.2"
|
|
83
83
|
},
|
|
84
84
|
"devDependencies": {
|
|
85
85
|
"@playwright/test": "1.62.0",
|