@module-federation/vite 1.23.0 → 1.23.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/{dtsConstants-DEMpRfBz.js → dtsConstants-Bs4FukGL.js} +2 -32
- package/lib/index.js +66 -295
- package/lib/logger--uKHOVjR.js +366 -0
- package/lib/{pluginDts-VSLhAx6d.js → pluginDts-DxgS2FEd.js} +2 -2
- package/lib/{ssrEntryLoader-BvAkpCyi.js → ssrEntryLoader-DmqLXUmm.js} +206 -49
- package/lib/{ssrVmStrategy-CDbcwMnf.js → ssrVmStrategy-B_e3yqa2.js} +74 -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--uKHOVjR.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,44 @@ 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 per-remote caches after a version change so old artifacts stop
|
|
481
|
-
* reused. Temp-file cache keys hold SSR entry/chunk URLs (not the
|
|
482
|
-
* entry URL), so scope the invalidation by origin.
|
|
486
|
+
* Drop per-remote temp-file caches after a version change so old artifacts stop
|
|
487
|
+
* being reused. Temp-file cache keys hold SSR entry/chunk URLs (not the
|
|
488
|
+
* browser entry URL), so scope the invalidation by origin.
|
|
483
489
|
*/
|
|
484
|
-
function
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
} catch {
|
|
489
|
-
return;
|
|
490
|
-
}
|
|
490
|
+
function dropRemoteTempFileCaches(remoteEntryUrl) {
|
|
491
|
+
const origin = getUrlOrigin(remoteEntryUrl);
|
|
492
|
+
if (!origin) return;
|
|
493
|
+
const staleRecords = [...tempFileRecords.values()].filter((record) => record.remoteOrigin === origin);
|
|
491
494
|
for (const [key] of tempFileCache) if (JSON.parse(key).find((part) => typeof part === "string" && /^https?:\/\//.test(part))?.startsWith(origin)) {
|
|
492
495
|
tempFileCache.delete(key);
|
|
493
496
|
tempFilePathCache.delete(key);
|
|
494
497
|
}
|
|
498
|
+
scheduleTempFileCleanup(staleRecords);
|
|
499
|
+
}
|
|
500
|
+
/** Invalidate all SSR loader caches owned by a remote. */
|
|
501
|
+
async function invalidateRemoteCaches(remoteEntryUrl) {
|
|
502
|
+
dropRemoteTempFileCaches(remoteEntryUrl);
|
|
503
|
+
scheduleVmCacheCleanup(remoteEntryUrl);
|
|
504
|
+
await clearRunnerCaches(remoteEntryUrl);
|
|
505
|
+
}
|
|
506
|
+
/**
|
|
507
|
+
* The VM strategy owns separate in-memory module-graph caches. Keep their
|
|
508
|
+
* invalidation aligned with the temp-file and ModuleRunner caches without a
|
|
509
|
+
* static import cycle between the two SSR strategies.
|
|
510
|
+
*/
|
|
511
|
+
function scheduleVmCacheCleanup(remoteEntryUrl) {
|
|
512
|
+
if (!vmStrategyModulePromise) return;
|
|
513
|
+
vmStrategyModulePromise.then(({ clearVmStrategyCaches }) => clearVmStrategyCaches(remoteEntryUrl)).catch(() => {});
|
|
495
514
|
}
|
|
496
515
|
async function clearRunnerCaches(remoteEntryUrl) {
|
|
497
516
|
let remoteOrigin;
|
|
498
|
-
if (remoteEntryUrl)
|
|
499
|
-
remoteOrigin =
|
|
500
|
-
|
|
501
|
-
return;
|
|
517
|
+
if (remoteEntryUrl) {
|
|
518
|
+
remoteOrigin = getUrlOrigin(remoteEntryUrl);
|
|
519
|
+
if (!remoteOrigin) return;
|
|
502
520
|
}
|
|
503
521
|
await Promise.all([...runnerCache.values()].filter((cached) => !remoteOrigin || cached.remoteOrigin === remoteOrigin).map(async (cached) => {
|
|
504
522
|
try {
|
|
@@ -506,6 +524,15 @@ async function clearRunnerCaches(remoteEntryUrl) {
|
|
|
506
524
|
} catch {}
|
|
507
525
|
}));
|
|
508
526
|
}
|
|
527
|
+
/** Invalidate all SSR loader caches across every remote. */
|
|
528
|
+
function invalidateAllRemoteCaches() {
|
|
529
|
+
const staleRecords = [...tempFileRecords.values()];
|
|
530
|
+
tempFileCache.clear();
|
|
531
|
+
tempFilePathCache.clear();
|
|
532
|
+
scheduleTempFileCleanup(staleRecords);
|
|
533
|
+
scheduleVmCacheCleanup();
|
|
534
|
+
clearRunnerCaches();
|
|
535
|
+
}
|
|
509
536
|
/**
|
|
510
537
|
* Drop the loader's caches so the next `loadEntry` re-resolves and re-fetches
|
|
511
538
|
* remote SSR entries. Pass a remote entry URL to scope the invalidation to one
|
|
@@ -522,15 +549,13 @@ function revalidate(remoteEntryUrl) {
|
|
|
522
549
|
for (const key of ssrEntryCache.keys()) if (key.endsWith(`::${remoteEntryUrl}`)) ssrEntryCache.delete(key);
|
|
523
550
|
const manifestUrl = getManifestUrl(remoteEntryUrl);
|
|
524
551
|
for (const key of manifestFetchCache.keys()) if (key.endsWith(`::${manifestUrl}`)) manifestFetchCache.delete(key);
|
|
525
|
-
|
|
552
|
+
invalidateRemoteCaches(remoteEntryUrl);
|
|
526
553
|
} else {
|
|
527
554
|
unversionedGlobalGeneration += 1;
|
|
528
555
|
ssrEntryCache.clear();
|
|
529
556
|
manifestFetchCache.clear();
|
|
530
|
-
|
|
531
|
-
tempFilePathCache.clear();
|
|
557
|
+
invalidateAllRemoteCaches();
|
|
532
558
|
}
|
|
533
|
-
clearRunnerCaches(remoteEntryUrl);
|
|
534
559
|
const federation = globalThis.__FEDERATION__;
|
|
535
560
|
for (const instance of federation?.__INSTANCES__ ?? []) try {
|
|
536
561
|
instance?.moduleCache?.clear?.();
|
|
@@ -538,6 +563,73 @@ function revalidate(remoteEntryUrl) {
|
|
|
538
563
|
}
|
|
539
564
|
const tempFileCache = /* @__PURE__ */ new Map();
|
|
540
565
|
const tempFilePathCache = /* @__PURE__ */ new Map();
|
|
566
|
+
const TEMP_FILE_CLEANUP_DELAY_MS = 3e4;
|
|
567
|
+
const tempFileRecords = /* @__PURE__ */ new Map();
|
|
568
|
+
const scheduledTempFileCleanup = /* @__PURE__ */ new Set();
|
|
569
|
+
function scheduleTempFileCleanup(records) {
|
|
570
|
+
for (const record of records) {
|
|
571
|
+
if (scheduledTempFileCleanup.has(record.cacheKey)) continue;
|
|
572
|
+
scheduledTempFileCleanup.add(record.cacheKey);
|
|
573
|
+
const timer = setTimeout(() => {
|
|
574
|
+
cleanupTempFileRecord(record);
|
|
575
|
+
}, TEMP_FILE_CLEANUP_DELAY_MS);
|
|
576
|
+
if (typeof timer === "object" && timer && "unref" in timer) timer.unref?.();
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
async function cleanupTempFileRecord(record) {
|
|
580
|
+
try {
|
|
581
|
+
await record.promise.catch(() => void 0);
|
|
582
|
+
if (tempFileRecords.get(record.cacheKey) !== record) return;
|
|
583
|
+
const filePath = await record.filePathPromise;
|
|
584
|
+
const { rmSync } = await _fs();
|
|
585
|
+
rmSync(filePath, { force: true });
|
|
586
|
+
} catch {} finally {
|
|
587
|
+
if (tempFileRecords.get(record.cacheKey) === record) tempFileRecords.delete(record.cacheKey);
|
|
588
|
+
scheduledTempFileCleanup.delete(record.cacheKey);
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
const requireShimCache = /* @__PURE__ */ new Map();
|
|
592
|
+
/**
|
|
593
|
+
* `resolvedShared` rewrites import/export specifiers only. Bundled CommonJS in a
|
|
594
|
+
* remote requires shared packages through `createRequire(import.meta.url)` —
|
|
595
|
+
* Rolldown's `__require("react")` — and Node resolves those from the temp dir,
|
|
596
|
+
* which can reach another copy than the pinned one. Remote modules importing
|
|
597
|
+
* `node:module` get this shim instead: its `createRequire` resolves pinned
|
|
598
|
+
* specifiers to the same files the rewritten imports load.
|
|
599
|
+
*/
|
|
600
|
+
function createRequireShimSource(sharedPkgMap) {
|
|
601
|
+
return [
|
|
602
|
+
"import * as nodeModule from 'node:module';",
|
|
603
|
+
"export * from 'node:module';",
|
|
604
|
+
"export default nodeModule.default;",
|
|
605
|
+
`const pinned = new Map(${JSON.stringify([...sharedPkgMap])});`,
|
|
606
|
+
"const pin = (id) => (typeof id === 'string' && pinned.get(id)) || id;",
|
|
607
|
+
"export function createRequire(filename) {",
|
|
608
|
+
" const require = nodeModule.createRequire(filename);",
|
|
609
|
+
" const resolve = (id, options) => require.resolve(pin(id), options);",
|
|
610
|
+
" resolve.paths = require.resolve.paths;",
|
|
611
|
+
" return Object.assign((id) => require(pin(id)), require, { resolve });",
|
|
612
|
+
"}",
|
|
613
|
+
""
|
|
614
|
+
].join("\n");
|
|
615
|
+
}
|
|
616
|
+
function getRequireShimUrl(tmpDir, sharedPkgMap, contextKey) {
|
|
617
|
+
const cacheKey = JSON.stringify([tmpDir, contextKey]);
|
|
618
|
+
const cached = requireShimCache.get(cacheKey);
|
|
619
|
+
if (cached) return cached;
|
|
620
|
+
const promise = (async () => {
|
|
621
|
+
const { join } = await _path();
|
|
622
|
+
const { writeFileSync } = await _fs();
|
|
623
|
+
const file = join(tmpDir, `node-module-${hashString(contextKey)}.mjs`);
|
|
624
|
+
writeFileSync(file, createRequireShimSource(sharedPkgMap), "utf8");
|
|
625
|
+
return `file://${file}`;
|
|
626
|
+
})();
|
|
627
|
+
requireShimCache.set(cacheKey, promise);
|
|
628
|
+
promise.catch(() => {
|
|
629
|
+
if (requireShimCache.get(cacheKey) === promise) requireShimCache.delete(cacheKey);
|
|
630
|
+
});
|
|
631
|
+
return promise;
|
|
632
|
+
}
|
|
541
633
|
function getSsrTransformContextKey(resolvedShared, shareScopeName) {
|
|
542
634
|
return JSON.stringify([shareScopeName, getSortedRecordEntries(resolvedShared)]);
|
|
543
635
|
}
|
|
@@ -574,15 +666,57 @@ function neutralizeBrowserPreloadHelpers(code) {
|
|
|
574
666
|
code = code.replace(/\b([A-Za-z_$][\w$]*)\s*\(\s*\(\s*\)\s*=>\s*import\(([^)]*)\)\s*,\s*\[\]\s*\)/g, "import($2)");
|
|
575
667
|
return code;
|
|
576
668
|
}
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
669
|
+
const SSR_MODULE_SPECIFIER_RE = /(?:from|export\s*\*\s*from|import\s*\(?\s*)\s*(["'`])([^"'`\s]+)\1/g;
|
|
670
|
+
function isRelativeSpecifier(specifier) {
|
|
671
|
+
return specifier.startsWith("./") || specifier.startsWith("../");
|
|
672
|
+
}
|
|
673
|
+
/**
|
|
674
|
+
* Module specifiers used by the import/export syntax of `code`. Import-like
|
|
675
|
+
* text inside comments and string literals (JSDoc such as
|
|
676
|
+
* `@type {import('./x')}`, docs, error messages) is skipped, and a comment
|
|
677
|
+
* between `import(` and the specifier (webpack-style magic comments) does not
|
|
678
|
+
* hide a real dynamic import. Discovery and rewriting both read this list, so
|
|
679
|
+
* they always agree on which specifiers exist.
|
|
680
|
+
*/
|
|
681
|
+
function findSsrModuleSpecifiers(code) {
|
|
682
|
+
let blanked = "";
|
|
683
|
+
let blankedUntil = 0;
|
|
684
|
+
const isCode = createCodePositionMap(code, (start, end) => {
|
|
685
|
+
blanked += code.slice(blankedUntil, start) + " ".repeat(end - start);
|
|
686
|
+
blankedUntil = end;
|
|
584
687
|
});
|
|
585
|
-
|
|
688
|
+
blanked += code.slice(blankedUntil);
|
|
689
|
+
const specifiers = [];
|
|
690
|
+
for (const match of blanked.matchAll(SSR_MODULE_SPECIFIER_RE)) {
|
|
691
|
+
if (!isCode[match.index]) continue;
|
|
692
|
+
const end = match.index + match[0].length;
|
|
693
|
+
const value = match[2];
|
|
694
|
+
specifiers.push({
|
|
695
|
+
start: end - value.length - 2,
|
|
696
|
+
end,
|
|
697
|
+
value
|
|
698
|
+
});
|
|
699
|
+
}
|
|
700
|
+
return specifiers;
|
|
701
|
+
}
|
|
702
|
+
function isNodeModuleSpecifier(specifier) {
|
|
703
|
+
return specifier === "node:module" || specifier === "module";
|
|
704
|
+
}
|
|
705
|
+
function transformSsrCode(code, base, specifiers, sharedPkgMap, requireShimUrl, tempFileUrlMap) {
|
|
706
|
+
const rewriter = new CodeRewriter(code);
|
|
707
|
+
for (const { start, end, value } of specifiers) {
|
|
708
|
+
if (requireShimUrl && isNodeModuleSpecifier(value)) {
|
|
709
|
+
rewriter.overwrite(start, end, `"${requireShimUrl}"`);
|
|
710
|
+
continue;
|
|
711
|
+
}
|
|
712
|
+
const resolvedShared = sharedPkgMap?.get(value);
|
|
713
|
+
const relativeUrl = isRelativeSpecifier(value) ? new URL(value, base).href : void 0;
|
|
714
|
+
const absoluteHttpUrl = value.startsWith("http://") || value.startsWith("https://") ? value : void 0;
|
|
715
|
+
const httpUrl = relativeUrl ?? absoluteHttpUrl;
|
|
716
|
+
const replacement = (httpUrl ? tempFileUrlMap?.get(httpUrl) : void 0) ?? relativeUrl ?? (resolvedShared && `file://${resolvedShared}`);
|
|
717
|
+
if (replacement) rewriter.overwrite(start, end, `"${replacement}"`);
|
|
718
|
+
}
|
|
719
|
+
return neutralizeBrowserPreloadHelpers(rewriter.toString());
|
|
586
720
|
}
|
|
587
721
|
function isVitePreloadHelperSpecifier(specifier) {
|
|
588
722
|
return specifier.includes("preload-helper");
|
|
@@ -591,21 +725,22 @@ function getTempFileImportUrl(filePath, versionKey) {
|
|
|
591
725
|
return `file://${filePath}?v=${encodeURIComponent(versionKey)}`;
|
|
592
726
|
}
|
|
593
727
|
/**
|
|
594
|
-
* Fetch an HTTP ESM module, transform it, write it to a temp
|
|
728
|
+
* Fetch an HTTP ESM module, transform it, write it to a temp file and
|
|
595
729
|
* return the file path. Recursively does the same for HTTP transitive imports
|
|
596
|
-
* so that `import('file:///...temp.
|
|
730
|
+
* so that `import('file:///...temp.mjs')` can resolve them.
|
|
597
731
|
*
|
|
598
732
|
* `versionKey` participates in both the cache key and the temp file name, so
|
|
599
733
|
* a remote redeploy (new manifest → new key) produces new files and bypasses
|
|
600
734
|
* Node's ESM module cache instead of serving the stale build.
|
|
601
735
|
*/
|
|
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) {
|
|
736
|
+
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") {
|
|
603
737
|
const cacheKey = JSON.stringify([
|
|
604
738
|
fetchTimeoutMs,
|
|
605
739
|
fetchMaxBytes,
|
|
606
740
|
versionKey,
|
|
607
741
|
url,
|
|
608
|
-
contextKey
|
|
742
|
+
contextKey,
|
|
743
|
+
extension
|
|
609
744
|
]);
|
|
610
745
|
if (visited.has(url)) return visited.get(url);
|
|
611
746
|
const cached = tempFileCache.get(cacheKey);
|
|
@@ -619,7 +754,7 @@ async function fetchEsmToTempFile(url, tmpDir, visited, pending, sharedPkgMap, v
|
|
|
619
754
|
const tmpFilePromise = (async () => {
|
|
620
755
|
const { createHash } = await _crypto();
|
|
621
756
|
const { join } = await _path();
|
|
622
|
-
return join(tmpDir, `${createHash("sha1").update(cacheKey).digest("hex").slice(0, 12)}
|
|
757
|
+
return join(tmpDir, `${createHash("sha1").update(cacheKey).digest("hex").slice(0, 12)}${extension}`);
|
|
623
758
|
})();
|
|
624
759
|
tempFilePathCache.set(cacheKey, tmpFilePromise);
|
|
625
760
|
const promise = (async () => {
|
|
@@ -629,32 +764,38 @@ async function fetchEsmToTempFile(url, tmpDir, visited, pending, sharedPkgMap, v
|
|
|
629
764
|
let code = await readResponseTextBounded(res, fetchMaxBytes, url);
|
|
630
765
|
if (!res.ok) throw new SsrEntryHttpError(url, res.status, res.statusText, getBodyPreview(code));
|
|
631
766
|
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);
|
|
767
|
+
const specifiers = findSsrModuleSpecifiers(code);
|
|
768
|
+
const relImports = specifiers.map(({ value }) => value).filter((value) => isRelativeSpecifier(value) && !isVitePreloadHelperSpecifier(value)).map((value) => new URL(value, base).href);
|
|
636
769
|
const subMap = /* @__PURE__ */ new Map();
|
|
637
770
|
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);
|
|
771
|
+
const tmpPath = await fetchEsmToTempFile(u, tmpDir, visited, pending, sharedPkgMap, versionKey, fetchTimeoutMs, contextKey, fetchMaxBytes, extension);
|
|
639
772
|
subMap.set(u, getTempFileImportUrl(tmpPath, versionKey));
|
|
640
773
|
}));
|
|
641
|
-
|
|
642
|
-
|
|
774
|
+
const requireShimUrl = sharedPkgMap?.size && specifiers.some(({ value }) => isNodeModuleSpecifier(value)) ? await getRequireShimUrl(tmpDir, sharedPkgMap, contextKey) : void 0;
|
|
775
|
+
code = transformSsrCode(code, base, specifiers, sharedPkgMap, requireShimUrl, subMap);
|
|
643
776
|
const { writeFileSync } = await _fs();
|
|
644
777
|
writeFileSync(tmpFile, code, "utf8");
|
|
645
778
|
return tmpFile;
|
|
646
779
|
})();
|
|
780
|
+
const record = {
|
|
781
|
+
cacheKey,
|
|
782
|
+
filePathPromise: tmpFilePromise,
|
|
783
|
+
promise,
|
|
784
|
+
remoteOrigin: getUrlOrigin(url)
|
|
785
|
+
};
|
|
786
|
+
tempFileRecords.set(cacheKey, record);
|
|
647
787
|
tempFileCache.set(cacheKey, promise);
|
|
648
788
|
pending.add(promise);
|
|
649
789
|
promise.catch(() => {
|
|
650
790
|
if (tempFileCache.get(cacheKey) === promise) tempFileCache.delete(cacheKey);
|
|
651
791
|
if (tempFilePathCache.get(cacheKey) === tmpFilePromise) tempFilePathCache.delete(cacheKey);
|
|
792
|
+
if (tempFileRecords.get(cacheKey) === record && !scheduledTempFileCleanup.has(cacheKey)) tempFileRecords.delete(cacheKey);
|
|
652
793
|
});
|
|
653
794
|
return promise;
|
|
654
795
|
}
|
|
655
|
-
async function fetchEsmGraphToTempFile(url, tmpDir, sharedPkgMap, versionKey = UNVERSIONED, fetchTimeoutMs = DEFAULT_SSR_FETCH_TIMEOUT_MS, contextKey = "default", fetchMaxBytes = DEFAULT_SSR_FETCH_MAX_BYTES) {
|
|
796
|
+
async function fetchEsmGraphToTempFile(url, tmpDir, sharedPkgMap, versionKey = UNVERSIONED, fetchTimeoutMs = DEFAULT_SSR_FETCH_TIMEOUT_MS, contextKey = "default", fetchMaxBytes = DEFAULT_SSR_FETCH_MAX_BYTES, extension = ".mjs") {
|
|
656
797
|
const pending = /* @__PURE__ */ new Set();
|
|
657
|
-
const rootFile = await fetchEsmToTempFile(url, tmpDir, /* @__PURE__ */ new Map(), pending, sharedPkgMap, versionKey, fetchTimeoutMs, contextKey, fetchMaxBytes);
|
|
798
|
+
const rootFile = await fetchEsmToTempFile(url, tmpDir, /* @__PURE__ */ new Map(), pending, sharedPkgMap, versionKey, fetchTimeoutMs, contextKey, fetchMaxBytes, extension);
|
|
658
799
|
await Promise.all(pending);
|
|
659
800
|
return rootFile;
|
|
660
801
|
}
|
|
@@ -665,12 +806,25 @@ async function importTempModule(filePath, versionKey) {
|
|
|
665
806
|
);
|
|
666
807
|
}
|
|
667
808
|
let warnedVmUnavailable = false;
|
|
809
|
+
let vmStrategyModulePromise;
|
|
810
|
+
function getVmStrategyModule() {
|
|
811
|
+
if (!vmStrategyModulePromise) vmStrategyModulePromise = import("./ssrVmStrategy-B_e3yqa2.js");
|
|
812
|
+
return vmStrategyModulePromise;
|
|
813
|
+
}
|
|
814
|
+
/**
|
|
815
|
+
* Non-HTTP failures keep falling back (to temp-file after vm, to the federation
|
|
816
|
+
* runtime's own Node loader after temp-file), but whatever the fallback throws
|
|
817
|
+
* then hides the real cause, so report it first.
|
|
818
|
+
*/
|
|
819
|
+
function warnStrategyFallback(strategy, url, fallback, error) {
|
|
820
|
+
mfWarn(`SSR entry loader: strategy "${strategy}" failed to load ${url}; falling back to ${fallback}.`, error);
|
|
821
|
+
}
|
|
668
822
|
async function tryVmStrategy(ssrEntry, options) {
|
|
669
|
-
const { loadViaVmStrategy, isVmStrategyAvailable } = await
|
|
823
|
+
const { loadViaVmStrategy, isVmStrategyAvailable } = await getVmStrategyModule();
|
|
670
824
|
if (!await isVmStrategyAvailable()) {
|
|
671
825
|
if (!warnedVmUnavailable) {
|
|
672
826
|
warnedVmUnavailable = true;
|
|
673
|
-
|
|
827
|
+
mfWarn("SSR entry loader: strategy \"vm\" requires vm.SourceTextModule (run Node with --experimental-vm-modules); falling back to the temp-file strategy.");
|
|
674
828
|
}
|
|
675
829
|
return null;
|
|
676
830
|
}
|
|
@@ -715,15 +869,18 @@ async function loadSSRRemoteEntry(ssrEntry, options) {
|
|
|
715
869
|
if (fromVm) return fromVm;
|
|
716
870
|
} catch (error) {
|
|
717
871
|
if (isSsrEntryHttpError(error) || isSsrFetchBodyTooLargeError(error)) throw error;
|
|
872
|
+
warnStrategyFallback("vm", url, "the temp-file strategy", error);
|
|
718
873
|
}
|
|
719
874
|
const { mkdirSync } = await _fs();
|
|
720
875
|
const cacheDir = await getSSRCacheDir();
|
|
721
876
|
mkdirSync(cacheDir, { recursive: true });
|
|
722
877
|
const sharedPkgMap = new Map(Object.entries(resolvedShared));
|
|
878
|
+
const extension = type === "commonjs-module" || type === "commonjs" ? ".js" : ".mjs";
|
|
723
879
|
try {
|
|
724
|
-
return await importTempModule(await fetchEsmGraphToTempFile(url, cacheDir, sharedPkgMap, versionKey, options.fetchTimeoutMs, getSsrTransformContextKey(resolvedShared, options.shareScopeName), options.fetchMaxBytes), versionKey);
|
|
880
|
+
return await importTempModule(await fetchEsmGraphToTempFile(url, cacheDir, sharedPkgMap, versionKey, options.fetchTimeoutMs, getSsrTransformContextKey(resolvedShared, options.shareScopeName), options.fetchMaxBytes, extension), versionKey);
|
|
725
881
|
} catch (error) {
|
|
726
882
|
if (isSsrEntryHttpError(error) || isSsrFetchBodyTooLargeError(error)) throw error;
|
|
883
|
+
warnStrategyFallback("temp-file", url, "the federation runtime's loader", error);
|
|
727
884
|
return null;
|
|
728
885
|
}
|
|
729
886
|
}
|
|
@@ -764,4 +921,4 @@ function ssrEntryLoaderPlugin(options = {}) {
|
|
|
764
921
|
};
|
|
765
922
|
}
|
|
766
923
|
//#endregion
|
|
767
|
-
export {
|
|
924
|
+
export { getUrlOrigin as a, fetchWithTimeout as c, ssrEntryLoaderPlugin as i, readResponseTextBounded as l, neutralizeBrowserPreloadHelpers as n, DEFAULT_SSR_FETCH_MAX_BYTES as o, revalidate as r, DEFAULT_SSR_FETCH_TIMEOUT_MS as s, SsrEntryHttpError as t };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { t as findSharedKey } from "./sharedKeyMatcher-Od_nYxJA.js";
|
|
2
|
-
import { c as
|
|
2
|
+
import { a as getUrlOrigin, c as fetchWithTimeout, l as readResponseTextBounded, n as neutralizeBrowserPreloadHelpers, t as SsrEntryHttpError } from "./ssrEntryLoader-DmqLXUmm.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,37 @@ 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, remoteOrigin) {
|
|
148
|
+
for (const [cacheKey, owners] of ownersByCacheKey) {
|
|
149
|
+
if (!remoteOrigin) {
|
|
150
|
+
cache.delete(cacheKey);
|
|
151
|
+
ownersByCacheKey.delete(cacheKey);
|
|
152
|
+
continue;
|
|
153
|
+
}
|
|
154
|
+
for (const owner of owners) if (getUrlOrigin(owner) === remoteOrigin) owners.delete(owner);
|
|
155
|
+
if (owners.size === 0) {
|
|
156
|
+
cache.delete(cacheKey);
|
|
157
|
+
ownersByCacheKey.delete(cacheKey);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* Drop cached VM module graphs after a remote revalidation. A module graph can
|
|
163
|
+
* contain URLs from another origin, so ownership is tracked by the root entry
|
|
164
|
+
* rather than inferred from each module URL.
|
|
165
|
+
*/
|
|
166
|
+
function clearVmStrategyCaches(remoteEntryUrl) {
|
|
167
|
+
const remoteOrigin = remoteEntryUrl === void 0 ? void 0 : getUrlOrigin(remoteEntryUrl);
|
|
168
|
+
if (remoteEntryUrl !== void 0 && !remoteOrigin) return;
|
|
169
|
+
clearOwnedVmCache(httpModuleCache, httpModuleCacheOwners, remoteOrigin);
|
|
170
|
+
clearOwnedVmCache(namespaceCache, namespaceCacheOwners, remoteOrigin);
|
|
171
|
+
}
|
|
111
172
|
function getBodyPreview(body) {
|
|
112
173
|
return body.slice(0, 240).replace(/\s+/g, " ").trim();
|
|
113
174
|
}
|
|
@@ -134,6 +195,7 @@ function getHttpModule(vm, url, options) {
|
|
|
134
195
|
options.versionKey,
|
|
135
196
|
url
|
|
136
197
|
]);
|
|
198
|
+
addVmCacheOwner(httpModuleCacheOwners, cacheKey, options.rootEntryUrl);
|
|
137
199
|
if (!httpModuleCache.has(cacheKey)) httpModuleCache.set(cacheKey, (async () => {
|
|
138
200
|
const code = await fetchModuleSource(url, options.fetchTimeoutMs, options.fetchMaxBytes);
|
|
139
201
|
return new vm.SourceTextModule(code, {
|
|
@@ -145,6 +207,7 @@ function getHttpModule(vm, url, options) {
|
|
|
145
207
|
});
|
|
146
208
|
})().catch((error) => {
|
|
147
209
|
httpModuleCache.delete(cacheKey);
|
|
210
|
+
httpModuleCacheOwners.delete(cacheKey);
|
|
148
211
|
throw error;
|
|
149
212
|
}));
|
|
150
213
|
return httpModuleCache.get(cacheKey);
|
|
@@ -180,18 +243,24 @@ async function importDynamically(vm, specifier, referencingModule, options) {
|
|
|
180
243
|
async function loadViaVmStrategy(entryUrl, options) {
|
|
181
244
|
const vm = await getVmApi();
|
|
182
245
|
if (!vm) return null;
|
|
246
|
+
const graphOptions = {
|
|
247
|
+
...options,
|
|
248
|
+
rootEntryUrl: entryUrl
|
|
249
|
+
};
|
|
183
250
|
const cacheKey = `${getVmCacheContextKey(options)}::${options.versionKey}::${entryUrl}`;
|
|
251
|
+
addVmCacheOwner(namespaceCacheOwners, cacheKey, entryUrl);
|
|
184
252
|
if (!namespaceCache.has(cacheKey)) namespaceCache.set(cacheKey, (async () => {
|
|
185
|
-
const entryModule = await getHttpModule(vm, entryUrl,
|
|
186
|
-
const linker = (specifier, referencingModule) => linkModule(vm, specifier, referencingModule,
|
|
253
|
+
const entryModule = await getHttpModule(vm, entryUrl, graphOptions);
|
|
254
|
+
const linker = (specifier, referencingModule) => linkModule(vm, specifier, referencingModule, graphOptions);
|
|
187
255
|
await linkModuleGraph(entryModule, linker, options.cacheContext);
|
|
188
256
|
if (entryModule.status === "linked") await entryModule.evaluate();
|
|
189
257
|
return entryModule.namespace;
|
|
190
258
|
})().catch((error) => {
|
|
191
259
|
namespaceCache.delete(cacheKey);
|
|
260
|
+
namespaceCacheOwners.delete(cacheKey);
|
|
192
261
|
throw error;
|
|
193
262
|
}));
|
|
194
263
|
return namespaceCache.get(cacheKey);
|
|
195
264
|
}
|
|
196
265
|
//#endregion
|
|
197
|
-
export { isVmStrategyAvailable, loadViaVmStrategy };
|
|
266
|
+
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-DmqLXUmm.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.1",
|
|
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",
|
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
//#region src/utils/buildPaths.ts
|
|
2
|
-
/**
|
|
3
|
-
* Rebase an import path for a bootstrap file that moved from root into `dir`.
|
|
4
|
-
*
|
|
5
|
-
* When entryFileNames places entries in a subdirectory (e.g. `static/js/`),
|
|
6
|
-
* the bootstrap file moves there too. Paths that resolved from the HTML root
|
|
7
|
-
* must resolve from the new directory instead.
|
|
8
|
-
*
|
|
9
|
-
* Cases: `/static/js/hostInit.js` → `./hostInit.js` (strip dir prefix)
|
|
10
|
-
* `./src/main.tsx` → `../../src/main.tsx` (climb back up for each dir level)
|
|
11
|
-
* `https://cdn.example.com` → unchanged (absolute URL)
|
|
12
|
-
*/
|
|
13
|
-
function rebaseImport(importSrc, dir) {
|
|
14
|
-
if (!dir) return importSrc;
|
|
15
|
-
if (isAbsoluteUrl(importSrc)) return importSrc;
|
|
16
|
-
const normalizedDir = dir.replace(/^\/+|\/+$/g, "");
|
|
17
|
-
if (!normalizedDir) return importSrc;
|
|
18
|
-
const stripDirPrefix = (src, prefix) => {
|
|
19
|
-
if (src === prefix) return "";
|
|
20
|
-
if (src.startsWith(prefix + "/")) return src.slice(prefix.length);
|
|
21
|
-
};
|
|
22
|
-
const absoluteRemainder = stripDirPrefix(importSrc, "/" + normalizedDir);
|
|
23
|
-
if (absoluteRemainder !== void 0) {
|
|
24
|
-
const remainder = absoluteRemainder.replace(/^\/+/, "");
|
|
25
|
-
return remainder ? "./" + remainder : "./";
|
|
26
|
-
}
|
|
27
|
-
const relativeRemainder = stripDirPrefix(importSrc, normalizedDir);
|
|
28
|
-
if (relativeRemainder !== void 0) {
|
|
29
|
-
const remainder = relativeRemainder.replace(/^\/+/, "");
|
|
30
|
-
return remainder ? "./" + remainder : "./";
|
|
31
|
-
}
|
|
32
|
-
const upLevels = normalizedDir.split("/").filter(Boolean).length;
|
|
33
|
-
const prefix = upLevels > 0 ? "../".repeat(upLevels) : "./";
|
|
34
|
-
if (importSrc.startsWith("./")) return prefix + importSrc.slice(2);
|
|
35
|
-
if (importSrc.startsWith("/")) return prefix + importSrc.slice(1);
|
|
36
|
-
return prefix + importSrc;
|
|
37
|
-
}
|
|
38
|
-
function normalizePathForImport(path) {
|
|
39
|
-
return path.replace(/\\/g, "/");
|
|
40
|
-
}
|
|
41
|
-
const EXTERNAL_URL_RE = /^(?:[a-z]+:|\/\/)/i;
|
|
42
|
-
function isAbsoluteUrl(src) {
|
|
43
|
-
if (/^[a-z]:[\\/]/i.test(src)) return false;
|
|
44
|
-
return EXTERNAL_URL_RE.test(src);
|
|
45
|
-
}
|
|
46
|
-
const HASH_PLACEHOLDER_RE = /(?:[._-]?\[hash(?::\d+)?\])/g;
|
|
47
|
-
function hasFileExtension(fileName) {
|
|
48
|
-
return fileName.slice(Math.max(fileName.lastIndexOf("/"), fileName.lastIndexOf("\\")) + 1).lastIndexOf(".") > 0;
|
|
49
|
-
}
|
|
50
|
-
/**
|
|
51
|
-
* Resolve a bundler `filename` template that still contains `[hash]` placeholders
|
|
52
|
-
* into the concrete, stable file name Module Federation serves.
|
|
53
|
-
*
|
|
54
|
-
* The federation entries are emitted by us rather than hashed by the bundler, so
|
|
55
|
-
* the placeholder is dropped instead of being substituted. When stripping it also
|
|
56
|
-
* removes the extension (`mf-[hash:8]` → `mf`), `.js` is appended so the result
|
|
57
|
-
* stays a loadable module. The extension check deliberately looks at the basename
|
|
58
|
-
* only — a dotted directory (`assets/v1.2/entry`) must not be mistaken for one.
|
|
59
|
-
*/
|
|
60
|
-
function resolveHashPlaceholderFileName(fileName) {
|
|
61
|
-
if (!fileName.includes("[hash")) return fileName;
|
|
62
|
-
const normalized = fileName.replace(HASH_PLACEHOLDER_RE, "");
|
|
63
|
-
return hasFileExtension(normalized) ? normalized : `${normalized}.js`;
|
|
64
|
-
}
|
|
65
|
-
//#endregion
|
|
66
|
-
export { resolveHashPlaceholderFileName as i, normalizePathForImport as n, rebaseImport as r, EXTERNAL_URL_RE as t };
|