@module-federation/vite 1.16.3 → 1.16.5
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 +21 -8
- package/lib/{index.d.cts → index.d.ts} +9 -1
- package/lib/{index.mjs → index.js} +162 -71
- package/lib/{packageUtils-Bbc6r6__.mjs → packageUtils-CxYRnFwy.js} +16 -8
- package/lib/{pluginDts-DDx4TPN8.mjs → pluginDts-Bgdw5ODE.js} +2 -2
- package/lib/utils/{ssrEntryLoader.mjs → ssrEntryLoader.js} +3 -3
- package/package.json +13 -21
- package/lib/index.cjs +0 -5364
- package/lib/index.d.mts +0 -167
- package/lib/packageUtils-se-UDhCa.cjs +0 -367
- package/lib/pluginDts-7EoFboCu.cjs +0 -311
- package/lib/utils/ssrEntryLoader.cjs +0 -301
- package/lib/utils/ssrEntryLoader.d.cts +0 -65
- /package/lib/utils/{ssrEntryLoader.d.mts → ssrEntryLoader.d.ts} +0 -0
|
@@ -1,311 +0,0 @@
|
|
|
1
|
-
const require_packageUtils = require("./packageUtils-se-UDhCa.cjs");
|
|
2
|
-
let fs = require("fs");
|
|
3
|
-
fs = require_packageUtils.__toESM(fs);
|
|
4
|
-
let pathe = require("pathe");
|
|
5
|
-
pathe = require_packageUtils.__toESM(pathe);
|
|
6
|
-
let _module_federation_sdk = require("@module-federation/sdk");
|
|
7
|
-
let _module_federation_dts_plugin = require("@module-federation/dts-plugin");
|
|
8
|
-
let _module_federation_dts_plugin_core = require("@module-federation/dts-plugin/core");
|
|
9
|
-
//#region src/plugins/pluginDts.ts
|
|
10
|
-
const DEFAULT_DEV_OPTIONS = {
|
|
11
|
-
disableLiveReload: true,
|
|
12
|
-
disableHotTypesReload: false,
|
|
13
|
-
disableDynamicRemoteTypeHints: false
|
|
14
|
-
};
|
|
15
|
-
const DYNAMIC_HINTS_PLUGIN = "@module-federation/dts-plugin/dynamic-remote-type-hints-plugin";
|
|
16
|
-
const getIPv4 = () => process.env["FEDERATION_IPV4"] || "127.0.0.1";
|
|
17
|
-
const DEV_TYPES_FOLDER = ".dev-server";
|
|
18
|
-
const DEFAULT_PUBLIC_TYPES_FOLDER = "@mf-types";
|
|
19
|
-
const forkDevWorkerPath = require.resolve("@module-federation/dts-plugin/dist/fork-dev-worker.js");
|
|
20
|
-
var DevWorker = class {
|
|
21
|
-
worker = _module_federation_dts_plugin_core.rpc.createRpcWorker(forkDevWorkerPath, {}, void 0, false);
|
|
22
|
-
constructor(options) {
|
|
23
|
-
this.worker.connect(options);
|
|
24
|
-
}
|
|
25
|
-
update() {
|
|
26
|
-
this.worker.process?.send?.({
|
|
27
|
-
type: _module_federation_dts_plugin_core.rpc.RpcGMCallTypes.CALL,
|
|
28
|
-
id: this.worker.id,
|
|
29
|
-
args: [void 0, "update"]
|
|
30
|
-
});
|
|
31
|
-
}
|
|
32
|
-
exit() {
|
|
33
|
-
this.worker.terminate();
|
|
34
|
-
}
|
|
35
|
-
};
|
|
36
|
-
const normalizeDevOptions = (dev) => {
|
|
37
|
-
if (dev === false) return false;
|
|
38
|
-
if (dev === true || typeof dev === "undefined") return { ...DEFAULT_DEV_OPTIONS };
|
|
39
|
-
return {
|
|
40
|
-
...DEFAULT_DEV_OPTIONS,
|
|
41
|
-
...dev
|
|
42
|
-
};
|
|
43
|
-
};
|
|
44
|
-
const buildDtsModuleFederationConfig = (options) => {
|
|
45
|
-
const exposes = {};
|
|
46
|
-
Object.entries(options.exposes).forEach(([key, value]) => {
|
|
47
|
-
if (value.import) exposes[key] = value.import;
|
|
48
|
-
});
|
|
49
|
-
const remotes = {};
|
|
50
|
-
Object.entries(options.remotes).forEach(([key, remote]) => {
|
|
51
|
-
if (!remote.entry) return;
|
|
52
|
-
remotes[key] = `${remote.entryGlobalName?.startsWith("http") || remote.entryGlobalName?.includes(".json") ? remote.name || key : remote.entryGlobalName || remote.name || key}@${remote.entry}`;
|
|
53
|
-
});
|
|
54
|
-
return {
|
|
55
|
-
...options,
|
|
56
|
-
exposes,
|
|
57
|
-
remotes
|
|
58
|
-
};
|
|
59
|
-
};
|
|
60
|
-
const resolveOutputDir = (config) => {
|
|
61
|
-
const { outDir } = config.build;
|
|
62
|
-
if (pathe.isAbsolute(outDir)) return pathe.relative(config.root, outDir);
|
|
63
|
-
return outDir;
|
|
64
|
-
};
|
|
65
|
-
const ensureRuntimePlugin = (options, pluginId) => {
|
|
66
|
-
if (!options.runtimePlugins.some((plugin) => {
|
|
67
|
-
if (typeof plugin === "string") return plugin === pluginId;
|
|
68
|
-
return plugin[0] === pluginId;
|
|
69
|
-
})) options.runtimePlugins.push(pluginId);
|
|
70
|
-
};
|
|
71
|
-
const getExposeImportPaths = (options) => {
|
|
72
|
-
return Object.values(options.exposes).map((value) => {
|
|
73
|
-
return value.import;
|
|
74
|
-
}).filter((value) => Boolean(value));
|
|
75
|
-
};
|
|
76
|
-
const usesVueSfcExposes = (options) => {
|
|
77
|
-
return getExposeImportPaths(options).some((value) => value.endsWith(".vue"));
|
|
78
|
-
};
|
|
79
|
-
const resolveDtsPluginOptions = (dts, options, context) => {
|
|
80
|
-
if (dts === false) return false;
|
|
81
|
-
const inferredGenerateTypesDefaults = { generateAPITypes: true };
|
|
82
|
-
if (usesVueSfcExposes(options) && require_packageUtils.hasPackageDependency("vue-tsc", context)) inferredGenerateTypesDefaults.compilerInstance = "vue-tsc";
|
|
83
|
-
if (dts === true || typeof dts === "undefined") return { generateTypes: inferredGenerateTypesDefaults };
|
|
84
|
-
const generateTypes = dts.generateTypes;
|
|
85
|
-
return {
|
|
86
|
-
...dts,
|
|
87
|
-
generateTypes: generateTypes === false ? false : {
|
|
88
|
-
...inferredGenerateTypesDefaults,
|
|
89
|
-
...generateTypes === true || typeof generateTypes === "undefined" ? {} : generateTypes
|
|
90
|
-
}
|
|
91
|
-
};
|
|
92
|
-
};
|
|
93
|
-
const getBasePath = (base) => {
|
|
94
|
-
if (base.startsWith("http://") || base.startsWith("https://")) return new URL(base).pathname.replace(/\/$/, "") || "/";
|
|
95
|
-
return base.replace(/\/$/, "") || "/";
|
|
96
|
-
};
|
|
97
|
-
const joinBaseAndAsset = (base, assetFileName) => {
|
|
98
|
-
const basePath = getBasePath(base);
|
|
99
|
-
return `${basePath === "/" ? "" : basePath}/${assetFileName}`.replace(/\/{2,}/g, "/");
|
|
100
|
-
};
|
|
101
|
-
const getDevDtsAssetPaths = (options) => {
|
|
102
|
-
const { outputDir, publicTypesFolder, root, base } = options;
|
|
103
|
-
return {
|
|
104
|
-
apiFilePath: pathe.resolve(root, outputDir, `${DEV_TYPES_FOLDER}.d.ts`),
|
|
105
|
-
apiRequestPath: joinBaseAndAsset(base, `${publicTypesFolder}.d.ts`),
|
|
106
|
-
zipFilePath: pathe.resolve(root, outputDir, `${DEV_TYPES_FOLDER}.zip`),
|
|
107
|
-
zipRequestPath: joinBaseAndAsset(base, `${publicTypesFolder}.zip`)
|
|
108
|
-
};
|
|
109
|
-
};
|
|
110
|
-
const createDevDtsAssetMiddleware = (assetPaths) => {
|
|
111
|
-
return (req, res, next) => {
|
|
112
|
-
const requestPath = req.url?.split("?")[0];
|
|
113
|
-
const isZipRequest = requestPath === assetPaths.zipRequestPath;
|
|
114
|
-
const isApiRequest = requestPath === assetPaths.apiRequestPath;
|
|
115
|
-
if (!isZipRequest && !isApiRequest) {
|
|
116
|
-
next();
|
|
117
|
-
return;
|
|
118
|
-
}
|
|
119
|
-
const filePath = isZipRequest ? assetPaths.zipFilePath : assetPaths.apiFilePath;
|
|
120
|
-
if (!fs.default.existsSync(filePath)) {
|
|
121
|
-
res.statusCode = 404;
|
|
122
|
-
res.end();
|
|
123
|
-
return;
|
|
124
|
-
}
|
|
125
|
-
res.statusCode = 200;
|
|
126
|
-
res.setHeader("Content-Type", isZipRequest ? "application/x-gzip" : "application/typescript");
|
|
127
|
-
if (req.method === "HEAD") {
|
|
128
|
-
res.end();
|
|
129
|
-
return;
|
|
130
|
-
}
|
|
131
|
-
const stream = fs.default.createReadStream(filePath);
|
|
132
|
-
stream.on("error", () => {
|
|
133
|
-
if (!res.headersSent) res.statusCode = 500;
|
|
134
|
-
res.end();
|
|
135
|
-
});
|
|
136
|
-
res.on("close", () => {
|
|
137
|
-
stream.destroy();
|
|
138
|
-
});
|
|
139
|
-
stream.pipe(res);
|
|
140
|
-
};
|
|
141
|
-
};
|
|
142
|
-
const normalizeDevDtsOptions = (dts, context) => {
|
|
143
|
-
return (0, _module_federation_sdk.normalizeOptions)((0, _module_federation_dts_plugin.isTSProject)(dts, context), {
|
|
144
|
-
generateTypes: { compileInChildProcess: true },
|
|
145
|
-
consumeTypes: { consumeAPITypes: true },
|
|
146
|
-
extraOptions: {},
|
|
147
|
-
displayErrorInTerminal: typeof dts === "object" && dts ? dts.displayErrorInTerminal : void 0
|
|
148
|
-
}, "mfOptions.dts")(dts);
|
|
149
|
-
};
|
|
150
|
-
const logDtsError = (error, dtsOptions) => {
|
|
151
|
-
if (dtsOptions === false) return;
|
|
152
|
-
if (typeof dtsOptions === "object" && dtsOptions && dtsOptions.displayErrorInTerminal === false) return;
|
|
153
|
-
require_packageUtils.mfError(error);
|
|
154
|
-
};
|
|
155
|
-
function pluginDts(options) {
|
|
156
|
-
if (options.dts === false) return [];
|
|
157
|
-
const baseDtsModuleFederationConfig = buildDtsModuleFederationConfig(options);
|
|
158
|
-
const getDtsModuleFederationConfig = (context) => ({
|
|
159
|
-
...baseDtsModuleFederationConfig,
|
|
160
|
-
dts: resolveDtsPluginOptions(options.dts, options, context)
|
|
161
|
-
});
|
|
162
|
-
let resolvedConfig;
|
|
163
|
-
let devWorker;
|
|
164
|
-
let normalizedDevOptions;
|
|
165
|
-
let hasGeneratedBundle = false;
|
|
166
|
-
return [{
|
|
167
|
-
name: "module-federation-dts-dev",
|
|
168
|
-
apply: "serve",
|
|
169
|
-
config(config) {
|
|
170
|
-
normalizedDevOptions = normalizeDevOptions(options.dev);
|
|
171
|
-
if (!normalizedDevOptions) return;
|
|
172
|
-
if (normalizedDevOptions.disableDynamicRemoteTypeHints) return;
|
|
173
|
-
ensureRuntimePlugin(options, DYNAMIC_HINTS_PLUGIN);
|
|
174
|
-
const define = config.define ? { ...config.define } : {};
|
|
175
|
-
if (!("FEDERATION_IPV4" in define)) define.FEDERATION_IPV4 = JSON.stringify(getIPv4());
|
|
176
|
-
config.define = define;
|
|
177
|
-
},
|
|
178
|
-
configResolved(config) {
|
|
179
|
-
resolvedConfig = config;
|
|
180
|
-
},
|
|
181
|
-
configureServer(server) {
|
|
182
|
-
if (!normalizedDevOptions || !resolvedConfig) return;
|
|
183
|
-
const devOptions = normalizedDevOptions;
|
|
184
|
-
if (devOptions.disableDynamicRemoteTypeHints && devOptions.disableHotTypesReload && devOptions.disableLiveReload) return;
|
|
185
|
-
if (!options.name) throw require_packageUtils.createModuleFederationError("name is required if you want to enable dev server!");
|
|
186
|
-
const outputDir = resolveOutputDir(resolvedConfig);
|
|
187
|
-
const dtsModuleFederationConfig = getDtsModuleFederationConfig(resolvedConfig.root);
|
|
188
|
-
const normalizedDtsOptions = normalizeDevDtsOptions(dtsModuleFederationConfig.dts, resolvedConfig.root);
|
|
189
|
-
if (typeof normalizedDtsOptions !== "object") return;
|
|
190
|
-
const normalizedGenerateTypes = (0, _module_federation_sdk.normalizeOptions)(Boolean(normalizedDtsOptions), { compileInChildProcess: true }, "mfOptions.dts.generateTypes")(normalizedDtsOptions.generateTypes);
|
|
191
|
-
const remote = normalizedGenerateTypes === false ? void 0 : {
|
|
192
|
-
implementation: normalizedDtsOptions.implementation,
|
|
193
|
-
context: resolvedConfig.root,
|
|
194
|
-
outputDir,
|
|
195
|
-
moduleFederationConfig: { ...dtsModuleFederationConfig },
|
|
196
|
-
hostRemoteTypesFolder: normalizedGenerateTypes.typesFolder || DEFAULT_PUBLIC_TYPES_FOLDER,
|
|
197
|
-
...normalizedGenerateTypes,
|
|
198
|
-
typesFolder: DEV_TYPES_FOLDER
|
|
199
|
-
};
|
|
200
|
-
if (remote) server.middlewares.use(createDevDtsAssetMiddleware(getDevDtsAssetPaths({
|
|
201
|
-
outputDir,
|
|
202
|
-
publicTypesFolder: remote.hostRemoteTypesFolder || DEFAULT_PUBLIC_TYPES_FOLDER,
|
|
203
|
-
root: resolvedConfig.root,
|
|
204
|
-
base: resolvedConfig.base
|
|
205
|
-
})));
|
|
206
|
-
if (remote && !remote.tsConfigPath && normalizedDtsOptions.tsConfigPath) remote.tsConfigPath = normalizedDtsOptions.tsConfigPath;
|
|
207
|
-
const normalizedConsumeTypes = (0, _module_federation_sdk.normalizeOptions)(Boolean(normalizedDtsOptions), { consumeAPITypes: true }, "mfOptions.dts.consumeTypes")(normalizedDtsOptions.consumeTypes);
|
|
208
|
-
const host = normalizedConsumeTypes === false ? void 0 : {
|
|
209
|
-
implementation: normalizedDtsOptions.implementation,
|
|
210
|
-
context: resolvedConfig.root,
|
|
211
|
-
moduleFederationConfig: dtsModuleFederationConfig,
|
|
212
|
-
typesFolder: normalizedConsumeTypes.typesFolder || "@mf-types",
|
|
213
|
-
abortOnError: false,
|
|
214
|
-
...normalizedConsumeTypes
|
|
215
|
-
};
|
|
216
|
-
const extraOptions = normalizedDtsOptions.extraOptions || {};
|
|
217
|
-
if (!remote && !host && devOptions.disableLiveReload) return;
|
|
218
|
-
const startDevWorker = async () => {
|
|
219
|
-
let remoteTypeUrls;
|
|
220
|
-
if (host) remoteTypeUrls = await new Promise((resolve) => {
|
|
221
|
-
(0, _module_federation_dts_plugin.consumeTypesAPI)({
|
|
222
|
-
host,
|
|
223
|
-
extraOptions,
|
|
224
|
-
displayErrorInTerminal: normalizedDtsOptions.displayErrorInTerminal
|
|
225
|
-
}, resolve);
|
|
226
|
-
});
|
|
227
|
-
devWorker = new DevWorker({
|
|
228
|
-
name: options.name,
|
|
229
|
-
remote,
|
|
230
|
-
host: host ? {
|
|
231
|
-
...host,
|
|
232
|
-
remoteTypeUrls
|
|
233
|
-
} : void 0,
|
|
234
|
-
extraOptions,
|
|
235
|
-
disableLiveReload: devOptions.disableLiveReload,
|
|
236
|
-
disableHotTypesReload: devOptions.disableHotTypesReload
|
|
237
|
-
});
|
|
238
|
-
const update = () => devWorker?.update();
|
|
239
|
-
server.watcher.on("change", update);
|
|
240
|
-
server.watcher.on("add", update);
|
|
241
|
-
server.watcher.on("unlink", update);
|
|
242
|
-
server.httpServer?.once("close", () => {
|
|
243
|
-
devWorker?.exit();
|
|
244
|
-
server.watcher.off("change", update);
|
|
245
|
-
server.watcher.off("add", update);
|
|
246
|
-
server.watcher.off("unlink", update);
|
|
247
|
-
});
|
|
248
|
-
};
|
|
249
|
-
startDevWorker().catch((error) => {
|
|
250
|
-
logDtsError(error, normalizedDtsOptions);
|
|
251
|
-
});
|
|
252
|
-
}
|
|
253
|
-
}, {
|
|
254
|
-
name: "module-federation-dts-build",
|
|
255
|
-
apply: "build",
|
|
256
|
-
configResolved(config) {
|
|
257
|
-
resolvedConfig = config;
|
|
258
|
-
},
|
|
259
|
-
async generateBundle() {
|
|
260
|
-
if (hasGeneratedBundle) return;
|
|
261
|
-
hasGeneratedBundle = true;
|
|
262
|
-
if (!resolvedConfig) return;
|
|
263
|
-
let normalizedDtsOptions;
|
|
264
|
-
try {
|
|
265
|
-
normalizedDtsOptions = (0, _module_federation_dts_plugin.normalizeDtsOptions)(getDtsModuleFederationConfig(resolvedConfig.root), resolvedConfig.root);
|
|
266
|
-
} catch (error) {
|
|
267
|
-
logDtsError(error, options.dts);
|
|
268
|
-
return;
|
|
269
|
-
}
|
|
270
|
-
if (typeof normalizedDtsOptions !== "object") return;
|
|
271
|
-
const context = resolvedConfig.root;
|
|
272
|
-
const outputDir = resolveOutputDir(resolvedConfig);
|
|
273
|
-
let consumeOptions;
|
|
274
|
-
try {
|
|
275
|
-
consumeOptions = (0, _module_federation_dts_plugin.normalizeConsumeTypesOptions)({
|
|
276
|
-
context,
|
|
277
|
-
dtsOptions: normalizedDtsOptions,
|
|
278
|
-
pluginOptions: getDtsModuleFederationConfig(resolvedConfig.root)
|
|
279
|
-
});
|
|
280
|
-
} catch (error) {
|
|
281
|
-
logDtsError(error, normalizedDtsOptions);
|
|
282
|
-
return;
|
|
283
|
-
}
|
|
284
|
-
if (consumeOptions?.host?.typesOnBuild) try {
|
|
285
|
-
await (0, _module_federation_dts_plugin.consumeTypesAPI)(consumeOptions);
|
|
286
|
-
} catch (error) {
|
|
287
|
-
logDtsError(error, normalizedDtsOptions);
|
|
288
|
-
}
|
|
289
|
-
let generateOptions;
|
|
290
|
-
try {
|
|
291
|
-
generateOptions = (0, _module_federation_dts_plugin.normalizeGenerateTypesOptions)({
|
|
292
|
-
context,
|
|
293
|
-
outputDir,
|
|
294
|
-
dtsOptions: normalizedDtsOptions,
|
|
295
|
-
pluginOptions: getDtsModuleFederationConfig(resolvedConfig.root)
|
|
296
|
-
});
|
|
297
|
-
} catch (error) {
|
|
298
|
-
logDtsError(error, normalizedDtsOptions);
|
|
299
|
-
return;
|
|
300
|
-
}
|
|
301
|
-
if (!generateOptions) return;
|
|
302
|
-
try {
|
|
303
|
-
await (0, _module_federation_dts_plugin.generateTypesAPI)({ dtsManagerOptions: generateOptions });
|
|
304
|
-
} catch (error) {
|
|
305
|
-
logDtsError(error, normalizedDtsOptions);
|
|
306
|
-
}
|
|
307
|
-
}
|
|
308
|
-
}];
|
|
309
|
-
}
|
|
310
|
-
//#endregion
|
|
311
|
-
exports.default = pluginDts;
|
|
@@ -1,301 +0,0 @@
|
|
|
1
|
-
//#region src/utils/ssrEntryLoader.ts
|
|
2
|
-
/**
|
|
3
|
-
* MF runtime plugin that intercepts the `loadEntry` lifecycle hook on the
|
|
4
|
-
* server and loads the SSR-compatible remote entry instead of the browser one.
|
|
5
|
-
*
|
|
6
|
-
* This completely replaces the need for any `@module-federation/sdk` patches.
|
|
7
|
-
* The `loadEntry` hook is emitted by `runtime-core` before it falls through to
|
|
8
|
-
* `loadScriptNode` — if the hook returns a value, the runtime uses it directly.
|
|
9
|
-
*
|
|
10
|
-
* Strategy:
|
|
11
|
-
* - In Node (typeof window === 'undefined'), fetch the remote's mf-manifest.json
|
|
12
|
-
* to discover the ssrRemoteEntry URL and its type.
|
|
13
|
-
* - ESM entry: use a dynamic `import()` — the SSR entry has no browser
|
|
14
|
-
* globals and all shared packages are external.
|
|
15
|
-
* - Dev mode (Vite 8+ only): use `ModuleRunner` with an HTTP transport backed
|
|
16
|
-
* by the remote's `/__mf_runner__` endpoint. This fetches fully-transformed
|
|
17
|
-
* module source through Vite's plugin pipeline, avoiding serialisation which
|
|
18
|
-
* cannot faithfully represent React components or closures.
|
|
19
|
-
*
|
|
20
|
-
* Dev mode on Vite < 8 is NOT supported — `ModuleRunner` and
|
|
21
|
-
* `FetchableDevEnvironment` are Vite 8+ APIs. If you need dev-mode SSR on
|
|
22
|
-
* an older Vite version, implement an alternative loader in `loadSSRRemoteEntry`
|
|
23
|
-
* for the `isDevSsrEntry` branch and expose a corresponding server endpoint
|
|
24
|
-
* from `pluginSSRRemoteEntry.configureServer`.
|
|
25
|
-
*
|
|
26
|
-
* Exported as a plain factory function so it can be serialised into the
|
|
27
|
-
* generated runtimePlugins list in virtualRemotes.ts.
|
|
28
|
-
*/
|
|
29
|
-
const importCache = /* @__PURE__ */ new Map();
|
|
30
|
-
async function nodeImport(id) {
|
|
31
|
-
if (!importCache.has(id)) importCache.set(id, import(
|
|
32
|
-
/* @vite-ignore */
|
|
33
|
-
id
|
|
34
|
-
));
|
|
35
|
-
return importCache.get(id);
|
|
36
|
-
}
|
|
37
|
-
const runnerCache = /* @__PURE__ */ new Map();
|
|
38
|
-
/**
|
|
39
|
-
* Import `vite/module-runner` dynamically. Returns null on Vite < 8 where the
|
|
40
|
-
* subpath doesn't exist. Uses a plain dynamic import (not nodeImport) so that
|
|
41
|
-
* Vitest can intercept it with vi.mock in tests.
|
|
42
|
-
*/
|
|
43
|
-
async function getModuleRunnerModule() {
|
|
44
|
-
try {
|
|
45
|
-
return await import("vite/module-runner");
|
|
46
|
-
} catch {
|
|
47
|
-
return null;
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
/**
|
|
51
|
-
* Create a ModuleRunner that fetches modules from a remote Vite dev server's
|
|
52
|
-
* `/__mf_runner__` endpoint. Each HTTP POST carries a `fetchModule` invoke
|
|
53
|
-
* payload; the remote responds with the transformed module source as JSON.
|
|
54
|
-
*
|
|
55
|
-
* This is Vite 8+ only — older versions don't expose `vite/module-runner` or
|
|
56
|
-
* the `/__mf_runner__` proxy endpoint.
|
|
57
|
-
*/
|
|
58
|
-
async function getOrCreateRunner(remoteOrigin) {
|
|
59
|
-
if (runnerCache.has(remoteOrigin)) return runnerCache.get(remoteOrigin);
|
|
60
|
-
const promise = (async () => {
|
|
61
|
-
const viteRunner = await getModuleRunnerModule();
|
|
62
|
-
if (!viteRunner) return null;
|
|
63
|
-
const { ModuleRunner, ESModulesEvaluator } = viteRunner;
|
|
64
|
-
const runnerEndpoint = `${remoteOrigin}/__mf_runner__`;
|
|
65
|
-
try {
|
|
66
|
-
return new ModuleRunner({
|
|
67
|
-
hmr: false,
|
|
68
|
-
transport: { async invoke(payload) {
|
|
69
|
-
return await (await fetch(runnerEndpoint, {
|
|
70
|
-
method: "POST",
|
|
71
|
-
headers: { "Content-Type": "application/json" },
|
|
72
|
-
body: JSON.stringify({
|
|
73
|
-
name: payload.data.name,
|
|
74
|
-
data: payload.data.data
|
|
75
|
-
})
|
|
76
|
-
})).json();
|
|
77
|
-
} }
|
|
78
|
-
}, new ESModulesEvaluator());
|
|
79
|
-
} catch {
|
|
80
|
-
return null;
|
|
81
|
-
}
|
|
82
|
-
})();
|
|
83
|
-
runnerCache.set(remoteOrigin, promise);
|
|
84
|
-
return promise;
|
|
85
|
-
}
|
|
86
|
-
const _path = () => nodeImport("path");
|
|
87
|
-
const _fs = () => nodeImport("fs");
|
|
88
|
-
const _crypto = () => nodeImport("crypto");
|
|
89
|
-
const _module = () => nodeImport("module");
|
|
90
|
-
const manifestCache = /* @__PURE__ */ new Map();
|
|
91
|
-
async function fetchManifest(manifestUrl) {
|
|
92
|
-
try {
|
|
93
|
-
const res = await fetch(manifestUrl);
|
|
94
|
-
if (!res.ok) return null;
|
|
95
|
-
return await res.json();
|
|
96
|
-
} catch {
|
|
97
|
-
return null;
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
function getManifestUrl(remoteEntryUrl) {
|
|
101
|
-
return remoteEntryUrl.replace(/\/[^/]+$/, "/mf-manifest.json");
|
|
102
|
-
}
|
|
103
|
-
function resolveSSREntryUrl(manifest, manifestUrl) {
|
|
104
|
-
const meta = manifest?.metaData;
|
|
105
|
-
if (!meta?.ssrRemoteEntry?.name) return null;
|
|
106
|
-
const base = manifestUrl.replace(/\/[^/]+$/, "/");
|
|
107
|
-
const entryPath = (meta.ssrRemoteEntry.path || "") + meta.ssrRemoteEntry.name;
|
|
108
|
-
return {
|
|
109
|
-
url: new URL(entryPath, base).href,
|
|
110
|
-
type: meta.ssrRemoteEntry.type || "module"
|
|
111
|
-
};
|
|
112
|
-
}
|
|
113
|
-
/**
|
|
114
|
-
* Derive the SSR entry URL by convention when no manifest is available.
|
|
115
|
-
* remoteEntry.js → remoteEntry.ssr.js
|
|
116
|
-
* remoteEntry.js → /__mf_ssr__/remoteEntry.ssr.js (dev middleware)
|
|
117
|
-
* Returns the first URL that responds with a 200.
|
|
118
|
-
*/
|
|
119
|
-
async function headCheckSsrEntry(candidate) {
|
|
120
|
-
try {
|
|
121
|
-
const res = await fetch(candidate.url, { method: "HEAD" });
|
|
122
|
-
const ct = res.headers.get("content-type") ?? "";
|
|
123
|
-
if (res.ok && !ct.includes("text/html")) return candidate;
|
|
124
|
-
} catch {}
|
|
125
|
-
return null;
|
|
126
|
-
}
|
|
127
|
-
async function getSSREntryByConvention(remoteEntryUrl, options = {}) {
|
|
128
|
-
const base = remoteEntryUrl.replace(/\.[^.]+$/, "");
|
|
129
|
-
const remoteOrigin = remoteEntryUrl.replace(/\/[^/]+$/, "");
|
|
130
|
-
const filename = remoteEntryUrl.split("/").pop()?.replace(/\.[^.]+$/, "") ?? "remoteEntry";
|
|
131
|
-
const candidates = [
|
|
132
|
-
...options.skipServerBuild ? [] : [{
|
|
133
|
-
url: `${remoteOrigin}/__mf_server__/${filename}.ssr.js`,
|
|
134
|
-
type: "module"
|
|
135
|
-
}],
|
|
136
|
-
{
|
|
137
|
-
url: `${base}.ssr.js`,
|
|
138
|
-
type: "module"
|
|
139
|
-
},
|
|
140
|
-
{
|
|
141
|
-
url: `${remoteOrigin}/__mf_ssr__/${filename}.ssr.js`,
|
|
142
|
-
type: "module"
|
|
143
|
-
}
|
|
144
|
-
];
|
|
145
|
-
for (const candidate of candidates) {
|
|
146
|
-
const hit = await headCheckSsrEntry(candidate);
|
|
147
|
-
if (hit) return hit;
|
|
148
|
-
}
|
|
149
|
-
return null;
|
|
150
|
-
}
|
|
151
|
-
async function getSSREntry(remoteEntryUrl) {
|
|
152
|
-
const remoteOrigin = remoteEntryUrl.replace(/\/[^/]+$/, "");
|
|
153
|
-
const filename = remoteEntryUrl.split("/").pop()?.replace(/\.[^.]+$/, "") ?? "remoteEntry";
|
|
154
|
-
const manifestUrl = getManifestUrl(remoteEntryUrl);
|
|
155
|
-
if (!manifestCache.has(manifestUrl)) manifestCache.set(manifestUrl, (async () => {
|
|
156
|
-
const fromServerBuild = await headCheckSsrEntry({
|
|
157
|
-
url: `${remoteOrigin}/__mf_server__/${filename}.ssr.js`,
|
|
158
|
-
type: "module"
|
|
159
|
-
});
|
|
160
|
-
if (fromServerBuild) return fromServerBuild;
|
|
161
|
-
const manifest = await fetchManifest(manifestUrl);
|
|
162
|
-
if (manifest) {
|
|
163
|
-
const fromManifest = resolveSSREntryUrl(manifest, manifestUrl);
|
|
164
|
-
if (fromManifest) return fromManifest;
|
|
165
|
-
}
|
|
166
|
-
return getSSREntryByConvention(remoteEntryUrl, { skipServerBuild: true });
|
|
167
|
-
})());
|
|
168
|
-
return manifestCache.get(manifestUrl);
|
|
169
|
-
}
|
|
170
|
-
const tempFileCache = /* @__PURE__ */ new Map();
|
|
171
|
-
let ssrCacheDirPromise;
|
|
172
|
-
async function getSSRCacheDir() {
|
|
173
|
-
if (!ssrCacheDirPromise) ssrCacheDirPromise = (async () => {
|
|
174
|
-
const { join } = await _path();
|
|
175
|
-
const { rmSync } = await _fs();
|
|
176
|
-
const dir = join(process.cwd(), "node_modules", ".ssr-cache");
|
|
177
|
-
process.once("exit", () => {
|
|
178
|
-
try {
|
|
179
|
-
rmSync(dir, {
|
|
180
|
-
recursive: true,
|
|
181
|
-
force: true
|
|
182
|
-
});
|
|
183
|
-
} catch {}
|
|
184
|
-
});
|
|
185
|
-
return dir;
|
|
186
|
-
})();
|
|
187
|
-
return ssrCacheDirPromise;
|
|
188
|
-
}
|
|
189
|
-
function transformSsrCode(code, base, sharedPkgMap) {
|
|
190
|
-
code = code.replace(/((?:from|export\s*\*\s*from)\s*)(["'`])(\.\.?\/[^"'`\s][^"'`]*)["'`]/g, (_m, prefix, _q, specifier) => `${prefix}"${new URL(specifier, base).href}"`);
|
|
191
|
-
code = code.replace(/(import\s*)(["'`])(\.\.?\/[^"'`\s][^"'`]*)["'`]/g, (_m, prefix, _q, specifier) => `${prefix}"${new URL(specifier, base).href}"`);
|
|
192
|
-
code = code.replace(/(import\s*\(\s*)(["'`])(\.\.?\/[^"'`\s][^"'`]*)["'`](\s*\))/g, (_m, prefix, _q, specifier, suffix) => `${prefix}"${new URL(specifier, base).href}"${suffix}`);
|
|
193
|
-
if (sharedPkgMap && sharedPkgMap.size > 0) code = code.replace(/(?:from|import\s*\()\s*(["'`])([^"'`./][^"'`]*)["'`]/g, (m, _q, specifier) => {
|
|
194
|
-
const resolved = sharedPkgMap.get(specifier);
|
|
195
|
-
return resolved ? m.replace(specifier, `file://${resolved}`) : m;
|
|
196
|
-
});
|
|
197
|
-
code = code.replace(/import\s*\{([^}]*)\}\s*from\s*["'][^"']*preload-helper[^"']*["'];?/g, (_m, bindings) => {
|
|
198
|
-
return bindings.split(",").map((b) => {
|
|
199
|
-
const parts = b.trim().split(/\s+as\s+/);
|
|
200
|
-
return (parts[1] ?? parts[0]).trim();
|
|
201
|
-
}).filter(Boolean).map((l) => `const ${l} = (fn) => fn();`).join("\n");
|
|
202
|
-
});
|
|
203
|
-
code = code.replace(/__vite__mapDeps\([^)]+\)/g, "[]");
|
|
204
|
-
code = code.replace(/\b([A-Za-z_$][\w$]*)\s*\(\s*\(\s*\)\s*=>\s*import\(([^)]*)\)\s*,\s*\[\]\s*\)/g, "import($2)");
|
|
205
|
-
return code;
|
|
206
|
-
}
|
|
207
|
-
/**
|
|
208
|
-
* Fetch an HTTP ESM module, transform it, write it to a temp .mjs file and
|
|
209
|
-
* return the file path. Recursively does the same for HTTP transitive imports
|
|
210
|
-
* so that `import('file:///...temp.mjs')` can resolve them.
|
|
211
|
-
*/
|
|
212
|
-
async function fetchEsmToTempFile(url, tmpDir, visited, sharedPkgMap) {
|
|
213
|
-
if (visited.has(url)) return visited.get(url);
|
|
214
|
-
if (tempFileCache.has(url)) return tempFileCache.get(url);
|
|
215
|
-
const promise = (async () => {
|
|
216
|
-
let code = await (await fetch(url)).text();
|
|
217
|
-
const base = url.replace(/\/[^/]*$/, "/");
|
|
218
|
-
const relImports = [];
|
|
219
|
-
const relRegex = /(?:from|export\s*\*\s*from|import\s*(?:\(|\s))\s*["'`]([^"'`\s]+)["'`]/g;
|
|
220
|
-
let m;
|
|
221
|
-
while ((m = relRegex.exec(code)) !== null) if (m[1].startsWith("./") || m[1].startsWith("../")) relImports.push(new URL(m[1], base).href);
|
|
222
|
-
const subMap = /* @__PURE__ */ new Map();
|
|
223
|
-
await Promise.all([...new Set(relImports)].filter((u) => u.startsWith("http://") || u.startsWith("https://")).map(async (u) => {
|
|
224
|
-
const tmpPath = await fetchEsmToTempFile(u, tmpDir, visited, sharedPkgMap);
|
|
225
|
-
subMap.set(u, `file://${tmpPath}`);
|
|
226
|
-
}));
|
|
227
|
-
code = transformSsrCode(code, base, sharedPkgMap);
|
|
228
|
-
for (const [httpUrl, fileUrl] of subMap) code = code.split(httpUrl).join(fileUrl);
|
|
229
|
-
const { createHash } = await _crypto();
|
|
230
|
-
const { join } = await _path();
|
|
231
|
-
const { writeFileSync } = await _fs();
|
|
232
|
-
const tmpFile = join(tmpDir, `${createHash("sha1").update(url).digest("hex").slice(0, 12)}.mjs`);
|
|
233
|
-
writeFileSync(tmpFile, code, "utf8");
|
|
234
|
-
visited.set(url, tmpFile);
|
|
235
|
-
return tmpFile;
|
|
236
|
-
})();
|
|
237
|
-
tempFileCache.set(url, promise);
|
|
238
|
-
return promise;
|
|
239
|
-
}
|
|
240
|
-
async function importTempModule(filePath) {
|
|
241
|
-
return await import(
|
|
242
|
-
/* @vite-ignore */
|
|
243
|
-
filePath
|
|
244
|
-
);
|
|
245
|
-
}
|
|
246
|
-
async function loadSSRRemoteEntry(ssrEntry, resolvedShared = {}) {
|
|
247
|
-
const { url, type } = ssrEntry;
|
|
248
|
-
if (type === "commonjs-module" || type === "commonjs") {
|
|
249
|
-
const { createRequire } = await _module();
|
|
250
|
-
const req = createRequire(require("url").pathToFileURL(__filename).href);
|
|
251
|
-
try {
|
|
252
|
-
return req(url);
|
|
253
|
-
} catch {}
|
|
254
|
-
}
|
|
255
|
-
if (url.startsWith("http://") || url.startsWith("https://")) {
|
|
256
|
-
const urlObj = new URL(url);
|
|
257
|
-
if (urlObj.pathname.includes("/__mf_ssr__/")) {
|
|
258
|
-
const remoteOrigin = urlObj.origin;
|
|
259
|
-
const runner = await getOrCreateRunner(remoteOrigin);
|
|
260
|
-
if (!runner) return null;
|
|
261
|
-
try {
|
|
262
|
-
return await runner.import(urlObj.pathname);
|
|
263
|
-
} catch {
|
|
264
|
-
return null;
|
|
265
|
-
}
|
|
266
|
-
}
|
|
267
|
-
const { mkdirSync } = await _fs();
|
|
268
|
-
const cacheDir = await getSSRCacheDir();
|
|
269
|
-
mkdirSync(cacheDir, { recursive: true });
|
|
270
|
-
const sharedPkgMap = new Map(Object.entries(resolvedShared));
|
|
271
|
-
try {
|
|
272
|
-
return await importTempModule(await fetchEsmToTempFile(url, cacheDir, /* @__PURE__ */ new Map(), sharedPkgMap));
|
|
273
|
-
} catch {
|
|
274
|
-
return null;
|
|
275
|
-
}
|
|
276
|
-
}
|
|
277
|
-
try {
|
|
278
|
-
return await import(
|
|
279
|
-
/* @vite-ignore */
|
|
280
|
-
url
|
|
281
|
-
);
|
|
282
|
-
} catch {
|
|
283
|
-
return null;
|
|
284
|
-
}
|
|
285
|
-
}
|
|
286
|
-
function ssrEntryLoaderPlugin(options = {}) {
|
|
287
|
-
const resolvedShared = options.resolvedShared ?? {};
|
|
288
|
-
return {
|
|
289
|
-
name: "mf-vite:ssr-entry-loader",
|
|
290
|
-
async loadEntry({ remoteInfo }) {
|
|
291
|
-
if (typeof globalThis.window !== "undefined") return;
|
|
292
|
-
const ssrEntry = await getSSREntry(remoteInfo.entry);
|
|
293
|
-
if (!ssrEntry) return;
|
|
294
|
-
const mod = await loadSSRRemoteEntry(ssrEntry, resolvedShared);
|
|
295
|
-
if (!mod) return;
|
|
296
|
-
return mod;
|
|
297
|
-
}
|
|
298
|
-
};
|
|
299
|
-
}
|
|
300
|
-
//#endregion
|
|
301
|
-
module.exports = ssrEntryLoaderPlugin;
|
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
//#region src/utils/ssrEntryLoader.d.ts
|
|
2
|
-
/**
|
|
3
|
-
* MF runtime plugin that intercepts the `loadEntry` lifecycle hook on the
|
|
4
|
-
* server and loads the SSR-compatible remote entry instead of the browser one.
|
|
5
|
-
*
|
|
6
|
-
* This completely replaces the need for any `@module-federation/sdk` patches.
|
|
7
|
-
* The `loadEntry` hook is emitted by `runtime-core` before it falls through to
|
|
8
|
-
* `loadScriptNode` — if the hook returns a value, the runtime uses it directly.
|
|
9
|
-
*
|
|
10
|
-
* Strategy:
|
|
11
|
-
* - In Node (typeof window === 'undefined'), fetch the remote's mf-manifest.json
|
|
12
|
-
* to discover the ssrRemoteEntry URL and its type.
|
|
13
|
-
* - ESM entry: use a dynamic `import()` — the SSR entry has no browser
|
|
14
|
-
* globals and all shared packages are external.
|
|
15
|
-
* - Dev mode (Vite 8+ only): use `ModuleRunner` with an HTTP transport backed
|
|
16
|
-
* by the remote's `/__mf_runner__` endpoint. This fetches fully-transformed
|
|
17
|
-
* module source through Vite's plugin pipeline, avoiding serialisation which
|
|
18
|
-
* cannot faithfully represent React components or closures.
|
|
19
|
-
*
|
|
20
|
-
* Dev mode on Vite < 8 is NOT supported — `ModuleRunner` and
|
|
21
|
-
* `FetchableDevEnvironment` are Vite 8+ APIs. If you need dev-mode SSR on
|
|
22
|
-
* an older Vite version, implement an alternative loader in `loadSSRRemoteEntry`
|
|
23
|
-
* for the `isDevSsrEntry` branch and expose a corresponding server endpoint
|
|
24
|
-
* from `pluginSSRRemoteEntry.configureServer`.
|
|
25
|
-
*
|
|
26
|
-
* Exported as a plain factory function so it can be serialised into the
|
|
27
|
-
* generated runtimePlugins list in virtualRemotes.ts.
|
|
28
|
-
*/
|
|
29
|
-
interface RemoteInfo {
|
|
30
|
-
name: string;
|
|
31
|
-
entry: string;
|
|
32
|
-
type?: string;
|
|
33
|
-
entryGlobalName?: string;
|
|
34
|
-
}
|
|
35
|
-
/**
|
|
36
|
-
* MF runtime plugin factory.
|
|
37
|
-
*
|
|
38
|
-
* Usage in runtimePlugins:
|
|
39
|
-
* import { ssrEntryLoaderPlugin } from '@module-federation/vite/ssrEntryLoader'
|
|
40
|
-
* federation({ runtimePlugins: [ssrEntryLoaderPlugin] })
|
|
41
|
-
*
|
|
42
|
-
* The plugin is also injected automatically for SSR contexts by the vite plugin.
|
|
43
|
-
*/
|
|
44
|
-
interface SsrEntryLoaderOptions {
|
|
45
|
-
/**
|
|
46
|
-
* Pre-resolved absolute file paths for common shared packages, keyed by
|
|
47
|
-
* bare specifier. Populated at build time by the Vite plugin from the MF
|
|
48
|
-
* plugin's own installed location so the resolution is package-manager-
|
|
49
|
-
* agnostic. ssrEntryLoader uses these directly when rewriting bare specifiers
|
|
50
|
-
* in remote SSR entry temp files — no runtime createRequire walk-up needed.
|
|
51
|
-
*/
|
|
52
|
-
resolvedShared?: Record<string, string>;
|
|
53
|
-
}
|
|
54
|
-
declare function ssrEntryLoaderPlugin(options?: SsrEntryLoaderOptions): {
|
|
55
|
-
name: string;
|
|
56
|
-
loadEntry({
|
|
57
|
-
remoteInfo
|
|
58
|
-
}: {
|
|
59
|
-
remoteInfo: RemoteInfo;
|
|
60
|
-
}): Promise<{
|
|
61
|
-
init: unknown;
|
|
62
|
-
get: unknown;
|
|
63
|
-
} | undefined>;
|
|
64
|
-
};
|
|
65
|
-
export = ssrEntryLoaderPlugin;
|
|
File without changes
|