@module-federation/vite 1.16.12 → 1.16.14
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 +9 -16
- package/lib/index.js +288 -128
- package/lib/ssrVmStrategy-DtpfkCw1.js +152 -0
- package/lib/utils/ssrEntryLoader.d.ts +51 -1
- package/lib/utils/ssrEntryLoader.js +153 -38
- package/package.json +5 -5
package/README.md
CHANGED
|
@@ -6,22 +6,15 @@
|
|
|
6
6
|
|
|
7
7
|
[Read the announcement](https://www.linkedin.com/posts/voidzero_github-module-federationvite-vite-plugin-activity-7449452398202241024-JyAL).
|
|
8
8
|
|
|
9
|
-
<
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
<img src="https://github.com/thecoder93.png?size=96" alt="thecoder93" width="64" height="64" />
|
|
19
|
-
</a>
|
|
20
|
-
<a href="https://github.com/stephanelgrg">
|
|
21
|
-
<img src="https://github.com/stephanelgrg.png?size=96" alt="stephanelgrg" width="64" height="64" />
|
|
22
|
-
</a>
|
|
23
|
-
</p>
|
|
24
|
-
</div>
|
|
9
|
+
<br />
|
|
10
|
+
|
|
11
|
+
<a href="https://github.com/sponsors/gioboa">
|
|
12
|
+
<img src="./docs/sponsors.png" alt="Sponsors" />
|
|
13
|
+
</a>
|
|
14
|
+
|
|
15
|
+
## Become a sponsor
|
|
16
|
+
|
|
17
|
+
[Support this project on GitHub Sponsors](https://github.com/sponsors/gioboa)
|
|
25
18
|
|
|
26
19
|
## Reason why 🤔
|
|
27
20
|
|
package/lib/index.js
CHANGED
|
@@ -380,7 +380,9 @@ function normalizeShared(shared) {
|
|
|
380
380
|
if (Array.isArray(shared)) shared.forEach((key) => {
|
|
381
381
|
if (isModuleFederationRuntimePackage(key)) return;
|
|
382
382
|
const normalizedKey = normalizeSharedKey(key);
|
|
383
|
+
const hadConfiguredPackageSubpath = (result[normalizedKey]?.shareConfig)?.__mfConfiguredPackageSubpath === true;
|
|
383
384
|
result[normalizedKey] = normalizeShareItem(normalizedKey, normalizedKey);
|
|
385
|
+
if (key.endsWith("/") || hadConfiguredPackageSubpath) result[normalizedKey].shareConfig.__mfConfiguredPackageSubpath = true;
|
|
384
386
|
explicitSharedKeys.add(normalizedKey);
|
|
385
387
|
sourceEntries.push([normalizedKey, normalizedKey]);
|
|
386
388
|
});
|
|
@@ -388,7 +390,9 @@ function normalizeShared(shared) {
|
|
|
388
390
|
if (isModuleFederationRuntimePackage(key)) return;
|
|
389
391
|
const normalizedKey = normalizeSharedKey(key);
|
|
390
392
|
const value = shared[key];
|
|
393
|
+
const hadConfiguredPackageSubpath = (result[normalizedKey]?.shareConfig)?.__mfConfiguredPackageSubpath === true;
|
|
391
394
|
result[normalizedKey] = normalizeShareItem(normalizedKey, value);
|
|
395
|
+
if (key.endsWith("/") || hadConfiguredPackageSubpath) result[normalizedKey].shareConfig.__mfConfiguredPackageSubpath = true;
|
|
392
396
|
explicitSharedKeys.add(normalizedKey);
|
|
393
397
|
sourceEntries.push([normalizedKey, value]);
|
|
394
398
|
});
|
|
@@ -650,7 +654,7 @@ function getExposesCssMapPlaceholder() {
|
|
|
650
654
|
function getVirtualExposesId(options) {
|
|
651
655
|
return `virtual:mf-exposes:${`${options.internalName}__${options.filename}`.replace(/[^a-zA-Z0-9_-]/g, "_")}`;
|
|
652
656
|
}
|
|
653
|
-
function generateExposes(options) {
|
|
657
|
+
function generateExposes(options, remoteDependencyMap = {}, command = "build") {
|
|
654
658
|
return `
|
|
655
659
|
const cssAssetMap = ${JSON.stringify(options.bundleAllCSS ? EXPOSES_CSS_MAP_PLACEHOLDER : {})};
|
|
656
660
|
const injectedCssHrefs = new Set();
|
|
@@ -683,8 +687,12 @@ function generateExposes(options) {
|
|
|
683
687
|
}
|
|
684
688
|
injectedCssHrefs.add(href);
|
|
685
689
|
|
|
690
|
+
// Check for any existing stylesheet with the same href, not just
|
|
691
|
+
// MF-injected ones. This prevents duplicate <link> tags when Vite's
|
|
692
|
+
// own CSS module injection or MF runtime's createLink has already
|
|
693
|
+
// created a <link rel="stylesheet"> for the same URL.
|
|
686
694
|
const existingLink = document.querySelector(
|
|
687
|
-
\`link[rel="stylesheet"][
|
|
695
|
+
\`link[rel="stylesheet"][href="\${href}"]\`
|
|
688
696
|
);
|
|
689
697
|
if (existingLink) {
|
|
690
698
|
return Promise.resolve();
|
|
@@ -694,7 +702,6 @@ function generateExposes(options) {
|
|
|
694
702
|
const link = document.createElement("link");
|
|
695
703
|
link.rel = "stylesheet";
|
|
696
704
|
link.href = href;
|
|
697
|
-
link.setAttribute("data-mf-href", href);
|
|
698
705
|
link.onload = () => resolve();
|
|
699
706
|
link.onerror = () => reject(new Error(\`[Module Federation] Failed to load CSS asset: \${href}\`));
|
|
700
707
|
document.head.appendChild(link);
|
|
@@ -705,12 +712,22 @@ function generateExposes(options) {
|
|
|
705
712
|
|
|
706
713
|
export default {
|
|
707
714
|
${Object.keys(options.exposes).map((key) => {
|
|
715
|
+
const remoteDependencyPreloads = (remoteDependencyMap[key] ?? []).map((remoteId) => {
|
|
716
|
+
const virtualRemote = getRemoteVirtualModule(remoteId, command);
|
|
717
|
+
return `import(${JSON.stringify(virtualRemote.getImportId())})
|
|
718
|
+
.then((mod) => mod.__mf_remote_pending)`;
|
|
719
|
+
}).join(",");
|
|
708
720
|
return `
|
|
709
721
|
${JSON.stringify(key)}: async () => {
|
|
710
722
|
await injectCssAssets(${JSON.stringify(key)})
|
|
723
|
+
await Promise.all([${remoteDependencyPreloads}])
|
|
711
724
|
const importModule = await importExposedModule(
|
|
712
725
|
() => import(${JSON.stringify(options.exposes[key].import)})
|
|
713
726
|
)
|
|
727
|
+
const dependencyPending = importModule && importModule.__mf_remote_dependency_pending;
|
|
728
|
+
if (dependencyPending && typeof dependencyPending.then === "function") {
|
|
729
|
+
await dependencyPending;
|
|
730
|
+
}
|
|
714
731
|
const exportModule = {}
|
|
715
732
|
Object.assign(exportModule, importModule)
|
|
716
733
|
Object.defineProperty(exportModule, "__esModule", {
|
|
@@ -1129,6 +1146,15 @@ function getWorkspacePackageJson(pkg) {
|
|
|
1129
1146
|
fromResolvedEntry: resolved
|
|
1130
1147
|
})?.packageJson;
|
|
1131
1148
|
}
|
|
1149
|
+
function getSharedDependencyGraphPackageJson(pkg) {
|
|
1150
|
+
const installedPackageJson = getInstalledPackageJson(pkg, { packageName: getPackageName(pkg) })?.packageJson;
|
|
1151
|
+
if (installedPackageJson) return installedPackageJson;
|
|
1152
|
+
try {
|
|
1153
|
+
const packageJsonPath = createRequire$1(pathToFileURL(path$1.join(getPackageDetectionCwd(), "package.json"))).resolve(`${getPackageName(pkg)}/package.json`);
|
|
1154
|
+
return JSON.parse(readFileSync(packageJsonPath, "utf-8"));
|
|
1155
|
+
} catch {}
|
|
1156
|
+
return getWorkspacePackageJson(pkg);
|
|
1157
|
+
}
|
|
1132
1158
|
function getDependencyNames(packageJson) {
|
|
1133
1159
|
if (!packageJson) return [];
|
|
1134
1160
|
const names = /* @__PURE__ */ new Set();
|
|
@@ -1143,7 +1169,7 @@ function getDependencyNames(packageJson) {
|
|
|
1143
1169
|
}
|
|
1144
1170
|
return Array.from(names);
|
|
1145
1171
|
}
|
|
1146
|
-
function
|
|
1172
|
+
function isSharedSingletonConsumedByPeer(pkg) {
|
|
1147
1173
|
const shared = getNormalizeModuleFederationOptions()?.shared || {};
|
|
1148
1174
|
const sharedKeyByPackageName = /* @__PURE__ */ new Map();
|
|
1149
1175
|
Object.entries(shared).filter(([, item]) => item.shareConfig.singleton === true).forEach(([key]) => {
|
|
@@ -1151,7 +1177,7 @@ function isWorkspaceSingletonConsumedByPeer(pkg) {
|
|
|
1151
1177
|
if (!sharedKeyByPackageName.get(packageName) || key === packageName) sharedKeyByPackageName.set(packageName, key);
|
|
1152
1178
|
});
|
|
1153
1179
|
const reachesPkg = (current, seen) => {
|
|
1154
|
-
const packageJson =
|
|
1180
|
+
const packageJson = getSharedDependencyGraphPackageJson(current);
|
|
1155
1181
|
for (const dependency of getDependencyNames(packageJson)) {
|
|
1156
1182
|
const sharedDependency = sharedKeyByPackageName.get(dependency);
|
|
1157
1183
|
if (!sharedDependency) continue;
|
|
@@ -1164,6 +1190,10 @@ function isWorkspaceSingletonConsumedByPeer(pkg) {
|
|
|
1164
1190
|
};
|
|
1165
1191
|
return Array.from(sharedKeyByPackageName.values()).some((sharedPkg) => sharedPkg !== pkg && reachesPkg(sharedPkg, new Set([sharedPkg])));
|
|
1166
1192
|
}
|
|
1193
|
+
function isRemoteOnlyContainer() {
|
|
1194
|
+
const options = getNormalizeModuleFederationOptions();
|
|
1195
|
+
return Object.keys(options.exposes || {}).length > 0 && Object.keys(options.remotes || {}).length === 0;
|
|
1196
|
+
}
|
|
1167
1197
|
function tryResolveImportFromPackageRoot(pkg, root) {
|
|
1168
1198
|
try {
|
|
1169
1199
|
return resolveWorkspaceEsmEntry(pkg, createRequire$1(pathToFileURL(path$1.join(root, "package.json"))).resolve(pkg), root);
|
|
@@ -1245,7 +1275,7 @@ function writePreBuildLibPath(pkg, shareItem) {
|
|
|
1245
1275
|
const __mfPrebuildExports = __mfPrebuildNamespace;
|
|
1246
1276
|
${declarations}
|
|
1247
1277
|
${namedExportLine}
|
|
1248
|
-
export default
|
|
1278
|
+
export default __mfPrebuildNamespace.default ?? __mfPrebuildNamespace;
|
|
1249
1279
|
`, true);
|
|
1250
1280
|
return;
|
|
1251
1281
|
}
|
|
@@ -1317,37 +1347,34 @@ function generateEagerWorkspaceSingletonExports(namedExports, importSource, cach
|
|
|
1317
1347
|
const __mf_default = exportModule.default ?? exportModule;${namedExportAssignments}
|
|
1318
1348
|
export { __mf_default as default };${namedExportLine}`;
|
|
1319
1349
|
}
|
|
1320
|
-
function generateLazyWorkspaceSingletonExports(namedExports, importSource, cacheDescriptor
|
|
1350
|
+
function generateLazyWorkspaceSingletonExports(namedExports, importSource, cacheDescriptor) {
|
|
1321
1351
|
const namedExportVars = namedExports.map((_name, i) => `__mf_${i}`);
|
|
1322
1352
|
const declarations = namedExports.length > 0 ? ["let __mf_default;", ...namedExportVars.map((name) => `let ${name};`)].join("\n ") : "let __mf_default;";
|
|
1323
1353
|
const assignments = namedExports.length > 0 ? [...namedExports.map((name, i) => `${namedExportVars[i]} = mod[${escapeGeneratedStringLiteral(name)}];`), "__mf_default = mod.default ?? mod;"].join("\n ") : "__mf_default = mod.default ?? mod;";
|
|
1324
1354
|
const namedExportLine = namedExports.length > 0 ? `\n export { ${namedExports.map((name, i) => `${namedExportVars[i]} as ${name}`).join(", ")} };` : "";
|
|
1325
|
-
|
|
1326
|
-
__mfWriteSharedCache(__mfModuleCache.share, ${cacheDescriptor}, exportModule);
|
|
1327
|
-
__mfApplyLazyShareExports(exportModule);`;
|
|
1328
|
-
const body = `${declarations}
|
|
1355
|
+
return `${declarations}
|
|
1329
1356
|
const __mfApplyLazyShareExports = (mod) => {
|
|
1330
1357
|
${assignments}
|
|
1331
1358
|
};
|
|
1332
1359
|
let exportModule = __mfReadSharedCache(__mfModuleCache.share, ${cacheDescriptor});
|
|
1333
1360
|
if (exportModule === undefined) {
|
|
1334
|
-
|
|
1335
|
-
${
|
|
1361
|
+
if (import.meta.env.SSR) {
|
|
1362
|
+
${`exportModule = __mfNormalizeShareModule(__mfLocalShare);
|
|
1363
|
+
__mfWriteSharedCache(__mfModuleCache.share, ${cacheDescriptor}, exportModule);
|
|
1364
|
+
__mfApplyLazyShareExports(exportModule);`}
|
|
1336
1365
|
} else {
|
|
1337
|
-
initPromise.then(() =>
|
|
1366
|
+
(__mfModuleCache.pendingShareLoads ||= []).push(initPromise.then(() =>
|
|
1338
1367
|
import(${escapeGeneratedStringLiteral(importSource)}).then((mod) => {
|
|
1339
1368
|
exportModule = __mfNormalizeShareModule(mod);
|
|
1340
1369
|
__mfWriteSharedCache(__mfModuleCache.share, ${cacheDescriptor}, exportModule);
|
|
1341
1370
|
__mfApplyLazyShareExports(exportModule);
|
|
1342
1371
|
})
|
|
1343
|
-
);
|
|
1344
|
-
}
|
|
1372
|
+
));
|
|
1373
|
+
}
|
|
1345
1374
|
} else {
|
|
1346
1375
|
__mfApplyLazyShareExports(exportModule);
|
|
1347
1376
|
}
|
|
1348
1377
|
export { __mf_default as default };${namedExportLine}`;
|
|
1349
|
-
return eagerLocalFallback ? `import * as __mfLocalShare from ${escapeGeneratedStringLiteral(importSource)};
|
|
1350
|
-
${body}` : body;
|
|
1351
1378
|
}
|
|
1352
1379
|
const WORKSPACE_SINGLETON_SSR_LOCAL_SHARE = "__mfNormalizeShareModule(__mfLocalShare)";
|
|
1353
1380
|
function prependWorkspaceSingletonSsrImport(code) {
|
|
@@ -1367,13 +1394,13 @@ function generateDeferredHostProvidedExports(namedExports, pkg, cacheDescriptor)
|
|
|
1367
1394
|
};
|
|
1368
1395
|
let exportModule = __mfReadSharedCache(__mfModuleCache.share, ${cacheDescriptor});
|
|
1369
1396
|
if (exportModule === undefined) {
|
|
1370
|
-
initPromise.then(() => {
|
|
1397
|
+
(__mfModuleCache.pendingShareLoads ||= []).push(initPromise.then(() => {
|
|
1371
1398
|
exportModule = __mfReadSharedCache(__mfModuleCache.share, ${cacheDescriptor});
|
|
1372
1399
|
if (exportModule === undefined) {
|
|
1373
1400
|
throw new Error("[Module Federation] Shared module ${pkg} was imported before federation bootstrap finished.");
|
|
1374
1401
|
}
|
|
1375
1402
|
__mfApplyHostProvidedExports(exportModule);
|
|
1376
|
-
});
|
|
1403
|
+
}));
|
|
1377
1404
|
} else {
|
|
1378
1405
|
__mfApplyHostProvidedExports(exportModule);
|
|
1379
1406
|
}
|
|
@@ -1421,17 +1448,21 @@ function writeLoadShareModule(pkg, shareItem, command, _isRolldown) {
|
|
|
1421
1448
|
const devImportSource = concreteSharedImportSource || pkg;
|
|
1422
1449
|
const localProviderPath = getLocalProviderImportPath(pkg);
|
|
1423
1450
|
const isWorkspacePackage = isWorkspacePackageEntry(pkg, localProviderPath) || isWorkspacePackageEntry(pkg, concreteSharedImportSource);
|
|
1424
|
-
const lazyLocalFallbackSource = concreteSharedImportSource || localProviderPath || sharedImportSource;
|
|
1451
|
+
const lazyLocalFallbackSource = command !== "build" ? concreteSharedImportSource || localProviderPath || devImportSource : concreteSharedImportSource || localProviderPath || sharedImportSource;
|
|
1425
1452
|
const skipServePrebuildWarmup = command !== "build" && (pkg === "lit" || pkg.startsWith("lit/"));
|
|
1426
1453
|
const isWorkspaceSingleton = isWorkspacePackage && shareItem.shareConfig.singleton === true;
|
|
1427
|
-
const
|
|
1454
|
+
const isDefaultShareScope = shareItem.scope === void 0 || shareItem.scope === "default" || Array.isArray(shareItem.scope) && shareItem.scope[0] === "default";
|
|
1455
|
+
const usesDeferredSingletonFallback = isWorkspaceSingleton || command !== "build" && isRemoteOnlyContainer() && shareItem.shareConfig.singleton === true || command === "build" && isRemoteOnlyContainer() && shareItem.shareConfig.singleton === true && !isDefaultShareScope;
|
|
1456
|
+
const isConsumedByPeerSingleton = isSharedSingletonConsumedByPeer(pkg);
|
|
1457
|
+
const usesEntryInjectedRemoteFallback = command !== "build" && !isWorkspaceSingleton && isRemoteOnlyContainer() && shareItem.shareConfig.singleton === true && getNormalizeModuleFederationOptions().hostInitInjectLocation === "entry" && isConsumedByPeerSingleton;
|
|
1458
|
+
const usesEagerWorkspaceFallback = isWorkspaceSingleton && isConsumedByPeerSingleton;
|
|
1428
1459
|
const namedExports = getSharedNamedExports(pkg, shareItem);
|
|
1429
1460
|
let exportLine;
|
|
1430
1461
|
let initBlock = "";
|
|
1431
|
-
if (usesEagerWorkspaceFallback) exportLine = generateEagerWorkspaceSingletonExports(namedExports, lazyLocalFallbackSource, cacheDescriptor);
|
|
1432
|
-
else if (
|
|
1462
|
+
if (usesEagerWorkspaceFallback || usesEntryInjectedRemoteFallback) exportLine = generateEagerWorkspaceSingletonExports(namedExports, lazyLocalFallbackSource, cacheDescriptor);
|
|
1463
|
+
else if (usesDeferredSingletonFallback) {
|
|
1433
1464
|
importLine = `${getRuntimeInitPromiseBootstrapCode()}\n ${importLine}`;
|
|
1434
|
-
exportLine = generateLazyWorkspaceSingletonExports(namedExports, lazyLocalFallbackSource, cacheDescriptor
|
|
1465
|
+
exportLine = generateLazyWorkspaceSingletonExports(namedExports, lazyLocalFallbackSource, cacheDescriptor);
|
|
1435
1466
|
} else if (namedExports.length > 0) {
|
|
1436
1467
|
const destructure = `const { ${namedExports.map((name, i) => `${name}: __mf_${i}`).join(", ")} } = exportModule;`;
|
|
1437
1468
|
const namedExportLine = `export { ${namedExports.map((name, i) => `__mf_${i} as ${name}`).join(", ")} };`;
|
|
@@ -1452,9 +1483,9 @@ function writeLoadShareModule(pkg, shareItem, command, _isRolldown) {
|
|
|
1452
1483
|
initBlock = `exportModule = __mfNormalizeShareModule(__mfLocalShare);
|
|
1453
1484
|
__mfWriteSharedCache(__mfModuleCache.share, ${cacheDescriptor}, exportModule);`;
|
|
1454
1485
|
}
|
|
1455
|
-
const prebuildImportLine =
|
|
1456
|
-
const devDynamicImportLine = isWorkspacePackage ? "" : command !== "build" && !skipServePrebuildWarmup ? `;() => import(${escapeGeneratedStringLiteral(devImportSource)}).catch(() => {});` : "";
|
|
1457
|
-
const moduleBody =
|
|
1486
|
+
const prebuildImportLine = usesDeferredSingletonFallback || isWorkspacePackage && command !== "build" ? "" : `import * as __mfLocalShare from ${escapeGeneratedStringLiteral(skipServePrebuildWarmup ? devImportSource : sharedImportSource)};`;
|
|
1487
|
+
const devDynamicImportLine = isWorkspacePackage ? "" : usesDeferredSingletonFallback ? "" : command !== "build" && !skipServePrebuildWarmup ? `;() => import(${escapeGeneratedStringLiteral(devImportSource)}).catch(() => {});` : "";
|
|
1488
|
+
const moduleBody = usesDeferredSingletonFallback ? `
|
|
1458
1489
|
${prebuildImportLine}
|
|
1459
1490
|
${devDynamicImportLine}
|
|
1460
1491
|
${importLine}
|
|
@@ -1587,29 +1618,14 @@ function generateLocalSharedImportMap() {
|
|
|
1587
1618
|
}
|
|
1588
1619
|
`;
|
|
1589
1620
|
}
|
|
1590
|
-
function generateUsedSharedPreloadConfig() {
|
|
1591
|
-
return `{
|
|
1592
|
-
${getOrderedUsedShares().map((pkg) => {
|
|
1593
|
-
const shareItem = getShareItemForPreload(pkg);
|
|
1594
|
-
if (!shareItem) return null;
|
|
1595
|
-
return `${JSON.stringify(pkg)}: {
|
|
1596
|
-
version: ${JSON.stringify(shareItem.version)},
|
|
1597
|
-
scope: ${JSON.stringify(shareItem.scope)},
|
|
1598
|
-
shareConfig: {
|
|
1599
|
-
singleton: ${shareItem.shareConfig.singleton},
|
|
1600
|
-
requiredVersion: ${JSON.stringify(shareItem.shareConfig.requiredVersion)},
|
|
1601
|
-
strictVersion: ${shareItem.shareConfig.strictVersion},
|
|
1602
|
-
${shareItem.shareConfig.import === false ? "import: false," : ""}
|
|
1603
|
-
}
|
|
1604
|
-
}`;
|
|
1605
|
-
}).filter((item) => item !== null).join(",\n")}
|
|
1606
|
-
}`;
|
|
1607
|
-
}
|
|
1608
1621
|
function getOrderedUsedShares() {
|
|
1609
1622
|
const shares = new Set(getUsedShares());
|
|
1610
1623
|
try {
|
|
1611
1624
|
Object.keys(getNormalizeModuleFederationOptions().shared).forEach((pkg) => {
|
|
1612
|
-
if (!pkg.endsWith("/"))
|
|
1625
|
+
if (!pkg.endsWith("/")) {
|
|
1626
|
+
shares.add(pkg);
|
|
1627
|
+
return;
|
|
1628
|
+
}
|
|
1613
1629
|
});
|
|
1614
1630
|
} catch {}
|
|
1615
1631
|
return orderSharedDependenciesFirst(Array.from(shares).sort((a, b) => {
|
|
@@ -1712,12 +1728,31 @@ const sharedProviderSelectionHelperCode = `const __mfOriginalProviderKey = Symbo
|
|
|
1712
1728
|
function hasImportFalseShared$1(options) {
|
|
1713
1729
|
return Object.values(options.shared ?? {}).some((share) => share?.shareConfig?.import === false);
|
|
1714
1730
|
}
|
|
1715
|
-
function
|
|
1716
|
-
return
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1731
|
+
function generateRuntimeSharedCacheSeedCode() {
|
|
1732
|
+
return `
|
|
1733
|
+
for (const [pkg, share] of Object.entries(usedShared)) {
|
|
1734
|
+
const cacheDescriptor = __mfGetSharedCacheDescriptor(pkg, share.shareConfig?.singleton, share.version, share.scope);
|
|
1735
|
+
if (share.shareConfig?.import === false || __mfReadSharedCache(__mfModuleCache.share, cacheDescriptor) !== undefined) {
|
|
1736
|
+
continue;
|
|
1737
|
+
}
|
|
1738
|
+
const singletonCacheDescriptor = __mfGetSharedCacheDescriptor(pkg, true, share.version, share.scope);
|
|
1739
|
+
const singletonModule = __mfReadSharedCache(__mfModuleCache.share, singletonCacheDescriptor);
|
|
1740
|
+
if (singletonModule !== undefined) {
|
|
1741
|
+
__mfWriteSharedCache(__mfModuleCache.share, cacheDescriptor, singletonModule);
|
|
1742
|
+
continue;
|
|
1743
|
+
}
|
|
1744
|
+
const factory = await share.get();
|
|
1745
|
+
const mod = typeof factory === "function" ? factory() : factory;
|
|
1746
|
+
const resolved = await Promise.resolve(mod);
|
|
1747
|
+
${normalizeRuntimeShareCode}
|
|
1748
|
+
const normalizedModule = __mfNormalizeRuntimeShare(resolved);
|
|
1749
|
+
const exportModule = normalizedModule === resolved ? {...resolved} : normalizedModule;
|
|
1750
|
+
Object.defineProperty(exportModule, "__esModule", {
|
|
1751
|
+
value: true,
|
|
1752
|
+
enumerable: false
|
|
1753
|
+
});
|
|
1754
|
+
__mfWriteSharedCache(__mfModuleCache.share, cacheDescriptor, exportModule);
|
|
1755
|
+
}`;
|
|
1721
1756
|
}
|
|
1722
1757
|
function getBrowserImportPath(importPath) {
|
|
1723
1758
|
if (/^(?:[a-zA-Z]:[\\/]|\/)/.test(importPath) && !importPath.startsWith("/@")) return `/@fs/${importPath}`;
|
|
@@ -1867,7 +1902,7 @@ function generateRemoteEntry(options, virtualExposesId = getVirtualExposesId(opt
|
|
|
1867
1902
|
__mfWriteSharedCache(__mfModuleCache.share, cacheDescriptor, singletonModule);
|
|
1868
1903
|
}
|
|
1869
1904
|
}
|
|
1870
|
-
${
|
|
1905
|
+
${generateRuntimeSharedCacheSeedCode()}
|
|
1871
1906
|
const __browserPlugins = [${pluginImportNames.filter((item) => !isSsrOnlyPlugin(item[1])).map((item) => `${item[0]}(${item[2]})`).join(", ")}];
|
|
1872
1907
|
const __ssrPlugins = typeof globalThis.window === 'undefined'
|
|
1873
1908
|
? await Promise.all([${pluginImportNames.filter((item) => isSsrOnlyPlugin(item[1])).map((item) => {
|
|
@@ -1890,7 +1925,6 @@ function generateRemoteEntry(options, virtualExposesId = getVirtualExposesId(opt
|
|
|
1890
1925
|
if (initScope.indexOf(initToken) >= 0) return;
|
|
1891
1926
|
initScope.push(initToken);
|
|
1892
1927
|
initRes.initShareScopeMap('${options.shareScope}', shared);
|
|
1893
|
-
initResolve(initRes)
|
|
1894
1928
|
try {
|
|
1895
1929
|
await retrySharedInit(async () => {
|
|
1896
1930
|
await Promise.all(await initRes.initializeSharing('${options.shareScope}', {
|
|
@@ -1914,6 +1948,7 @@ function generateRemoteEntry(options, virtualExposesId = getVirtualExposesId(opt
|
|
|
1914
1948
|
const resolved = await Promise.resolve(mod);
|
|
1915
1949
|
__mfWriteSharedCache(__mfModuleCache.share, cacheDescriptor, __mfNormalizeRuntimeShare(resolved));
|
|
1916
1950
|
}
|
|
1951
|
+
initResolve(initRes)
|
|
1917
1952
|
return initRes
|
|
1918
1953
|
}
|
|
1919
1954
|
|
|
@@ -1943,7 +1978,7 @@ function generateHostAutoInitCode(remoteEntryImport, _command = "build") {
|
|
|
1943
1978
|
${generateHostAutoInitSharedCacheSeedCode(_command)}
|
|
1944
1979
|
const remoteEntry = await import(${remoteEntryImport});
|
|
1945
1980
|
const runtime = await remoteEntry.init();
|
|
1946
|
-
const usedShared = ${
|
|
1981
|
+
const {usedShared} = await import("${getLocalSharedImportMapPath()}");
|
|
1947
1982
|
${normalizeRuntimeShareCode}
|
|
1948
1983
|
${shouldPreloadShares ? `
|
|
1949
1984
|
for (const [pkg, share] of Object.entries(usedShared)) {
|
|
@@ -2272,10 +2307,10 @@ function injectHostInitPreloads(html, bundle, resolvePath) {
|
|
|
2272
2307
|
function getFirstHtmlEntryFile(entryFiles) {
|
|
2273
2308
|
return entryFiles.find((file) => file.endsWith(".html"));
|
|
2274
2309
|
}
|
|
2275
|
-
function stripQueryAndHash(file) {
|
|
2310
|
+
function stripQueryAndHash$1(file) {
|
|
2276
2311
|
return file.split(/[?#]/)[0];
|
|
2277
2312
|
}
|
|
2278
|
-
function resolveDevHashEntryFileName(fileName) {
|
|
2313
|
+
function resolveDevHashEntryFileName$1(fileName) {
|
|
2279
2314
|
if (!fileName.includes("[hash")) return fileName;
|
|
2280
2315
|
const normalized = fileName.replace(/(?:[._-]?\[hash(?::\d+)?\])/g, "");
|
|
2281
2316
|
const baseName = path$1.basename(normalized);
|
|
@@ -2419,6 +2454,9 @@ const addEntry = ({ entryName, entryPath, fileName, inject = "entry", forceClien
|
|
|
2419
2454
|
await __mfHostInit.__tla;
|
|
2420
2455
|
const { initHost } = __mfHostInit;
|
|
2421
2456
|
${preloadBlock}
|
|
2457
|
+
if (__mfModuleCache.pendingShareLoads) {
|
|
2458
|
+
await Promise.all(__mfModuleCache.pendingShareLoads);
|
|
2459
|
+
}
|
|
2422
2460
|
})().then(() => ${importExpression(entrySrc)});
|
|
2423
2461
|
`;
|
|
2424
2462
|
return [
|
|
@@ -2461,7 +2499,7 @@ const addEntry = ({ entryName, entryPath, fileName, inject = "entry", forceClien
|
|
|
2461
2499
|
const scriptRegex = /<script\b(?=[^>]*\btype=["']module["'])(?=[^>]*\bsrc=["']([^"']+)["'])[^>]*>/gi;
|
|
2462
2500
|
let match;
|
|
2463
2501
|
while ((match = scriptRegex.exec(htmlContent)) !== null) {
|
|
2464
|
-
const scriptSrc = stripQueryAndHash(match[1]);
|
|
2502
|
+
const scriptSrc = stripQueryAndHash$1(match[1]);
|
|
2465
2503
|
if (/^(?:[a-z]+:)?\/\//i.test(scriptSrc)) continue;
|
|
2466
2504
|
addEntryFile(scriptSrc);
|
|
2467
2505
|
addEntryFile(scriptSrc.startsWith("/") ? path$1.resolve(viteConfig.root, scriptSrc.slice(1)) : path$1.resolve(path$1.dirname(htmlPath), scriptSrc));
|
|
@@ -2504,7 +2542,7 @@ const addEntry = ({ entryName, entryPath, fileName, inject = "entry", forceClien
|
|
|
2504
2542
|
next();
|
|
2505
2543
|
return;
|
|
2506
2544
|
}
|
|
2507
|
-
const devFileName = resolveDevHashEntryFileName(fileName);
|
|
2545
|
+
const devFileName = resolveDevHashEntryFileName$1(fileName);
|
|
2508
2546
|
if (devFileName !== fileName && req.url?.startsWith((viteConfig.base + devFileName).replace(/^\/?/, "/"))) req.url = req.url.replace(devFileName, fileName);
|
|
2509
2547
|
if (req.url && req.url.startsWith((viteConfig.base + fileName).replace(/^\/?/, "/"))) req.url = devEntryPath;
|
|
2510
2548
|
next();
|
|
@@ -4088,8 +4126,60 @@ function pluginModuleParseEnd_default(excludeFn, options) {
|
|
|
4088
4126
|
}
|
|
4089
4127
|
//#endregion
|
|
4090
4128
|
//#region src/plugins/pluginProxyRemoteEntry.ts
|
|
4129
|
+
function resolveDevHashEntryFileName(fileName) {
|
|
4130
|
+
if (!fileName.includes("[hash")) return fileName;
|
|
4131
|
+
const normalized = fileName.replace(/(?:[._-]?\[hash(?::\d+)?\])/g, "");
|
|
4132
|
+
const baseName = path$1.basename(normalized);
|
|
4133
|
+
return path$1.extname(baseName) ? normalized : `${normalized}.js`;
|
|
4134
|
+
}
|
|
4091
4135
|
function pluginProxyRemoteEntry_default({ options, remoteEntryId, virtualExposesId }) {
|
|
4092
4136
|
let viteConfig, _command, root;
|
|
4137
|
+
let exposeRemoteDependencies = {};
|
|
4138
|
+
function isRemoteImport(source) {
|
|
4139
|
+
return Object.keys(options.remotes).some((name) => source === name || source.startsWith(name + "/"));
|
|
4140
|
+
}
|
|
4141
|
+
function collectImportSources(code) {
|
|
4142
|
+
const sources = /* @__PURE__ */ new Set();
|
|
4143
|
+
for (const match of code.matchAll(/(?:^|[;\n\r])\s*import\s+(?:[\s\S]*?\s+from\s+)?["']([^"']+)["']|import\(\s*["']([^"']+)["']\s*\)/g)) {
|
|
4144
|
+
const source = match[1] || match[2];
|
|
4145
|
+
if (source) sources.add(source);
|
|
4146
|
+
}
|
|
4147
|
+
return Array.from(sources).sort();
|
|
4148
|
+
}
|
|
4149
|
+
function shouldScanResolvedImport(id) {
|
|
4150
|
+
if (!id || id.includes("\0")) return false;
|
|
4151
|
+
if (id.includes("/node_modules/") || id.includes("\\node_modules\\")) return false;
|
|
4152
|
+
return /\.(?:[cm]?[jt]sx?|vue|svelte)(?:\?|$)/.test(id);
|
|
4153
|
+
}
|
|
4154
|
+
async function collectRemoteDependencies(ctx, id, seen = /* @__PURE__ */ new Set()) {
|
|
4155
|
+
if (seen.has(id) || !shouldScanResolvedImport(id)) return [];
|
|
4156
|
+
seen.add(id);
|
|
4157
|
+
let code;
|
|
4158
|
+
try {
|
|
4159
|
+
code = readFileSync$1(id, "utf8");
|
|
4160
|
+
} catch {
|
|
4161
|
+
return [];
|
|
4162
|
+
}
|
|
4163
|
+
const dependencies = /* @__PURE__ */ new Set();
|
|
4164
|
+
for (const source of collectImportSources(code)) {
|
|
4165
|
+
if (isRemoteImport(source)) {
|
|
4166
|
+
dependencies.add(source);
|
|
4167
|
+
continue;
|
|
4168
|
+
}
|
|
4169
|
+
const resolved = await ctx.resolve(source, id);
|
|
4170
|
+
if (!resolved?.id || !shouldScanResolvedImport(resolved.id)) continue;
|
|
4171
|
+
for (const dependency of await collectRemoteDependencies(ctx, resolved.id, seen)) dependencies.add(dependency);
|
|
4172
|
+
}
|
|
4173
|
+
return Array.from(dependencies).sort();
|
|
4174
|
+
}
|
|
4175
|
+
async function refreshExposeRemoteDependencies(ctx) {
|
|
4176
|
+
const next = {};
|
|
4177
|
+
for (const [exposeKey, expose] of Object.entries(options.exposes)) {
|
|
4178
|
+
const resolved = await ctx.resolve(expose.import);
|
|
4179
|
+
next[exposeKey] = resolved?.id ? await collectRemoteDependencies(ctx, resolved.id) : [];
|
|
4180
|
+
}
|
|
4181
|
+
exposeRemoteDependencies = next;
|
|
4182
|
+
}
|
|
4093
4183
|
return {
|
|
4094
4184
|
name: "proxyRemoteEntry",
|
|
4095
4185
|
enforce: "post",
|
|
@@ -4101,6 +4191,7 @@ function pluginProxyRemoteEntry_default({ options, remoteEntryId, virtualExposes
|
|
|
4101
4191
|
_command = command;
|
|
4102
4192
|
},
|
|
4103
4193
|
async buildStart() {
|
|
4194
|
+
await refreshExposeRemoteDependencies(this);
|
|
4104
4195
|
if (_command !== "build") return;
|
|
4105
4196
|
for (const expose of Object.values(options.exposes)) {
|
|
4106
4197
|
const resolved = await this.resolve(expose.import);
|
|
@@ -4122,19 +4213,19 @@ function pluginProxyRemoteEntry_default({ options, remoteEntryId, virtualExposes
|
|
|
4122
4213
|
},
|
|
4123
4214
|
load(id) {
|
|
4124
4215
|
if (id === remoteEntryId) return parsePromise.then((_) => generateRemoteEntry(options, virtualExposesId, _command));
|
|
4125
|
-
if (id === virtualExposesId) return generateExposes(options);
|
|
4216
|
+
if (id === virtualExposesId) return generateExposes(options, exposeRemoteDependencies, _command);
|
|
4126
4217
|
if (_command === "serve" && id.includes(getHostAutoInitPath())) return id;
|
|
4127
4218
|
},
|
|
4128
4219
|
transform(code, id) {
|
|
4129
4220
|
return mapCodeToCodeWithSourcemap((() => {
|
|
4130
4221
|
if (!filterId(id)) return;
|
|
4131
4222
|
if (id.includes(remoteEntryId)) return parsePromise.then((_) => generateRemoteEntry(options, virtualExposesId, _command));
|
|
4132
|
-
if (id === virtualExposesId) return generateExposes(options);
|
|
4223
|
+
if (id === virtualExposesId) return generateExposes(options, exposeRemoteDependencies, _command);
|
|
4133
4224
|
if (id.includes(getHostAutoInitPath())) {
|
|
4134
4225
|
if (_command === "serve") {
|
|
4135
4226
|
const host = typeof viteConfig.server?.host === "string" && viteConfig.server.host !== "0.0.0.0" ? viteConfig.server.host : "localhost";
|
|
4136
4227
|
const resolvedPublicPath = resolvePublicPath(options, viteConfig.base);
|
|
4137
|
-
const publicPath = JSON.stringify((resolvedPublicPath === "auto" ? "/" : resolvedPublicPath) + options.filename);
|
|
4228
|
+
const publicPath = JSON.stringify((resolvedPublicPath === "auto" ? "/" : resolvedPublicPath) + resolveDevHashEntryFileName(options.filename));
|
|
4138
4229
|
const fallbackOrigin = `//${host}:${viteConfig.server?.port}`;
|
|
4139
4230
|
const ssrRemoteEntry = "data:text/javascript," + encodeURIComponent("export async function init(){return {loadRemote:async()=>({}),loadShare:async()=>({})}}");
|
|
4140
4231
|
return `
|
|
@@ -4573,26 +4664,7 @@ function applyRewrites(code, imports, id) {
|
|
|
4573
4664
|
const ms = new CodeRewriter(code);
|
|
4574
4665
|
let changed = false;
|
|
4575
4666
|
let counter = 0;
|
|
4576
|
-
let namedProxyHelperDeclared = false;
|
|
4577
4667
|
const dependencyPendingIds = [];
|
|
4578
|
-
const namedProxyHelper = `function __mfCreateNamedRemoteProxy(ns, key) {
|
|
4579
|
-
const target = function (...args) {
|
|
4580
|
-
const value = ns[key];
|
|
4581
|
-
return typeof value === "function" ? value.apply(this, args) : value;
|
|
4582
|
-
};
|
|
4583
|
-
return new Proxy(target, {
|
|
4584
|
-
get(_target, prop) {
|
|
4585
|
-
if (prop === "then") return undefined;
|
|
4586
|
-
const value = ns[key];
|
|
4587
|
-
if (prop === Symbol.toPrimitive) return () => value;
|
|
4588
|
-
const item = value == null ? undefined : value[prop];
|
|
4589
|
-
return typeof item === "function" ? item.bind(value) : item;
|
|
4590
|
-
},
|
|
4591
|
-
apply(target, thisArg, args) {
|
|
4592
|
-
return target.apply(thisArg, args);
|
|
4593
|
-
}
|
|
4594
|
-
});
|
|
4595
|
-
}`;
|
|
4596
4668
|
for (const imp of imports) switch (imp.kind) {
|
|
4597
4669
|
case "static": {
|
|
4598
4670
|
const src = JSON.stringify(imp.source);
|
|
@@ -4610,20 +4682,8 @@ function applyRewrites(code, imports, id) {
|
|
|
4610
4682
|
importParts.push(`__mf_remote_pending as ${pendingId}`);
|
|
4611
4683
|
let rewrite = `import { ${importParts.join(", ")} } from ${src};`;
|
|
4612
4684
|
if (imp.named.length > 0) {
|
|
4613
|
-
const
|
|
4614
|
-
|
|
4615
|
-
const destructParts = imp.named.map((s, index) => `${s.imported}: ${tempNames[index]}`);
|
|
4616
|
-
const bindingLines = imp.named.map((s, index) => {
|
|
4617
|
-
const temp = tempNames[index];
|
|
4618
|
-
return `const ${s.local} = ${isProxyId} ? __mfCreateNamedRemoteProxy(${nsId}, ${JSON.stringify(s.imported)}) : ${temp};`;
|
|
4619
|
-
});
|
|
4620
|
-
if (!namedProxyHelperDeclared) {
|
|
4621
|
-
rewrite += `\n${namedProxyHelper}`;
|
|
4622
|
-
namedProxyHelperDeclared = true;
|
|
4623
|
-
}
|
|
4624
|
-
rewrite += `\nconst ${isProxyId} = ${nsId} && ${nsId}.__mf_is_remote_proxy;`;
|
|
4625
|
-
rewrite += `\nconst { ${destructParts.join(", ")} } = ${isProxyId} ? {} : ${nsId};`;
|
|
4626
|
-
rewrite += `\n${bindingLines.join("\n")}`;
|
|
4685
|
+
const destructParts = imp.named.map((s) => `${s.imported}: ${s.local}`);
|
|
4686
|
+
rewrite += `\nconst { ${destructParts.join(", ")} } = ${nsId};`;
|
|
4627
4687
|
}
|
|
4628
4688
|
ms.overwrite(imp.start, imp.end, rewrite);
|
|
4629
4689
|
}
|
|
@@ -4880,6 +4940,104 @@ function pluginRemoteNamedExports(options) {
|
|
|
4880
4940
|
}
|
|
4881
4941
|
//#endregion
|
|
4882
4942
|
//#region src/plugins/pluginSSRRemoteEntry.ts
|
|
4943
|
+
const MAX_RUNNER_BODY_BYTES = 1024 * 1024;
|
|
4944
|
+
const ALLOWED_RUNNER_INVOKE_NAMES = new Set(["fetchModule", "getBuiltins"]);
|
|
4945
|
+
const VITE_FS_PREFIX = "/@fs/";
|
|
4946
|
+
function isPlainObject(value) {
|
|
4947
|
+
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
4948
|
+
}
|
|
4949
|
+
function stripQueryAndHash(id) {
|
|
4950
|
+
const queryIndex = id.indexOf("?");
|
|
4951
|
+
const hashIndex = id.indexOf("#");
|
|
4952
|
+
const endIndex = queryIndex === -1 ? hashIndex : hashIndex === -1 ? queryIndex : Math.min(queryIndex, hashIndex);
|
|
4953
|
+
return endIndex === -1 ? id : id.slice(0, endIndex);
|
|
4954
|
+
}
|
|
4955
|
+
function decodeRunnerFilePath(filePath) {
|
|
4956
|
+
try {
|
|
4957
|
+
return decodeURIComponent(filePath);
|
|
4958
|
+
} catch {
|
|
4959
|
+
return;
|
|
4960
|
+
}
|
|
4961
|
+
}
|
|
4962
|
+
function hasRelativeTraversal(id) {
|
|
4963
|
+
return id.split(/[\\/]+/).includes("..");
|
|
4964
|
+
}
|
|
4965
|
+
function getRealPathIfExists(filePath) {
|
|
4966
|
+
try {
|
|
4967
|
+
return fs$1.realpathSync.native(filePath);
|
|
4968
|
+
} catch {
|
|
4969
|
+
return;
|
|
4970
|
+
}
|
|
4971
|
+
}
|
|
4972
|
+
function isPathWithinDirectory(filePath, directory) {
|
|
4973
|
+
const realFilePath = getRealPathIfExists(filePath) ?? path$1.resolve(filePath);
|
|
4974
|
+
const realDirectory = getRealPathIfExists(directory) ?? path$1.resolve(directory);
|
|
4975
|
+
const relative = path$1.relative(realDirectory, realFilePath);
|
|
4976
|
+
return relative === "" || !relative.startsWith("..") && !path$1.isAbsolute(relative);
|
|
4977
|
+
}
|
|
4978
|
+
function getRunnerAllowedDirectories(config) {
|
|
4979
|
+
return [config.root, ...config.server?.fs?.allow ?? []].map((directory) => path$1.resolve(directory));
|
|
4980
|
+
}
|
|
4981
|
+
function isPathWithinAllowedDirectories(filePath, allowedDirectories) {
|
|
4982
|
+
return allowedDirectories.some((directory) => isPathWithinDirectory(filePath, directory));
|
|
4983
|
+
}
|
|
4984
|
+
function isSafeRunnerFetchModuleId(id, config) {
|
|
4985
|
+
if (typeof id !== "string" || !id || id.includes("\0")) return false;
|
|
4986
|
+
const decoded = decodeViteId(id).replace(/^\0+/, "");
|
|
4987
|
+
if (!decoded || decoded.startsWith("virtual:")) return !!decoded;
|
|
4988
|
+
if (/^[a-zA-Z][a-zA-Z\d+\-.]*:/.test(decoded) || decoded.startsWith("//")) return false;
|
|
4989
|
+
const cleanId = decodeRunnerFilePath(stripQueryAndHash(decoded));
|
|
4990
|
+
if (!cleanId || hasRelativeTraversal(cleanId)) return false;
|
|
4991
|
+
const allowedDirectories = getRunnerAllowedDirectories(config);
|
|
4992
|
+
if (cleanId.startsWith(VITE_FS_PREFIX)) {
|
|
4993
|
+
const fsPath = cleanId.slice(5);
|
|
4994
|
+
return path$1.isAbsolute(fsPath) && isPathWithinAllowedDirectories(fsPath, allowedDirectories);
|
|
4995
|
+
}
|
|
4996
|
+
if (path$1.isAbsolute(cleanId)) {
|
|
4997
|
+
if (isPathWithinAllowedDirectories(cleanId, allowedDirectories)) return true;
|
|
4998
|
+
return !fs$1.existsSync(cleanId);
|
|
4999
|
+
}
|
|
5000
|
+
return true;
|
|
5001
|
+
}
|
|
5002
|
+
function isRunnerInvokePayload(payload, config) {
|
|
5003
|
+
if (!payload || typeof payload !== "object") return false;
|
|
5004
|
+
if (payload.type !== "custom" || payload.event !== "vite:invoke") return false;
|
|
5005
|
+
const data = payload.data;
|
|
5006
|
+
if (!data || typeof data !== "object") return false;
|
|
5007
|
+
const name = data.name;
|
|
5008
|
+
const args = data.data;
|
|
5009
|
+
if (typeof name !== "string" || !ALLOWED_RUNNER_INVOKE_NAMES.has(name) || !Array.isArray(args)) return false;
|
|
5010
|
+
if (name === "getBuiltins") return args.length === 0;
|
|
5011
|
+
if (args.length < 1 || args.length > 3) return false;
|
|
5012
|
+
const [id, importer, opts] = args;
|
|
5013
|
+
return isSafeRunnerFetchModuleId(id, config) && (importer === void 0 || importer === null || isSafeRunnerFetchModuleId(importer, config)) && (opts === void 0 || isPlainObject(opts));
|
|
5014
|
+
}
|
|
5015
|
+
function readBoundedRunnerBody(req, res) {
|
|
5016
|
+
return new Promise((resolve) => {
|
|
5017
|
+
const chunks = [];
|
|
5018
|
+
let size = 0;
|
|
5019
|
+
let done = false;
|
|
5020
|
+
const fail = (statusCode, message) => {
|
|
5021
|
+
if (done) return;
|
|
5022
|
+
done = true;
|
|
5023
|
+
res.statusCode = statusCode;
|
|
5024
|
+
res.end(message);
|
|
5025
|
+
resolve(void 0);
|
|
5026
|
+
};
|
|
5027
|
+
req.on("data", (chunk) => {
|
|
5028
|
+
if (done) return;
|
|
5029
|
+
size += chunk.length;
|
|
5030
|
+
if (size > MAX_RUNNER_BODY_BYTES) return fail(413, "Payload too large");
|
|
5031
|
+
chunks.push(chunk);
|
|
5032
|
+
});
|
|
5033
|
+
req.on("end", () => {
|
|
5034
|
+
if (done) return;
|
|
5035
|
+
done = true;
|
|
5036
|
+
resolve(Buffer.concat(chunks));
|
|
5037
|
+
});
|
|
5038
|
+
req.on("error", () => fail(400, "Bad request"));
|
|
5039
|
+
});
|
|
5040
|
+
}
|
|
4883
5041
|
/**
|
|
4884
5042
|
* Emits a Node-compatible SSR remote entry alongside the browser entry.
|
|
4885
5043
|
*
|
|
@@ -4966,7 +5124,8 @@ function pluginSSRRemoteEntry(options) {
|
|
|
4966
5124
|
});
|
|
4967
5125
|
const ssrEnv = server.environments?.ssr;
|
|
4968
5126
|
const clientEnv = server.environments?.client;
|
|
4969
|
-
|
|
5127
|
+
const runnerEnv = typeof ssrEnv?.hot?.handleInvoke === "function" ? ssrEnv : typeof clientEnv?.hot?.handleInvoke === "function" ? clientEnv : void 0;
|
|
5128
|
+
if (typeof (ssrEnv?.fetchModule ?? clientEnv?.fetchModule) === "function" && runnerEnv) server.middlewares.use("/__mf_runner__", async (req, res) => {
|
|
4970
5129
|
res.setHeader("Access-Control-Allow-Origin", "*");
|
|
4971
5130
|
if (req.method === "OPTIONS") {
|
|
4972
5131
|
res.setHeader("Access-Control-Allow-Methods", "POST");
|
|
@@ -4981,46 +5140,37 @@ function pluginSSRRemoteEntry(options) {
|
|
|
4981
5140
|
return;
|
|
4982
5141
|
}
|
|
4983
5142
|
try {
|
|
4984
|
-
const
|
|
4985
|
-
|
|
4986
|
-
|
|
4987
|
-
|
|
4988
|
-
|
|
4989
|
-
}
|
|
4990
|
-
|
|
4991
|
-
|
|
4992
|
-
const builtins = (clientEnv ?? ssrEnv)?.config?.resolve?.builtins ?? [];
|
|
4993
|
-
res.setHeader("Content-Type", "application/json");
|
|
4994
|
-
res.end(JSON.stringify({ result: builtins }));
|
|
5143
|
+
const rawBody = await readBoundedRunnerBody(req, res);
|
|
5144
|
+
if (!rawBody) return;
|
|
5145
|
+
let body;
|
|
5146
|
+
try {
|
|
5147
|
+
body = JSON.parse(rawBody.toString("utf8"));
|
|
5148
|
+
} catch {
|
|
5149
|
+
res.statusCode = 400;
|
|
5150
|
+
res.end(JSON.stringify({ error: { message: "Invalid JSON" } }));
|
|
4995
5151
|
return;
|
|
4996
5152
|
}
|
|
4997
|
-
if (body.
|
|
5153
|
+
if (!isRunnerInvokePayload(body, server.config)) {
|
|
4998
5154
|
res.statusCode = 400;
|
|
4999
|
-
res.end(JSON.stringify({ error: { message:
|
|
5155
|
+
res.end(JSON.stringify({ error: { message: "Invalid runner invoke" } }));
|
|
5000
5156
|
return;
|
|
5001
5157
|
}
|
|
5002
|
-
|
|
5003
|
-
|
|
5004
|
-
|
|
5005
|
-
|
|
5006
|
-
|
|
5007
|
-
result = await fetchFn(id, importer, opts);
|
|
5008
|
-
} catch (fetchErr) {
|
|
5009
|
-
const bareId = decodeViteId(id);
|
|
5010
|
-
try {
|
|
5158
|
+
let result = await runnerEnv.hot.handleInvoke(body);
|
|
5159
|
+
if ("error" in result && body.data.name === "fetchModule") {
|
|
5160
|
+
const id = body.data.data[0];
|
|
5161
|
+
const bareId = typeof id === "string" ? decodeViteId(id).replace(/^\0/, "") : "";
|
|
5162
|
+
if (bareId && !bareId.startsWith(".") && !bareId.startsWith("/") && !bareId.startsWith("file:") && !/^[a-zA-Z][a-zA-Z\d+\-.]*:/.test(bareId)) try {
|
|
5011
5163
|
const { createRequire } = await import("module");
|
|
5012
5164
|
const path = await import("path");
|
|
5013
5165
|
const { pathToFileURL } = await import("url");
|
|
5014
|
-
result = {
|
|
5015
|
-
externalize: pathToFileURL(createRequire(pathToFileURL(path.join(server.config.root, "package.json"))).resolve(bareId
|
|
5166
|
+
result = { result: {
|
|
5167
|
+
externalize: pathToFileURL(createRequire(pathToFileURL(path.join(server.config.root, "package.json"))).resolve(bareId)).href,
|
|
5016
5168
|
type: "module"
|
|
5017
|
-
};
|
|
5018
|
-
} catch {
|
|
5019
|
-
throw fetchErr;
|
|
5020
|
-
}
|
|
5169
|
+
} };
|
|
5170
|
+
} catch {}
|
|
5021
5171
|
}
|
|
5022
5172
|
res.setHeader("Content-Type", "application/json");
|
|
5023
|
-
res.end(JSON.stringify(
|
|
5173
|
+
res.end(JSON.stringify(result));
|
|
5024
5174
|
} catch (e) {
|
|
5025
5175
|
res.setHeader("Content-Type", "application/json");
|
|
5026
5176
|
res.end(JSON.stringify({ error: { message: String(e instanceof Error ? e.message : e) } }));
|
|
@@ -5864,7 +6014,9 @@ function federation(mfUserOptions) {
|
|
|
5864
6014
|
},
|
|
5865
6015
|
load(id) {
|
|
5866
6016
|
if (id.includes("__loadShare__") || id.includes("__loadRemote__")) {
|
|
5867
|
-
|
|
6017
|
+
const virtualModule = VirtualModule.findById(id);
|
|
6018
|
+
if (!virtualModule?.code) return null;
|
|
6019
|
+
let code = virtualModule.code;
|
|
5868
6020
|
const environmentName = this.environment?.name;
|
|
5869
6021
|
if (environmentName && environmentName !== "client" || !environmentName && isSsrBuild) code = prependWorkspaceSingletonSsrImport(code);
|
|
5870
6022
|
code = code.replace(/import\s+["'][^"']*__prebuild__[^"']*["']\s*;?/g, "");
|
|
@@ -5948,7 +6100,15 @@ function federation(mfUserOptions) {
|
|
|
5948
6100
|
options.runtimePlugins.forEach((p) => {
|
|
5949
6101
|
const pluginPath = typeof p === "string" ? p : p[0];
|
|
5950
6102
|
if (SSR_ONLY_PLUGINS.has(pluginPath)) return;
|
|
5951
|
-
if (pluginPath && !pluginPath.startsWith(".") && !pluginPath.startsWith("/") && !pluginPath.startsWith("\0") && !pluginPath.startsWith("virtual:"))
|
|
6103
|
+
if (pluginPath && !pluginPath.startsWith(".") && !pluginPath.startsWith("/") && !pluginPath.startsWith("\0") && !pluginPath.startsWith("virtual:")) {
|
|
6104
|
+
let optimizeDep = pluginPath;
|
|
6105
|
+
if (pluginPath === "@module-federation/dts-plugin/dynamic-remote-type-hints-plugin") try {
|
|
6106
|
+
optimizeDep = normalizePathForImport(resolveImportPath(pluginPath));
|
|
6107
|
+
} catch {
|
|
6108
|
+
optimizeDep = pluginPath;
|
|
6109
|
+
}
|
|
6110
|
+
config.optimizeDeps.include.push(optimizeDep);
|
|
6111
|
+
}
|
|
5952
6112
|
});
|
|
5953
6113
|
if (isRolldown) {
|
|
5954
6114
|
config.build ??= {};
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import { SsrEntryHttpError, neutralizeBrowserPreloadHelpers } from "./utils/ssrEntryLoader.js";
|
|
2
|
+
//#region src/utils/ssrVmStrategy.ts
|
|
3
|
+
/**
|
|
4
|
+
* vm.SourceTextModule strategy for loading remote SSR entries.
|
|
5
|
+
*
|
|
6
|
+
* Unlike the temp-file strategy (which rewrites bare shared imports to
|
|
7
|
+
* host-resolved file:// paths at fetch time), this strategy evaluates the
|
|
8
|
+
* remote's ESM graph with `vm.SourceTextModule` in the current context and
|
|
9
|
+
* resolves bare imports through a linker, in order:
|
|
10
|
+
*
|
|
11
|
+
* 1. The host's federation share scope — `instance.loadShare(name)` on the
|
|
12
|
+
* global `__FEDERATION__` instances. This restores real share-scope
|
|
13
|
+
* semantics (version negotiation, loaded-first reuse) on the server.
|
|
14
|
+
* 2. The build-time `resolvedShared` file map (same source as the temp-file
|
|
15
|
+
* strategy) as a fallback when no instance shares the package.
|
|
16
|
+
* 3. Plain host `import(specifier)` for everything else (node builtins,
|
|
17
|
+
* packages the remote expects the host to provide).
|
|
18
|
+
*
|
|
19
|
+
* Requires Node with `--experimental-vm-modules`; callers must check
|
|
20
|
+
* `isVmStrategyAvailable()` and fall back to the temp-file strategy when the
|
|
21
|
+
* API is missing.
|
|
22
|
+
*/
|
|
23
|
+
let vmApiPromise;
|
|
24
|
+
async function getVmApi() {
|
|
25
|
+
if (!vmApiPromise) vmApiPromise = (async () => {
|
|
26
|
+
try {
|
|
27
|
+
const vm = await import(
|
|
28
|
+
/* @vite-ignore */
|
|
29
|
+
"vm"
|
|
30
|
+
);
|
|
31
|
+
if (typeof vm.SourceTextModule !== "function" || typeof vm.SyntheticModule !== "function") return null;
|
|
32
|
+
return vm;
|
|
33
|
+
} catch {
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
})();
|
|
37
|
+
return vmApiPromise;
|
|
38
|
+
}
|
|
39
|
+
async function isVmStrategyAvailable() {
|
|
40
|
+
return await getVmApi() !== null;
|
|
41
|
+
}
|
|
42
|
+
function getFederationInstances() {
|
|
43
|
+
return globalThis.__FEDERATION__?.__INSTANCES__ ?? [];
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Resolve a bare specifier to a module namespace: share scope first, then the
|
|
47
|
+
* build-time resolvedShared file map, then plain host import.
|
|
48
|
+
*/
|
|
49
|
+
async function loadBareModule(specifier, options) {
|
|
50
|
+
for (const instance of getFederationInstances()) {
|
|
51
|
+
if (typeof instance?.loadShare !== "function") continue;
|
|
52
|
+
if (!instance.options?.shared || !(specifier in instance.options.shared)) continue;
|
|
53
|
+
try {
|
|
54
|
+
const factory = await instance.loadShare(specifier);
|
|
55
|
+
if (typeof factory === "function") {
|
|
56
|
+
const shared = factory();
|
|
57
|
+
if (shared) return shared;
|
|
58
|
+
}
|
|
59
|
+
} catch {}
|
|
60
|
+
}
|
|
61
|
+
const resolvedPath = options.resolvedShared[specifier];
|
|
62
|
+
if (resolvedPath) return import(
|
|
63
|
+
/* @vite-ignore */
|
|
64
|
+
`file://${resolvedPath}`
|
|
65
|
+
);
|
|
66
|
+
return import(
|
|
67
|
+
/* @vite-ignore */
|
|
68
|
+
specifier
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
function createSyntheticModule(vm, specifier, namespace) {
|
|
72
|
+
const source = namespace && typeof namespace === "object" ? namespace : { default: namespace };
|
|
73
|
+
const exportNames = new Set(Object.keys(source));
|
|
74
|
+
exportNames.add("default");
|
|
75
|
+
const syntheticModule = new vm.SyntheticModule([...exportNames], () => {
|
|
76
|
+
for (const exportName of exportNames) if (exportName === "default") syntheticModule.setExport("default", source.default !== void 0 ? source.default : namespace);
|
|
77
|
+
else syntheticModule.setExport(exportName, source[exportName]);
|
|
78
|
+
}, { identifier: `mf-shared:${specifier}` });
|
|
79
|
+
return syntheticModule;
|
|
80
|
+
}
|
|
81
|
+
const httpModuleCache = /* @__PURE__ */ new Map();
|
|
82
|
+
const namespaceCache = /* @__PURE__ */ new Map();
|
|
83
|
+
function getBodyPreview(body) {
|
|
84
|
+
return body.slice(0, 240).replace(/\s+/g, " ").trim();
|
|
85
|
+
}
|
|
86
|
+
async function fetchModuleSource(url) {
|
|
87
|
+
const res = await fetch(url);
|
|
88
|
+
const text = await res.text();
|
|
89
|
+
if (!res.ok) throw new SsrEntryHttpError(url, res.status, res.statusText, getBodyPreview(text));
|
|
90
|
+
return neutralizeBrowserPreloadHelpers(text);
|
|
91
|
+
}
|
|
92
|
+
function isHttpUrl(value) {
|
|
93
|
+
return value.startsWith("http://") || value.startsWith("https://");
|
|
94
|
+
}
|
|
95
|
+
/** Resolve a specifier against the referencing module's URL; null for bare specifiers. */
|
|
96
|
+
function resolveSpecifierUrl(specifier, referencerUrl) {
|
|
97
|
+
if (isHttpUrl(specifier)) return specifier;
|
|
98
|
+
if (specifier.startsWith("./") || specifier.startsWith("../") || specifier.startsWith("/")) return new URL(specifier, referencerUrl).href;
|
|
99
|
+
return null;
|
|
100
|
+
}
|
|
101
|
+
function getHttpModule(vm, url, options) {
|
|
102
|
+
const cacheKey = `${options.versionKey}::${url}`;
|
|
103
|
+
if (!httpModuleCache.has(cacheKey)) httpModuleCache.set(cacheKey, (async () => {
|
|
104
|
+
const code = await fetchModuleSource(url);
|
|
105
|
+
return new vm.SourceTextModule(code, {
|
|
106
|
+
identifier: url,
|
|
107
|
+
initializeImportMeta(meta) {
|
|
108
|
+
meta.url = url;
|
|
109
|
+
},
|
|
110
|
+
importModuleDynamically: (specifier, referencingModule) => importDynamically(vm, specifier, referencingModule, options)
|
|
111
|
+
});
|
|
112
|
+
})().catch((error) => {
|
|
113
|
+
httpModuleCache.delete(cacheKey);
|
|
114
|
+
throw error;
|
|
115
|
+
}));
|
|
116
|
+
return httpModuleCache.get(cacheKey);
|
|
117
|
+
}
|
|
118
|
+
async function linkModule(vm, specifier, referencingModule, options) {
|
|
119
|
+
const url = resolveSpecifierUrl(specifier, referencingModule.identifier);
|
|
120
|
+
if (url) return getHttpModule(vm, url, options);
|
|
121
|
+
return createSyntheticModule(vm, specifier, await loadBareModule(specifier, options));
|
|
122
|
+
}
|
|
123
|
+
async function importDynamically(vm, specifier, referencingModule, options) {
|
|
124
|
+
const linker = (spec, referencer) => linkModule(vm, spec, referencer, options);
|
|
125
|
+
const module = await linker(specifier, referencingModule);
|
|
126
|
+
if (module.status === "unlinked") await module.link(linker);
|
|
127
|
+
if (module.status === "linked") await module.evaluate();
|
|
128
|
+
return module;
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Load and evaluate a remote SSR entry as a `vm.SourceTextModule` graph and
|
|
132
|
+
* return its namespace (the federation container with `init`/`get`).
|
|
133
|
+
* Returns null when the vm module APIs are unavailable.
|
|
134
|
+
*/
|
|
135
|
+
async function loadViaVmStrategy(entryUrl, options) {
|
|
136
|
+
const vm = await getVmApi();
|
|
137
|
+
if (!vm) return null;
|
|
138
|
+
const cacheKey = `${options.versionKey}::${entryUrl}`;
|
|
139
|
+
if (!namespaceCache.has(cacheKey)) namespaceCache.set(cacheKey, (async () => {
|
|
140
|
+
const entryModule = await getHttpModule(vm, entryUrl, options);
|
|
141
|
+
const linker = (specifier, referencingModule) => linkModule(vm, specifier, referencingModule, options);
|
|
142
|
+
if (entryModule.status === "unlinked") await entryModule.link(linker);
|
|
143
|
+
if (entryModule.status === "linked") await entryModule.evaluate();
|
|
144
|
+
return entryModule.namespace;
|
|
145
|
+
})().catch((error) => {
|
|
146
|
+
namespaceCache.delete(cacheKey);
|
|
147
|
+
throw error;
|
|
148
|
+
}));
|
|
149
|
+
return namespaceCache.get(cacheKey);
|
|
150
|
+
}
|
|
151
|
+
//#endregion
|
|
152
|
+
export { isVmStrategyAvailable, loadViaVmStrategy };
|
|
@@ -32,6 +32,29 @@ interface RemoteInfo {
|
|
|
32
32
|
type?: string;
|
|
33
33
|
entryGlobalName?: string;
|
|
34
34
|
}
|
|
35
|
+
declare class SsrEntryHttpError extends Error {
|
|
36
|
+
readonly url: string;
|
|
37
|
+
readonly status: number;
|
|
38
|
+
readonly statusText: string;
|
|
39
|
+
readonly bodyPreview: string;
|
|
40
|
+
constructor(url: string, status: number, statusText: string, bodyPreview: string);
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Drop the loader's caches so the next `loadEntry` re-resolves and re-fetches
|
|
44
|
+
* remote SSR entries. Pass a remote entry URL to scope the invalidation to one
|
|
45
|
+
* remote; call with no arguments to invalidate everything.
|
|
46
|
+
*
|
|
47
|
+
* Note: the MF runtime keeps its own container/module caches per federation
|
|
48
|
+
* instance. This function best-effort clears the module caches of all global
|
|
49
|
+
* federation instances so re-renders load fresh remote modules, but hosts that
|
|
50
|
+
* hold direct references to previously loaded modules keep those references.
|
|
51
|
+
*/
|
|
52
|
+
declare function revalidate(remoteEntryUrl?: string): void;
|
|
53
|
+
/**
|
|
54
|
+
* Neutralize browser-only preload machinery in Vite/Rolldown output so the
|
|
55
|
+
* code can evaluate in Node. Shared by the temp-file and vm strategies.
|
|
56
|
+
*/
|
|
57
|
+
declare function neutralizeBrowserPreloadHelpers(code: string): string;
|
|
35
58
|
/**
|
|
36
59
|
* MF runtime plugin factory.
|
|
37
60
|
*
|
|
@@ -50,6 +73,33 @@ interface SsrEntryLoaderOptions {
|
|
|
50
73
|
* in remote SSR entry temp files — no runtime createRequire walk-up needed.
|
|
51
74
|
*/
|
|
52
75
|
resolvedShared?: Record<string, string>;
|
|
76
|
+
/**
|
|
77
|
+
* How to evaluate remote SSR entries on the server.
|
|
78
|
+
*
|
|
79
|
+
* - `'temp-file'` (default): fetch the ESM graph, rewrite specifiers, write
|
|
80
|
+
* temp files and `import()` them. Works on stock Node; shared packages are
|
|
81
|
+
* pinned to the host's copies via `resolvedShared` (no version negotiation).
|
|
82
|
+
* - `'vm'`: evaluate the graph with `vm.SourceTextModule` and link bare
|
|
83
|
+
* shared imports through the host's federation share scope (`loadShare`),
|
|
84
|
+
* restoring version negotiation. Requires `--experimental-vm-modules`;
|
|
85
|
+
* falls back to `'temp-file'` when unavailable.
|
|
86
|
+
*/
|
|
87
|
+
strategy?: 'temp-file' | 'vm';
|
|
88
|
+
/**
|
|
89
|
+
* Share scope consulted by the `'vm'` strategy when linking bare imports.
|
|
90
|
+
* Defaults to `'default'`.
|
|
91
|
+
*/
|
|
92
|
+
shareScopeName?: string;
|
|
93
|
+
/**
|
|
94
|
+
* Re-check each remote's manifest when the cached SSR entry resolution is
|
|
95
|
+
* older than this many milliseconds. When the manifest's version changes
|
|
96
|
+
* (remote redeployed at the same URL), the loader drops its caches for that
|
|
97
|
+
* remote so subsequent loads use the new build. Omit to cache until process
|
|
98
|
+
* exit or an explicit `revalidate()` call. Only manifest-resolved entries
|
|
99
|
+
* can be revalidated this way — convention-resolved entries have no version
|
|
100
|
+
* source.
|
|
101
|
+
*/
|
|
102
|
+
maxAgeMs?: number;
|
|
53
103
|
}
|
|
54
104
|
declare function ssrEntryLoaderPlugin(options?: SsrEntryLoaderOptions): {
|
|
55
105
|
name: string;
|
|
@@ -63,4 +113,4 @@ declare function ssrEntryLoaderPlugin(options?: SsrEntryLoaderOptions): {
|
|
|
63
113
|
} | undefined>;
|
|
64
114
|
};
|
|
65
115
|
//#endregion
|
|
66
|
-
export { ssrEntryLoaderPlugin as default };
|
|
116
|
+
export { SsrEntryHttpError, ssrEntryLoaderPlugin as default, neutralizeBrowserPreloadHelpers, revalidate };
|
|
@@ -70,10 +70,7 @@ async function getOrCreateRunner(remoteOrigin) {
|
|
|
70
70
|
return await (await fetch(runnerEndpoint, {
|
|
71
71
|
method: "POST",
|
|
72
72
|
headers: { "Content-Type": "application/json" },
|
|
73
|
-
body: JSON.stringify(
|
|
74
|
-
name: payload.data.name,
|
|
75
|
-
data: payload.data.data
|
|
76
|
-
})
|
|
73
|
+
body: JSON.stringify(payload)
|
|
77
74
|
})).json();
|
|
78
75
|
} }
|
|
79
76
|
}, new ESModulesEvaluator());
|
|
@@ -88,6 +85,27 @@ const _path = () => nodeImport("path");
|
|
|
88
85
|
const _fs = () => nodeImport("fs");
|
|
89
86
|
const _crypto = () => nodeImport("crypto");
|
|
90
87
|
const _module = () => nodeImport("module");
|
|
88
|
+
/**
|
|
89
|
+
* Version key for a resolved SSR entry. Derived from the remote's manifest
|
|
90
|
+
* content so a redeploy at the same URL produces a different key, which in
|
|
91
|
+
* turn produces different temp-file names — busting both our caches and
|
|
92
|
+
* Node's ESM module cache. Convention-resolved entries (no manifest) get a
|
|
93
|
+
* stable placeholder key and cannot be revalidated automatically.
|
|
94
|
+
*/
|
|
95
|
+
const UNVERSIONED = "unversioned";
|
|
96
|
+
function hashString(value) {
|
|
97
|
+
let hash = 2166136261;
|
|
98
|
+
for (let i = 0; i < value.length; i++) {
|
|
99
|
+
hash ^= value.charCodeAt(i);
|
|
100
|
+
hash = Math.imul(hash, 16777619);
|
|
101
|
+
}
|
|
102
|
+
return (hash >>> 0).toString(16).padStart(8, "0");
|
|
103
|
+
}
|
|
104
|
+
function computeManifestVersionKey(manifest) {
|
|
105
|
+
const buildVersion = manifest.metaData?.buildInfo?.buildVersion;
|
|
106
|
+
const contentHash = hashString(JSON.stringify(manifest));
|
|
107
|
+
return buildVersion ? `${buildVersion}-${contentHash}` : contentHash;
|
|
108
|
+
}
|
|
91
109
|
const ssrEntryCache = /* @__PURE__ */ new Map();
|
|
92
110
|
const manifestFetchCache = /* @__PURE__ */ new Map();
|
|
93
111
|
var SsrEntryHttpError = class extends Error {
|
|
@@ -149,7 +167,8 @@ function resolveSSREntryUrl(manifest, manifestUrl) {
|
|
|
149
167
|
const entryPath = (meta.ssrRemoteEntry.path || "") + meta.ssrRemoteEntry.name;
|
|
150
168
|
return {
|
|
151
169
|
url: new URL(entryPath, base).href,
|
|
152
|
-
type: meta.ssrRemoteEntry.type || "module"
|
|
170
|
+
type: meta.ssrRemoteEntry.type || "module",
|
|
171
|
+
versionKey: computeManifestVersionKey(manifest)
|
|
153
172
|
};
|
|
154
173
|
}
|
|
155
174
|
/**
|
|
@@ -191,14 +210,17 @@ function buildSsrEntryCandidates(ctx, options = {}) {
|
|
|
191
210
|
const candidates = [];
|
|
192
211
|
if (!options.skipServerBuild) candidates.push({
|
|
193
212
|
url: `${remoteOrigin}/__mf_server__/${filename}.ssr.js`,
|
|
194
|
-
type: "module"
|
|
213
|
+
type: "module",
|
|
214
|
+
versionKey: UNVERSIONED
|
|
195
215
|
});
|
|
196
216
|
candidates.push({
|
|
197
217
|
url: `${base}.ssr.js`,
|
|
198
|
-
type: "module"
|
|
218
|
+
type: "module",
|
|
219
|
+
versionKey: UNVERSIONED
|
|
199
220
|
}, {
|
|
200
221
|
url: `${remoteOrigin}/__mf_ssr__/${filename}.ssr.js`,
|
|
201
|
-
type: "module"
|
|
222
|
+
type: "module",
|
|
223
|
+
versionKey: UNVERSIONED
|
|
202
224
|
});
|
|
203
225
|
return candidates;
|
|
204
226
|
}
|
|
@@ -212,13 +234,15 @@ async function resolveFirstReachableCandidate(candidates) {
|
|
|
212
234
|
async function resolveSSREntryImpl(remoteEntryUrl) {
|
|
213
235
|
if (isSsrEntry(remoteEntryUrl)) return {
|
|
214
236
|
url: remoteEntryUrl,
|
|
215
|
-
type: "module"
|
|
237
|
+
type: "module",
|
|
238
|
+
versionKey: UNVERSIONED
|
|
216
239
|
};
|
|
217
240
|
if (!isManifestEntry(remoteEntryUrl)) {
|
|
218
241
|
const filename = getEntryFilename(remoteEntryUrl);
|
|
219
242
|
const fromServerBuild = await headCheckSsrEntry({
|
|
220
243
|
url: `${remoteEntryUrl.replace(/\/[^/]+$/, "")}/__mf_server__/${filename}.ssr.js`,
|
|
221
|
-
type: "module"
|
|
244
|
+
type: "module",
|
|
245
|
+
versionKey: UNVERSIONED
|
|
222
246
|
});
|
|
223
247
|
if (fromServerBuild) return fromServerBuild;
|
|
224
248
|
}
|
|
@@ -229,9 +253,63 @@ async function resolveSSREntryImpl(remoteEntryUrl) {
|
|
|
229
253
|
}
|
|
230
254
|
return resolveFirstReachableCandidate(buildSsrEntryCandidates(ctx, { skipServerBuild: !isManifestEntry(remoteEntryUrl) }));
|
|
231
255
|
}
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
256
|
+
function setSsrEntryCache(remoteEntryUrl) {
|
|
257
|
+
const record = {
|
|
258
|
+
promise: resolveSSREntryImpl(remoteEntryUrl),
|
|
259
|
+
resolvedAt: Date.now()
|
|
260
|
+
};
|
|
261
|
+
ssrEntryCache.set(remoteEntryUrl, record);
|
|
262
|
+
return record;
|
|
263
|
+
}
|
|
264
|
+
async function getSSREntry(remoteEntryUrl, maxAgeMs) {
|
|
265
|
+
const cached = ssrEntryCache.get(remoteEntryUrl);
|
|
266
|
+
if (!cached) return setSsrEntryCache(remoteEntryUrl).promise;
|
|
267
|
+
if (!(typeof maxAgeMs === "number" && maxAgeMs >= 0 && Date.now() - cached.resolvedAt >= maxAgeMs)) return cached.promise;
|
|
268
|
+
const previous = await cached.promise.catch(() => null);
|
|
269
|
+
manifestFetchCache.delete(getManifestUrl(remoteEntryUrl));
|
|
270
|
+
const record = setSsrEntryCache(remoteEntryUrl);
|
|
271
|
+
const next = await record.promise.catch(() => null);
|
|
272
|
+
if (previous && next && previous.versionKey !== next.versionKey) dropRemoteCaches(remoteEntryUrl);
|
|
273
|
+
return record.promise;
|
|
274
|
+
}
|
|
275
|
+
/**
|
|
276
|
+
* Drop per-remote caches after a version change so old artifacts stop being
|
|
277
|
+
* reused. Temp-file cache keys hold SSR entry/chunk URLs (not the browser
|
|
278
|
+
* entry URL), so scope the invalidation by origin.
|
|
279
|
+
*/
|
|
280
|
+
function dropRemoteCaches(remoteEntryUrl) {
|
|
281
|
+
let origin;
|
|
282
|
+
try {
|
|
283
|
+
origin = new URL(remoteEntryUrl).origin;
|
|
284
|
+
} catch {
|
|
285
|
+
return;
|
|
286
|
+
}
|
|
287
|
+
for (const key of tempFileCache.keys()) if (key.slice(key.indexOf("::") + 2).startsWith(origin)) tempFileCache.delete(key);
|
|
288
|
+
}
|
|
289
|
+
/**
|
|
290
|
+
* Drop the loader's caches so the next `loadEntry` re-resolves and re-fetches
|
|
291
|
+
* remote SSR entries. Pass a remote entry URL to scope the invalidation to one
|
|
292
|
+
* remote; call with no arguments to invalidate everything.
|
|
293
|
+
*
|
|
294
|
+
* Note: the MF runtime keeps its own container/module caches per federation
|
|
295
|
+
* instance. This function best-effort clears the module caches of all global
|
|
296
|
+
* federation instances so re-renders load fresh remote modules, but hosts that
|
|
297
|
+
* hold direct references to previously loaded modules keep those references.
|
|
298
|
+
*/
|
|
299
|
+
function revalidate(remoteEntryUrl) {
|
|
300
|
+
if (remoteEntryUrl) {
|
|
301
|
+
ssrEntryCache.delete(remoteEntryUrl);
|
|
302
|
+
manifestFetchCache.delete(getManifestUrl(remoteEntryUrl));
|
|
303
|
+
dropRemoteCaches(remoteEntryUrl);
|
|
304
|
+
} else {
|
|
305
|
+
ssrEntryCache.clear();
|
|
306
|
+
manifestFetchCache.clear();
|
|
307
|
+
tempFileCache.clear();
|
|
308
|
+
}
|
|
309
|
+
const federation = globalThis.__FEDERATION__;
|
|
310
|
+
for (const instance of federation?.__INSTANCES__ ?? []) try {
|
|
311
|
+
instance?.moduleCache?.clear?.();
|
|
312
|
+
} catch {}
|
|
235
313
|
}
|
|
236
314
|
const tempFileCache = /* @__PURE__ */ new Map();
|
|
237
315
|
let ssrCacheDirPromise;
|
|
@@ -252,14 +330,11 @@ async function getSSRCacheDir() {
|
|
|
252
330
|
})();
|
|
253
331
|
return ssrCacheDirPromise;
|
|
254
332
|
}
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
const resolved = sharedPkgMap.get(specifier);
|
|
261
|
-
return resolved ? m.replace(specifier, `file://${resolved}`) : m;
|
|
262
|
-
});
|
|
333
|
+
/**
|
|
334
|
+
* Neutralize browser-only preload machinery in Vite/Rolldown output so the
|
|
335
|
+
* code can evaluate in Node. Shared by the temp-file and vm strategies.
|
|
336
|
+
*/
|
|
337
|
+
function neutralizeBrowserPreloadHelpers(code) {
|
|
263
338
|
code = code.replace(/import\s*\{([^}]*)\}\s*from\s*["'][^"']*preload-helper[^"']*["'];?/g, (_m, bindings) => {
|
|
264
339
|
return bindings.split(",").map((b) => {
|
|
265
340
|
const parts = b.trim().split(/\s+as\s+/);
|
|
@@ -270,6 +345,16 @@ function transformSsrCode(code, base, sharedPkgMap) {
|
|
|
270
345
|
code = code.replace(/\b([A-Za-z_$][\w$]*)\s*\(\s*\(\s*\)\s*=>\s*import\(([^)]*)\)\s*,\s*\[\]\s*\)/g, "import($2)");
|
|
271
346
|
return code;
|
|
272
347
|
}
|
|
348
|
+
function transformSsrCode(code, base, sharedPkgMap) {
|
|
349
|
+
code = code.replace(/((?:from|export\s*\*\s*from)\s*)(["'`])(\.\.?\/[^"'`\s][^"'`]*)["'`]/g, (_m, prefix, _q, specifier) => `${prefix}"${new URL(specifier, base).href}"`);
|
|
350
|
+
code = code.replace(/(import\s*)(["'`])(\.\.?\/[^"'`\s][^"'`]*)["'`]/g, (_m, prefix, _q, specifier) => `${prefix}"${new URL(specifier, base).href}"`);
|
|
351
|
+
code = code.replace(/(import\s*\(\s*)(["'`])(\.\.?\/[^"'`\s][^"'`]*)["'`](\s*\))/g, (_m, prefix, _q, specifier, suffix) => `${prefix}"${new URL(specifier, base).href}"${suffix}`);
|
|
352
|
+
if (sharedPkgMap && sharedPkgMap.size > 0) code = code.replace(/(?:from|import\s*\()\s*(["'`])([^"'`./][^"'`]*)["'`]/g, (m, _q, specifier) => {
|
|
353
|
+
const resolved = sharedPkgMap.get(specifier);
|
|
354
|
+
return resolved ? m.replace(specifier, `file://${resolved}`) : m;
|
|
355
|
+
});
|
|
356
|
+
return neutralizeBrowserPreloadHelpers(code);
|
|
357
|
+
}
|
|
273
358
|
function isVitePreloadHelperSpecifier(specifier) {
|
|
274
359
|
return specifier.includes("preload-helper");
|
|
275
360
|
}
|
|
@@ -277,10 +362,15 @@ function isVitePreloadHelperSpecifier(specifier) {
|
|
|
277
362
|
* Fetch an HTTP ESM module, transform it, write it to a temp .js file and
|
|
278
363
|
* return the file path. Recursively does the same for HTTP transitive imports
|
|
279
364
|
* so that `import('file:///...temp.js')` can resolve them.
|
|
365
|
+
*
|
|
366
|
+
* `versionKey` participates in both the cache key and the temp file name, so
|
|
367
|
+
* a remote redeploy (new manifest → new key) produces new files and bypasses
|
|
368
|
+
* Node's ESM module cache instead of serving the stale build.
|
|
280
369
|
*/
|
|
281
|
-
async function fetchEsmToTempFile(url, tmpDir, visited, sharedPkgMap) {
|
|
370
|
+
async function fetchEsmToTempFile(url, tmpDir, visited, sharedPkgMap, versionKey = UNVERSIONED) {
|
|
371
|
+
const cacheKey = `${versionKey}::${url}`;
|
|
282
372
|
if (visited.has(url)) return visited.get(url);
|
|
283
|
-
if (tempFileCache.has(
|
|
373
|
+
if (tempFileCache.has(cacheKey)) return tempFileCache.get(cacheKey);
|
|
284
374
|
const promise = (async () => {
|
|
285
375
|
const res = await fetch(url);
|
|
286
376
|
let code = await res.text();
|
|
@@ -292,7 +382,7 @@ async function fetchEsmToTempFile(url, tmpDir, visited, sharedPkgMap) {
|
|
|
292
382
|
while ((m = relRegex.exec(code)) !== null) if ((m[1].startsWith("./") || m[1].startsWith("../")) && !isVitePreloadHelperSpecifier(m[1])) relImports.push(new URL(m[1], base).href);
|
|
293
383
|
const subMap = /* @__PURE__ */ new Map();
|
|
294
384
|
await Promise.all([...new Set(relImports)].filter((u) => u.startsWith("http://") || u.startsWith("https://")).map(async (u) => {
|
|
295
|
-
const tmpPath = await fetchEsmToTempFile(u, tmpDir, visited, sharedPkgMap);
|
|
385
|
+
const tmpPath = await fetchEsmToTempFile(u, tmpDir, visited, sharedPkgMap, versionKey);
|
|
296
386
|
subMap.set(u, `file://${tmpPath}`);
|
|
297
387
|
}));
|
|
298
388
|
code = transformSsrCode(code, base, sharedPkgMap);
|
|
@@ -300,22 +390,36 @@ async function fetchEsmToTempFile(url, tmpDir, visited, sharedPkgMap) {
|
|
|
300
390
|
const { createHash } = await _crypto();
|
|
301
391
|
const { join } = await _path();
|
|
302
392
|
const { writeFileSync } = await _fs();
|
|
303
|
-
const tmpFile = join(tmpDir, `${createHash("sha1").update(
|
|
393
|
+
const tmpFile = join(tmpDir, `${createHash("sha1").update(cacheKey).digest("hex").slice(0, 12)}.js`);
|
|
304
394
|
writeFileSync(tmpFile, code, "utf8");
|
|
305
395
|
visited.set(url, tmpFile);
|
|
306
396
|
return tmpFile;
|
|
307
397
|
})();
|
|
308
|
-
tempFileCache.set(
|
|
398
|
+
tempFileCache.set(cacheKey, promise);
|
|
309
399
|
return promise;
|
|
310
400
|
}
|
|
311
|
-
async function importTempModule(filePath) {
|
|
312
|
-
return await import(
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
)
|
|
401
|
+
async function importTempModule(filePath, versionKey) {
|
|
402
|
+
return await import(`${filePath}?v=${encodeURIComponent(versionKey)}`);
|
|
403
|
+
}
|
|
404
|
+
let warnedVmUnavailable = false;
|
|
405
|
+
async function tryVmStrategy(ssrEntry, options) {
|
|
406
|
+
const { loadViaVmStrategy, isVmStrategyAvailable } = await import("../ssrVmStrategy-DtpfkCw1.js");
|
|
407
|
+
if (!await isVmStrategyAvailable()) {
|
|
408
|
+
if (!warnedVmUnavailable) {
|
|
409
|
+
warnedVmUnavailable = true;
|
|
410
|
+
console.warn("[mf-vite:ssr-entry-loader] strategy \"vm\" requires vm.SourceTextModule (run Node with --experimental-vm-modules); falling back to the temp-file strategy.");
|
|
411
|
+
}
|
|
412
|
+
return null;
|
|
413
|
+
}
|
|
414
|
+
return await loadViaVmStrategy(ssrEntry.url, {
|
|
415
|
+
resolvedShared: options.resolvedShared,
|
|
416
|
+
shareScopeName: options.shareScopeName,
|
|
417
|
+
versionKey: ssrEntry.versionKey
|
|
418
|
+
});
|
|
316
419
|
}
|
|
317
|
-
async function loadSSRRemoteEntry(ssrEntry,
|
|
318
|
-
const { url, type } = ssrEntry;
|
|
420
|
+
async function loadSSRRemoteEntry(ssrEntry, options) {
|
|
421
|
+
const { url, type, versionKey } = ssrEntry;
|
|
422
|
+
const { resolvedShared } = options;
|
|
319
423
|
if (type === "commonjs-module" || type === "commonjs") {
|
|
320
424
|
const { createRequire } = await _module();
|
|
321
425
|
const req = createRequire(import.meta.url);
|
|
@@ -338,12 +442,18 @@ async function loadSSRRemoteEntry(ssrEntry, resolvedShared = {}) {
|
|
|
338
442
|
if (process.env.NODE_ENV !== "production") return null;
|
|
339
443
|
}
|
|
340
444
|
}
|
|
445
|
+
if (options.strategy === "vm") try {
|
|
446
|
+
const fromVm = await tryVmStrategy(ssrEntry, options);
|
|
447
|
+
if (fromVm) return fromVm;
|
|
448
|
+
} catch (error) {
|
|
449
|
+
if (isSsrEntryHttpError(error)) throw error;
|
|
450
|
+
}
|
|
341
451
|
const { mkdirSync } = await _fs();
|
|
342
452
|
const cacheDir = await getSSRCacheDir();
|
|
343
453
|
mkdirSync(cacheDir, { recursive: true });
|
|
344
454
|
const sharedPkgMap = new Map(Object.entries(resolvedShared));
|
|
345
455
|
try {
|
|
346
|
-
return await importTempModule(await fetchEsmToTempFile(url, cacheDir, /* @__PURE__ */ new Map(), sharedPkgMap));
|
|
456
|
+
return await importTempModule(await fetchEsmToTempFile(url, cacheDir, /* @__PURE__ */ new Map(), sharedPkgMap, versionKey), versionKey);
|
|
347
457
|
} catch (error) {
|
|
348
458
|
if (isSsrEntryHttpError(error)) throw error;
|
|
349
459
|
return null;
|
|
@@ -359,18 +469,23 @@ async function loadSSRRemoteEntry(ssrEntry, resolvedShared = {}) {
|
|
|
359
469
|
}
|
|
360
470
|
}
|
|
361
471
|
function ssrEntryLoaderPlugin(options = {}) {
|
|
362
|
-
const
|
|
472
|
+
const resolved = {
|
|
473
|
+
resolvedShared: options.resolvedShared ?? {},
|
|
474
|
+
strategy: options.strategy ?? "temp-file",
|
|
475
|
+
shareScopeName: options.shareScopeName ?? "default",
|
|
476
|
+
maxAgeMs: options.maxAgeMs
|
|
477
|
+
};
|
|
363
478
|
return {
|
|
364
479
|
name: "mf-vite:ssr-entry-loader",
|
|
365
480
|
async loadEntry({ remoteInfo }) {
|
|
366
481
|
if (!isNodeServer()) return;
|
|
367
|
-
const ssrEntry = await getSSREntry(remoteInfo.entry);
|
|
482
|
+
const ssrEntry = await getSSREntry(remoteInfo.entry, resolved.maxAgeMs);
|
|
368
483
|
if (!ssrEntry) return;
|
|
369
|
-
const mod = await loadSSRRemoteEntry(ssrEntry,
|
|
484
|
+
const mod = await loadSSRRemoteEntry(ssrEntry, resolved);
|
|
370
485
|
if (!mod) return;
|
|
371
486
|
return mod;
|
|
372
487
|
}
|
|
373
488
|
};
|
|
374
489
|
}
|
|
375
490
|
//#endregion
|
|
376
|
-
export { ssrEntryLoaderPlugin as default };
|
|
491
|
+
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.16.
|
|
3
|
+
"version": "1.16.14",
|
|
4
4
|
"description": "Vite plugin for Module Federation",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./lib/index.js",
|
|
@@ -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.
|
|
74
|
-
"@module-federation/runtime": "2.
|
|
75
|
-
"@module-federation/sdk": "2.
|
|
73
|
+
"@module-federation/dts-plugin": "2.7.0",
|
|
74
|
+
"@module-federation/runtime": "2.7.0",
|
|
75
|
+
"@module-federation/sdk": "2.7.0"
|
|
76
76
|
},
|
|
77
77
|
"devDependencies": {
|
|
78
78
|
"@playwright/test": "1.58.2",
|
|
@@ -86,4 +86,4 @@
|
|
|
86
86
|
"vite": "8.1.0",
|
|
87
87
|
"vitest": "4.0.18"
|
|
88
88
|
}
|
|
89
|
-
}
|
|
89
|
+
}
|