@module-federation/metro 2.3.3 → 2.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -70,19 +70,44 @@ function buildLoadBundleAsyncWrapper() {
70
70
  const loadBundleAsync =
71
71
  __loadBundleAsync as typeof globalThis.__loadBundleAsync;
72
72
 
73
- return async (originalBundlePath: string) => {
73
+ return async (originalBundlePath: string): Promise<void> => {
74
74
  const scope = globalThis.__FEDERATION__.__NATIVE__[__METRO_GLOBAL_PREFIX__];
75
75
 
76
76
  // entry is always in the root directory of assets associated with remote
77
77
  // based on that, we extract the public path from the origin URL
78
78
  // e.g. http://example.com/a/b/c/mf-manfiest.json -> http://example.com/a/b/c
79
79
  const publicPath = getPublicPath(scope.origin);
80
- const bundlePath = getBundlePath(originalBundlePath, publicPath);
80
+ let bundlePath = getBundlePath(originalBundlePath, publicPath);
81
+
82
+ const isSplitBundle = !isUrl(originalBundlePath);
83
+
84
+ // Cache handler registered externally (e.g. by zephyr-native-cache register()).
85
+ // Stored under __FEDERATION__.__NATIVE__.__CACHE__ as a singleton (no prefix scope).
86
+ const cacheHandler = (globalThis as any).__FEDERATION__?.__NATIVE__
87
+ ?.__CACHE__ as
88
+ | ((
89
+ fallback: (path: string) => Promise<void>,
90
+ bundlePath: string,
91
+ ) => Promise<void>)
92
+ | undefined;
93
+
94
+ // For remote split bundles with cache enabled, convert relative paths to
95
+ // full URLs so they enter the same cache path as container bundles.
96
+ // In dev mode, getBundlePath returns relative paths unchanged, but we need
97
+ // full URLs for the cache layer (download + eval).
98
+ if (isSplitBundle && cacheHandler && publicPath && !isUrl(bundlePath)) {
99
+ bundlePath = joinComponents(publicPath, bundlePath);
100
+ }
81
101
 
82
102
  // ../../node_modules/ -> ..%2F..%2Fnode_modules/ so that it's not automatically sanitized
83
103
  const encodedBundlePath = bundlePath.replaceAll('../', '..%2F');
84
104
 
85
- const result = await loadBundleAsync(encodedBundlePath);
105
+ let result;
106
+ if (cacheHandler) {
107
+ await cacheHandler(loadBundleAsync, encodedBundlePath);
108
+ } else {
109
+ result = await loadBundleAsync(encodedBundlePath);
110
+ }
86
111
 
87
112
  // when the origin is not the same, it means we are loading a remote container
88
113
  // we can return early since dependencies are processed differently for entry bundles
@@ -2,7 +2,6 @@ import type { Federation } from '@module-federation/runtime';
2
2
  import React from 'react';
3
3
 
4
4
  declare global {
5
- // @ts-expect-error -- Intentional redeclaration for Metro/React Native runtime global.
6
5
  // eslint-disable-next-line no-var
7
6
  var __DEV__: boolean;
8
7
  // eslint-disable-next-line no-var
@@ -4,7 +4,6 @@ import type {
4
4
  } from '@module-federation/runtime';
5
5
 
6
6
  declare global {
7
- // @ts-expect-error -- Intentional redeclaration for Metro/React Native runtime global.
8
7
  // eslint-disable-next-line no-var
9
8
  var __DEV__: boolean;
10
9
  // eslint-disable-next-line no-var
@@ -97,12 +97,12 @@ function generateRemotes(remotes = {}) {
97
97
  const remoteEntryParts = remoteEntry.split('@');
98
98
  const remoteName = remoteEntryParts[0];
99
99
  const remoteEntryUrl = remoteEntryParts.slice(1).join('@');
100
- remotesEntries.push(`{
101
- alias: "${remoteAlias}",
102
- name: "${remoteName}",
103
- entry: "${remoteEntryUrl}",
104
- entryGlobalName: "${remoteName}",
105
- type: "var"
100
+ remotesEntries.push(`{
101
+ alias: ${JSON.stringify(remoteAlias)},
102
+ name: ${JSON.stringify(remoteName)},
103
+ entry: ${JSON.stringify(remoteEntryUrl)},
104
+ entryGlobalName: ${JSON.stringify(remoteName)},
105
+ type: "var"
106
106
  }`);
107
107
  }
108
108
  return `[${remotesEntries.join(',\n')}]`;
@@ -55,12 +55,12 @@ function generateRemotes(remotes = {}) {
55
55
  const remoteEntryParts = remoteEntry.split('@');
56
56
  const remoteName = remoteEntryParts[0];
57
57
  const remoteEntryUrl = remoteEntryParts.slice(1).join('@');
58
- remotesEntries.push(`{
59
- alias: "${remoteAlias}",
60
- name: "${remoteName}",
61
- entry: "${remoteEntryUrl}",
62
- entryGlobalName: "${remoteName}",
63
- type: "var"
58
+ remotesEntries.push(`{
59
+ alias: ${JSON.stringify(remoteAlias)},
60
+ name: ${JSON.stringify(remoteName)},
61
+ entry: ${JSON.stringify(remoteEntryUrl)},
62
+ entryGlobalName: ${JSON.stringify(remoteName)},
63
+ type: "var"
64
64
  }`);
65
65
  }
66
66
  return `[${remotesEntries.join(',\n')}]`;
@@ -45,6 +45,7 @@ const federated_remote_types_js_namespaceObject = require("../utils/federated-re
45
45
  const external_babel_transformer_js_namespaceObject = require("./babel-transformer.js");
46
46
  const external_helpers_js_namespaceObject = require("./helpers.js");
47
47
  const external_manifest_js_namespaceObject = require("./manifest.js");
48
+ const external_manifest_middleware_js_namespaceObject = require("./manifest-middleware.js");
48
49
  const external_normalize_extra_options_js_namespaceObject = require("./normalize-extra-options.js");
49
50
  const external_normalize_options_js_namespaceObject = require("./normalize-options.js");
50
51
  const external_resolver_js_namespaceObject = require("./resolver.js");
@@ -87,7 +88,12 @@ function augmentConfig(config, federationOptions, extraOptions) {
87
88
  enableInitializeCorePatching: flags.unstable_patchInitializeCore,
88
89
  enableRuntimeRequirePatching: flags.unstable_patchRuntimeRequire
89
90
  });
90
- const manifestPath = (0, external_manifest_js_namespaceObject.createManifest)(options, tmpDirPath);
91
+ const manifestOptions = {
92
+ projectRoot: config.projectRoot,
93
+ target: (0, external_helpers_js_namespaceObject.isUsingMFBundleCommand)() ? 'build' : 'development',
94
+ tmpDirPath
95
+ };
96
+ const manifestPath = (0, external_manifest_js_namespaceObject.createManifest)(options, tmpDirPath, manifestOptions);
91
97
  (0, external_helpers_js_namespaceObject.stubHostEntry)(hostEntryPath);
92
98
  (0, external_helpers_js_namespaceObject.stubRemoteEntry)(remoteEntryPath);
93
99
  global.__METRO_FEDERATION_CONFIG = options;
@@ -106,7 +112,7 @@ function augmentConfig(config, federationOptions, extraOptions) {
106
112
  ...config,
107
113
  serializer: {
108
114
  ...config.serializer,
109
- customSerializer: (0, external_serializer_js_namespaceObject.getModuleFederationSerializer)(options, (0, external_helpers_js_namespaceObject.isUsingMFBundleCommand)()),
115
+ customSerializer: (0, external_serializer_js_namespaceObject.getModuleFederationSerializer)(options, (0, external_helpers_js_namespaceObject.isUsingMFBundleCommand)(), manifestPath, manifestOptions),
110
116
  getModulesRunBeforeMainModule: (entryFilePath)=>{
111
117
  if (flags.unstable_patchInitializeCore) return config.serializer.getModulesRunBeforeMainModule(entryFilePath);
112
118
  if (isRemote) return [];
@@ -150,7 +156,16 @@ function augmentConfig(config, federationOptions, extraOptions) {
150
156
  },
151
157
  server: {
152
158
  ...config.server,
153
- enhanceMiddleware: vmManager.getMiddleware(),
159
+ enhanceMiddleware: (middleware, metroServer)=>{
160
+ const manifestMiddleware = (0, external_manifest_middleware_js_namespaceObject.createManifestMiddleware)({
161
+ federationConfig: options,
162
+ projectRoot: config.projectRoot,
163
+ remoteEntryPath,
164
+ tmpDirPath,
165
+ vmManager
166
+ })(middleware, metroServer);
167
+ return vmManager.getMiddleware()(manifestMiddleware, metroServer);
168
+ },
154
169
  rewriteRequestUrl: (0, external_rewrite_request_js_namespaceObject.createRewriteRequest)({
155
170
  config,
156
171
  originalEntryFilename,
@@ -8,6 +8,7 @@ import { applyTypesMetaToManifest, maybeGenerateFederatedRemoteTypes } from "../
8
8
  import { createBabelTransformer } from "./babel-transformer.mjs";
9
9
  import { isUsingMFBundleCommand, isUsingMFCommand, prepareTmpDir, replaceExtension, stubHostEntry, stubRemoteEntry } from "./helpers.mjs";
10
10
  import { createManifest } from "./manifest.mjs";
11
+ import { createManifestMiddleware } from "./manifest-middleware.mjs";
11
12
  import { normalizeExtraOptions } from "./normalize-extra-options.mjs";
12
13
  import { normalizeOptions } from "./normalize-options.mjs";
13
14
  import { createResolveRequest } from "./resolver.mjs";
@@ -50,7 +51,12 @@ function augmentConfig(config, federationOptions, extraOptions) {
50
51
  enableInitializeCorePatching: flags.unstable_patchInitializeCore,
51
52
  enableRuntimeRequirePatching: flags.unstable_patchRuntimeRequire
52
53
  });
53
- const manifestPath = createManifest(options, tmpDirPath);
54
+ const manifestOptions = {
55
+ projectRoot: config.projectRoot,
56
+ target: isUsingMFBundleCommand() ? 'build' : 'development',
57
+ tmpDirPath
58
+ };
59
+ const manifestPath = createManifest(options, tmpDirPath, manifestOptions);
54
60
  stubHostEntry(hostEntryPath);
55
61
  stubRemoteEntry(remoteEntryPath);
56
62
  global.__METRO_FEDERATION_CONFIG = options;
@@ -69,7 +75,7 @@ function augmentConfig(config, federationOptions, extraOptions) {
69
75
  ...config,
70
76
  serializer: {
71
77
  ...config.serializer,
72
- customSerializer: getModuleFederationSerializer(options, isUsingMFBundleCommand()),
78
+ customSerializer: getModuleFederationSerializer(options, isUsingMFBundleCommand(), manifestPath, manifestOptions),
73
79
  getModulesRunBeforeMainModule: (entryFilePath)=>{
74
80
  if (flags.unstable_patchInitializeCore) return config.serializer.getModulesRunBeforeMainModule(entryFilePath);
75
81
  if (isRemote) return [];
@@ -113,7 +119,16 @@ function augmentConfig(config, federationOptions, extraOptions) {
113
119
  },
114
120
  server: {
115
121
  ...config.server,
116
- enhanceMiddleware: vmManager.getMiddleware(),
122
+ enhanceMiddleware: (middleware, metroServer)=>{
123
+ const manifestMiddleware = createManifestMiddleware({
124
+ federationConfig: options,
125
+ projectRoot: config.projectRoot,
126
+ remoteEntryPath,
127
+ tmpDirPath,
128
+ vmManager
129
+ })(middleware, metroServer);
130
+ return vmManager.getMiddleware()(manifestMiddleware, metroServer);
131
+ },
117
132
  rewriteRequestUrl: createRewriteRequest({
118
133
  config,
119
134
  originalEntryFilename,
@@ -0,0 +1,12 @@
1
+ import type { ServerConfigT } from 'metro-config';
2
+ import type { ModuleFederationConfigNormalized } from '../types';
3
+ import type { VirtualModuleManager } from '../utils';
4
+ type ManifestMiddlewareOptions = {
5
+ federationConfig: ModuleFederationConfigNormalized;
6
+ projectRoot: string;
7
+ remoteEntryPath: string;
8
+ tmpDirPath: string;
9
+ vmManager: Pick<VirtualModuleManager, 'registerVirtualModule'>;
10
+ };
11
+ export declare function createManifestMiddleware({ federationConfig, projectRoot, remoteEntryPath, tmpDirPath, vmManager, }: ManifestMiddlewareOptions): NonNullable<ServerConfigT['enhanceMiddleware']>;
12
+ export {};
@@ -0,0 +1,213 @@
1
+ "use strict";
2
+ var __webpack_require__ = {};
3
+ (()=>{
4
+ __webpack_require__.n = (module)=>{
5
+ var getter = module && module.__esModule ? ()=>module['default'] : ()=>module;
6
+ __webpack_require__.d(getter, {
7
+ a: getter
8
+ });
9
+ return getter;
10
+ };
11
+ })();
12
+ (()=>{
13
+ __webpack_require__.d = (exports1, definition)=>{
14
+ for(var key in definition)if (__webpack_require__.o(definition, key) && !__webpack_require__.o(exports1, key)) Object.defineProperty(exports1, key, {
15
+ enumerable: true,
16
+ get: definition[key]
17
+ });
18
+ };
19
+ })();
20
+ (()=>{
21
+ __webpack_require__.o = (obj, prop)=>Object.prototype.hasOwnProperty.call(obj, prop);
22
+ })();
23
+ (()=>{
24
+ __webpack_require__.r = (exports1)=>{
25
+ if ('undefined' != typeof Symbol && Symbol.toStringTag) Object.defineProperty(exports1, Symbol.toStringTag, {
26
+ value: 'Module'
27
+ });
28
+ Object.defineProperty(exports1, '__esModule', {
29
+ value: true
30
+ });
31
+ };
32
+ })();
33
+ var __webpack_exports__ = {};
34
+ __webpack_require__.r(__webpack_exports__);
35
+ __webpack_require__.d(__webpack_exports__, {
36
+ createManifestMiddleware: ()=>createManifestMiddleware
37
+ });
38
+ const external_node_fs_namespaceObject = require("node:fs");
39
+ const external_node_path_namespaceObject = require("node:path");
40
+ var external_node_path_default = /*#__PURE__*/ __webpack_require__.n(external_node_path_namespaceObject);
41
+ const metro_compat_js_namespaceObject = require("../utils/metro-compat.js");
42
+ const external_constants_js_namespaceObject = require("./constants.js");
43
+ const external_generators_js_namespaceObject = require("./generators.js");
44
+ const external_helpers_js_namespaceObject = require("./helpers.js");
45
+ const external_manifest_js_namespaceObject = require("./manifest.js");
46
+ function createManifestMiddleware({ federationConfig, projectRoot, remoteEntryPath, tmpDirPath, vmManager }) {
47
+ const warmups = new Map();
48
+ return (middleware, metroServer)=>{
49
+ const nextMiddleware = middleware;
50
+ return async (req, res, next)=>{
51
+ try {
52
+ const warmupOptions = getManifestWarmupOptions(req.url);
53
+ if (warmupOptions) {
54
+ const warmupKey = JSON.stringify(warmupOptions);
55
+ let warmup = warmups.get(warmupKey);
56
+ if (!warmup) {
57
+ warmup = warmManifestBundles(metroServer, {
58
+ federationConfig,
59
+ host: req.headers.host,
60
+ projectRoot,
61
+ remoteEntryPath,
62
+ tmpDirPath,
63
+ vmManager,
64
+ warmupOptions
65
+ }).finally(()=>{
66
+ warmups.delete(warmupKey);
67
+ });
68
+ warmups.set(warmupKey, warmup);
69
+ }
70
+ await warmup;
71
+ }
72
+ return nextMiddleware(req, res, next);
73
+ } catch (error) {
74
+ next(error instanceof Error ? error : new Error(String(error)));
75
+ }
76
+ };
77
+ };
78
+ }
79
+ function getManifestWarmupOptions(rawUrl) {
80
+ const parsedUrl = new URL(rawUrl ?? '/', 'http://localhost');
81
+ if (parsedUrl.pathname !== `/${external_constants_js_namespaceObject.MANIFEST_FILENAME}`) return null;
82
+ const platform = parsedUrl.searchParams.get('platform');
83
+ if (!platform) return null;
84
+ return {
85
+ dev: getBoolean(parsedUrl.searchParams, 'dev', true),
86
+ excludeSource: getBoolean(parsedUrl.searchParams, 'excludeSource', false),
87
+ minify: getBoolean(parsedUrl.searchParams, 'minify', false),
88
+ platform,
89
+ sourcePaths: parsedUrl.searchParams.get('sourcePaths') ?? void 0
90
+ };
91
+ }
92
+ async function warmManifestBundles(metroServer, { federationConfig, host, projectRoot, remoteEntryPath, tmpDirPath, vmManager, warmupOptions }) {
93
+ for (const request of getBundleWarmupRequests({
94
+ federationConfig,
95
+ projectRoot,
96
+ remoteEntryPath,
97
+ tmpDirPath,
98
+ vmManager
99
+ })){
100
+ if (request.virtualModule) await ensureVirtualModuleReady(request.virtualModule, vmManager);
101
+ await metroServer.build(getBundleOptions(request, host, warmupOptions));
102
+ }
103
+ }
104
+ function getBundleWarmupRequests({ federationConfig, projectRoot, remoteEntryPath, tmpDirPath }) {
105
+ const relativeTmpDirPath = (0, external_helpers_js_namespaceObject.toPosixPath)(external_node_path_default().relative(projectRoot, tmpDirPath));
106
+ const remoteEntryName = (0, external_helpers_js_namespaceObject.removeExtension)(external_node_path_default().basename(remoteEntryPath));
107
+ const relativeRemoteEntryPath = (0, external_helpers_js_namespaceObject.toPosixPath)(external_node_path_default().relative(projectRoot, remoteEntryPath));
108
+ const requests = [
109
+ {
110
+ bundlePath: `${relativeTmpDirPath}/${remoteEntryName}.bundle`,
111
+ entryFile: `./${relativeRemoteEntryPath}`,
112
+ isContainer: true,
113
+ virtualModule: {
114
+ filePath: remoteEntryPath,
115
+ getCode: ()=>(0, external_generators_js_namespaceObject.getRemoteEntryModule)(federationConfig, {
116
+ projectDir: projectRoot,
117
+ tmpDir: tmpDirPath
118
+ })
119
+ }
120
+ }
121
+ ];
122
+ for (const exposePath of Object.values(federationConfig.exposes)){
123
+ const bundlePath = getBundlePathForSource(exposePath);
124
+ requests.push({
125
+ bundlePath,
126
+ entryFile: normalizeEntryFile(exposePath),
127
+ isContainer: false
128
+ });
129
+ }
130
+ for (const [sharedName, sharedConfig] of Object.entries(federationConfig.shared)){
131
+ if (sharedConfig.eager || false === sharedConfig.import) continue;
132
+ const sharedVirtualModulePath = (0, external_manifest_js_namespaceObject.getSharedVirtualModulePath)(tmpDirPath, sharedName);
133
+ const relativeSharedVirtualModulePath = (0, external_helpers_js_namespaceObject.toPosixPath)(external_node_path_default().relative(projectRoot, sharedVirtualModulePath));
134
+ const sharedImportName = getSharedImportName(sharedName, sharedConfig);
135
+ requests.push({
136
+ bundlePath: (0, external_helpers_js_namespaceObject.replaceExtension)(relativeSharedVirtualModulePath, '.bundle'),
137
+ entryFile: normalizeEntryFile(relativeSharedVirtualModulePath),
138
+ isContainer: false,
139
+ virtualModule: {
140
+ filePath: sharedVirtualModulePath,
141
+ getCode: ()=>(0, external_generators_js_namespaceObject.getRemoteModule)(sharedImportName)
142
+ }
143
+ });
144
+ }
145
+ return requests;
146
+ }
147
+ async function ensureVirtualModuleReady(virtualModule, vmManager) {
148
+ const code = virtualModule.getCode();
149
+ await external_node_fs_namespaceObject.promises.mkdir(external_node_path_default().dirname(virtualModule.filePath), {
150
+ recursive: true
151
+ });
152
+ await external_node_fs_namespaceObject.promises.writeFile(virtualModule.filePath, code, 'utf-8');
153
+ vmManager.registerVirtualModule(virtualModule.filePath, ()=>code);
154
+ }
155
+ function getBundleOptions(request, host, warmupOptions) {
156
+ const query = getBundleQuery(warmupOptions, request.isContainer);
157
+ const sourceUrl = getSourceUrl(host, request.bundlePath, query);
158
+ const bundleOptions = {
159
+ ...metro_compat_js_namespaceObject.Server.DEFAULT_BUNDLE_OPTIONS,
160
+ dev: warmupOptions.dev,
161
+ entryFile: request.entryFile,
162
+ excludeSource: warmupOptions.excludeSource,
163
+ lazy: true,
164
+ minify: warmupOptions.minify,
165
+ modulesOnly: !request.isContainer,
166
+ platform: warmupOptions.platform,
167
+ runModule: request.isContainer,
168
+ sourceMapUrl: sourceUrl.replace(/\.bundle(\?)/, '.map$1'),
169
+ sourceUrl
170
+ };
171
+ if (warmupOptions.sourcePaths) bundleOptions.sourcePaths = warmupOptions.sourcePaths;
172
+ return bundleOptions;
173
+ }
174
+ function getBundleQuery(warmupOptions, isContainer) {
175
+ const query = new URLSearchParams();
176
+ query.set('platform', warmupOptions.platform);
177
+ query.set('dev', String(warmupOptions.dev));
178
+ query.set('lazy', 'true');
179
+ query.set('minify', String(warmupOptions.minify));
180
+ query.set('runModule', String(isContainer));
181
+ query.set('modulesOnly', String(!isContainer));
182
+ if (warmupOptions.excludeSource) query.set('excludeSource', 'true');
183
+ if (warmupOptions.sourcePaths) query.set('sourcePaths', warmupOptions.sourcePaths);
184
+ return query;
185
+ }
186
+ function getSourceUrl(host, bundlePath, query) {
187
+ const normalizedBundlePath = bundlePath.startsWith('/') ? bundlePath : `/${bundlePath}`;
188
+ return `http://${host ?? 'localhost'}${normalizedBundlePath}?${query.toString()}`;
189
+ }
190
+ function getBundlePathForSource(sourcePath) {
191
+ const normalized = (0, external_helpers_js_namespaceObject.toPosixPath)(external_node_path_default().normalize(sourcePath));
192
+ const withoutPrefix = normalized.startsWith('./') ? normalized.slice(2) : normalized;
193
+ return (0, external_helpers_js_namespaceObject.replaceExtension)(withoutPrefix, '.bundle');
194
+ }
195
+ function normalizeEntryFile(sourcePath) {
196
+ const normalized = (0, external_helpers_js_namespaceObject.toPosixPath)(external_node_path_default().normalize(sourcePath));
197
+ return normalized.startsWith('./') ? normalized : `./${normalized}`;
198
+ }
199
+ function getSharedImportName(sharedName, sharedConfig) {
200
+ return 'string' == typeof sharedConfig.import ? sharedConfig.import : sharedName;
201
+ }
202
+ function getBoolean(params, key, defaultValue) {
203
+ const value = params.get(key);
204
+ if (null == value) return defaultValue;
205
+ return 'true' === value || '1' === value;
206
+ }
207
+ exports.createManifestMiddleware = __webpack_exports__.createManifestMiddleware;
208
+ for(var __webpack_i__ in __webpack_exports__)if (-1 === [
209
+ "createManifestMiddleware"
210
+ ].indexOf(__webpack_i__)) exports[__webpack_i__] = __webpack_exports__[__webpack_i__];
211
+ Object.defineProperty(exports, '__esModule', {
212
+ value: true
213
+ });
@@ -0,0 +1,171 @@
1
+ import 'module';
2
+ /*#__PURE__*/ import.meta.url;
3
+ import { promises } from "node:fs";
4
+ import node_path from "node:path";
5
+ import { Server } from "../utils/metro-compat.mjs";
6
+ import { MANIFEST_FILENAME } from "./constants.mjs";
7
+ import { getRemoteEntryModule, getRemoteModule } from "./generators.mjs";
8
+ import { removeExtension, replaceExtension, toPosixPath } from "./helpers.mjs";
9
+ import { getSharedVirtualModulePath } from "./manifest.mjs";
10
+ function createManifestMiddleware({ federationConfig, projectRoot, remoteEntryPath, tmpDirPath, vmManager }) {
11
+ const warmups = new Map();
12
+ return (middleware, metroServer)=>{
13
+ const nextMiddleware = middleware;
14
+ return async (req, res, next)=>{
15
+ try {
16
+ const warmupOptions = getManifestWarmupOptions(req.url);
17
+ if (warmupOptions) {
18
+ const warmupKey = JSON.stringify(warmupOptions);
19
+ let warmup = warmups.get(warmupKey);
20
+ if (!warmup) {
21
+ warmup = warmManifestBundles(metroServer, {
22
+ federationConfig,
23
+ host: req.headers.host,
24
+ projectRoot,
25
+ remoteEntryPath,
26
+ tmpDirPath,
27
+ vmManager,
28
+ warmupOptions
29
+ }).finally(()=>{
30
+ warmups.delete(warmupKey);
31
+ });
32
+ warmups.set(warmupKey, warmup);
33
+ }
34
+ await warmup;
35
+ }
36
+ return nextMiddleware(req, res, next);
37
+ } catch (error) {
38
+ next(error instanceof Error ? error : new Error(String(error)));
39
+ }
40
+ };
41
+ };
42
+ }
43
+ function getManifestWarmupOptions(rawUrl) {
44
+ const parsedUrl = new URL(rawUrl ?? '/', 'http://localhost');
45
+ if (parsedUrl.pathname !== `/${MANIFEST_FILENAME}`) return null;
46
+ const platform = parsedUrl.searchParams.get('platform');
47
+ if (!platform) return null;
48
+ return {
49
+ dev: getBoolean(parsedUrl.searchParams, 'dev', true),
50
+ excludeSource: getBoolean(parsedUrl.searchParams, 'excludeSource', false),
51
+ minify: getBoolean(parsedUrl.searchParams, 'minify', false),
52
+ platform,
53
+ sourcePaths: parsedUrl.searchParams.get('sourcePaths') ?? void 0
54
+ };
55
+ }
56
+ async function warmManifestBundles(metroServer, { federationConfig, host, projectRoot, remoteEntryPath, tmpDirPath, vmManager, warmupOptions }) {
57
+ for (const request of getBundleWarmupRequests({
58
+ federationConfig,
59
+ projectRoot,
60
+ remoteEntryPath,
61
+ tmpDirPath,
62
+ vmManager
63
+ })){
64
+ if (request.virtualModule) await ensureVirtualModuleReady(request.virtualModule, vmManager);
65
+ await metroServer.build(getBundleOptions(request, host, warmupOptions));
66
+ }
67
+ }
68
+ function getBundleWarmupRequests({ federationConfig, projectRoot, remoteEntryPath, tmpDirPath }) {
69
+ const relativeTmpDirPath = toPosixPath(node_path.relative(projectRoot, tmpDirPath));
70
+ const remoteEntryName = removeExtension(node_path.basename(remoteEntryPath));
71
+ const relativeRemoteEntryPath = toPosixPath(node_path.relative(projectRoot, remoteEntryPath));
72
+ const requests = [
73
+ {
74
+ bundlePath: `${relativeTmpDirPath}/${remoteEntryName}.bundle`,
75
+ entryFile: `./${relativeRemoteEntryPath}`,
76
+ isContainer: true,
77
+ virtualModule: {
78
+ filePath: remoteEntryPath,
79
+ getCode: ()=>getRemoteEntryModule(federationConfig, {
80
+ projectDir: projectRoot,
81
+ tmpDir: tmpDirPath
82
+ })
83
+ }
84
+ }
85
+ ];
86
+ for (const exposePath of Object.values(federationConfig.exposes)){
87
+ const bundlePath = getBundlePathForSource(exposePath);
88
+ requests.push({
89
+ bundlePath,
90
+ entryFile: normalizeEntryFile(exposePath),
91
+ isContainer: false
92
+ });
93
+ }
94
+ for (const [sharedName, sharedConfig] of Object.entries(federationConfig.shared)){
95
+ if (sharedConfig.eager || false === sharedConfig.import) continue;
96
+ const sharedVirtualModulePath = getSharedVirtualModulePath(tmpDirPath, sharedName);
97
+ const relativeSharedVirtualModulePath = toPosixPath(node_path.relative(projectRoot, sharedVirtualModulePath));
98
+ const sharedImportName = getSharedImportName(sharedName, sharedConfig);
99
+ requests.push({
100
+ bundlePath: replaceExtension(relativeSharedVirtualModulePath, '.bundle'),
101
+ entryFile: normalizeEntryFile(relativeSharedVirtualModulePath),
102
+ isContainer: false,
103
+ virtualModule: {
104
+ filePath: sharedVirtualModulePath,
105
+ getCode: ()=>getRemoteModule(sharedImportName)
106
+ }
107
+ });
108
+ }
109
+ return requests;
110
+ }
111
+ async function ensureVirtualModuleReady(virtualModule, vmManager) {
112
+ const code = virtualModule.getCode();
113
+ await promises.mkdir(node_path.dirname(virtualModule.filePath), {
114
+ recursive: true
115
+ });
116
+ await promises.writeFile(virtualModule.filePath, code, 'utf-8');
117
+ vmManager.registerVirtualModule(virtualModule.filePath, ()=>code);
118
+ }
119
+ function getBundleOptions(request, host, warmupOptions) {
120
+ const query = getBundleQuery(warmupOptions, request.isContainer);
121
+ const sourceUrl = getSourceUrl(host, request.bundlePath, query);
122
+ const bundleOptions = {
123
+ ...Server.DEFAULT_BUNDLE_OPTIONS,
124
+ dev: warmupOptions.dev,
125
+ entryFile: request.entryFile,
126
+ excludeSource: warmupOptions.excludeSource,
127
+ lazy: true,
128
+ minify: warmupOptions.minify,
129
+ modulesOnly: !request.isContainer,
130
+ platform: warmupOptions.platform,
131
+ runModule: request.isContainer,
132
+ sourceMapUrl: sourceUrl.replace(/\.bundle(\?)/, '.map$1'),
133
+ sourceUrl
134
+ };
135
+ if (warmupOptions.sourcePaths) bundleOptions.sourcePaths = warmupOptions.sourcePaths;
136
+ return bundleOptions;
137
+ }
138
+ function getBundleQuery(warmupOptions, isContainer) {
139
+ const query = new URLSearchParams();
140
+ query.set('platform', warmupOptions.platform);
141
+ query.set('dev', String(warmupOptions.dev));
142
+ query.set('lazy', 'true');
143
+ query.set('minify', String(warmupOptions.minify));
144
+ query.set('runModule', String(isContainer));
145
+ query.set('modulesOnly', String(!isContainer));
146
+ if (warmupOptions.excludeSource) query.set('excludeSource', 'true');
147
+ if (warmupOptions.sourcePaths) query.set('sourcePaths', warmupOptions.sourcePaths);
148
+ return query;
149
+ }
150
+ function getSourceUrl(host, bundlePath, query) {
151
+ const normalizedBundlePath = bundlePath.startsWith('/') ? bundlePath : `/${bundlePath}`;
152
+ return `http://${host ?? 'localhost'}${normalizedBundlePath}?${query.toString()}`;
153
+ }
154
+ function getBundlePathForSource(sourcePath) {
155
+ const normalized = toPosixPath(node_path.normalize(sourcePath));
156
+ const withoutPrefix = normalized.startsWith('./') ? normalized.slice(2) : normalized;
157
+ return replaceExtension(withoutPrefix, '.bundle');
158
+ }
159
+ function normalizeEntryFile(sourcePath) {
160
+ const normalized = toPosixPath(node_path.normalize(sourcePath));
161
+ return normalized.startsWith('./') ? normalized : `./${normalized}`;
162
+ }
163
+ function getSharedImportName(sharedName, sharedConfig) {
164
+ return 'string' == typeof sharedConfig.import ? sharedConfig.import : sharedName;
165
+ }
166
+ function getBoolean(params, key, defaultValue) {
167
+ const value = params.get(key);
168
+ if (null == value) return defaultValue;
169
+ return 'true' === value || '1' === value;
170
+ }
171
+ export { createManifestMiddleware };
@@ -1,3 +1,16 @@
1
1
  import type { ModuleFederationConfigNormalized } from '../types';
2
- export declare function createManifest(options: ModuleFederationConfigNormalized, mfMetroPath: string): string;
3
- export declare const updateManifest: (manifestPath: string, options: ModuleFederationConfigNormalized) => string;
2
+ export type BundleHashMap = Map<string, string>;
3
+ export type ManifestGenerationOptions = {
4
+ projectRoot?: string;
5
+ target?: 'development' | 'build';
6
+ tmpDirPath?: string;
7
+ };
8
+ export declare function createManifest(options: ModuleFederationConfigNormalized, mfMetroPath: string, hashesOrOptions?: BundleHashMap | ManifestGenerationOptions, manifestOptions?: ManifestGenerationOptions): string;
9
+ export declare function updateManifest(manifestPath: string, options: ModuleFederationConfigNormalized, hashesOrOptions?: BundleHashMap | ManifestGenerationOptions, manifestOptions?: ManifestGenerationOptions): string;
10
+ /**
11
+ * Compute SHA-256 of bundle code and store the hash for the matching
12
+ * manifest entry (container, exposed, or shared).
13
+ */
14
+ export declare function recordBundleHash(hashes: BundleHashMap, code: string, entryPoint: string, projectRoot: string, config: ModuleFederationConfigNormalized): void;
15
+ export declare function getSharedVirtualModuleName(sharedName: string): string;
16
+ export declare function getSharedVirtualModulePath(tmpDirPath: string, sharedName: string): string;
@@ -33,42 +33,62 @@ var __webpack_require__ = {};
33
33
  var __webpack_exports__ = {};
34
34
  __webpack_require__.r(__webpack_exports__);
35
35
  __webpack_require__.d(__webpack_exports__, {
36
+ getSharedVirtualModulePath: ()=>getSharedVirtualModulePath,
36
37
  createManifest: ()=>createManifest,
37
- updateManifest: ()=>updateManifest
38
+ recordBundleHash: ()=>recordBundleHash,
39
+ updateManifest: ()=>updateManifest,
40
+ getSharedVirtualModuleName: ()=>getSharedVirtualModuleName
38
41
  });
42
+ const external_node_crypto_namespaceObject = require("node:crypto");
43
+ var external_node_crypto_default = /*#__PURE__*/ __webpack_require__.n(external_node_crypto_namespaceObject);
39
44
  const external_node_fs_namespaceObject = require("node:fs");
40
45
  var external_node_fs_default = /*#__PURE__*/ __webpack_require__.n(external_node_fs_namespaceObject);
41
46
  const external_node_path_namespaceObject = require("node:path");
42
47
  var external_node_path_default = /*#__PURE__*/ __webpack_require__.n(external_node_path_namespaceObject);
43
48
  const external_constants_js_namespaceObject = require("./constants.js");
44
- function createManifest(options, mfMetroPath) {
49
+ const external_helpers_js_namespaceObject = require("./helpers.js");
50
+ function createManifest(options, mfMetroPath, hashesOrOptions, manifestOptions) {
51
+ const { hashes, options: generationOptions } = normalizeManifestArgs(hashesOrOptions, manifestOptions);
45
52
  const manifestPath = external_node_path_default().join(mfMetroPath, external_constants_js_namespaceObject.MANIFEST_FILENAME);
46
- const manifest = generateManifest(options);
53
+ const manifest = generateManifest(options, hashes, generationOptions);
47
54
  external_node_fs_default().writeFileSync(manifestPath, JSON.stringify(manifest, void 0, 2));
48
55
  return manifestPath;
49
56
  }
50
- const updateManifest = (manifestPath, options)=>{
51
- const manifest = generateManifest(options);
57
+ function updateManifest(manifestPath, options, hashesOrOptions, manifestOptions) {
58
+ var _existingManifest_metaData;
59
+ const { hashes, options: generationOptions } = normalizeManifestArgs(hashesOrOptions, manifestOptions);
60
+ const manifest = generateManifest(options, hashes, generationOptions);
61
+ const existingManifest = readManifest(manifestPath);
62
+ if (null == existingManifest ? void 0 : null == (_existingManifest_metaData = existingManifest.metaData) ? void 0 : _existingManifest_metaData.types) manifest.metaData.types = {
63
+ ...manifest.metaData.types,
64
+ ...existingManifest.metaData.types
65
+ };
52
66
  external_node_fs_default().writeFileSync(manifestPath, JSON.stringify(manifest, void 0, 2));
53
67
  return manifestPath;
54
- };
55
- function generateManifest(config) {
68
+ }
69
+ function recordBundleHash(hashes, code, entryPoint, projectRoot, config) {
70
+ const hash = external_node_crypto_default().createHash('sha256').update(code).digest('hex');
71
+ const key = resolveBundleKey(entryPoint, projectRoot, config);
72
+ if (key) hashes.set(key, hash);
73
+ }
74
+ function generateManifest(config, hashes, manifestOptions = {}) {
56
75
  return {
57
76
  id: config.name,
58
77
  name: config.name,
59
- metaData: generateMetaData(config),
60
- exposes: generateExposes(config),
78
+ metaData: generateMetaData(config, hashes),
79
+ exposes: generateExposes(config, hashes),
61
80
  remotes: generateRemotes(config),
62
- shared: generateShared(config)
81
+ shared: generateShared(config, hashes, manifestOptions)
63
82
  };
64
83
  }
65
- function generateMetaData(config) {
84
+ function generateMetaData(config, hashes) {
66
85
  return {
67
86
  name: config.name,
68
87
  type: 'app',
69
88
  buildInfo: {
70
89
  buildVersion: '1.0.0',
71
- buildName: config.name
90
+ buildName: config.name,
91
+ hash: (null == hashes ? void 0 : hashes.get(`container:${config.name}`)) ?? ''
72
92
  },
73
93
  remoteEntry: {
74
94
  name: config.filename,
@@ -86,7 +106,7 @@ function generateMetaData(config) {
86
106
  publicPath: 'auto'
87
107
  };
88
108
  }
89
- function generateExposes(config) {
109
+ function generateExposes(config, hashes) {
90
110
  return Object.keys(config.exposes).map((expose)=>{
91
111
  const formatKey = expose.replace('./', '');
92
112
  const assets = getEmptyAssets();
@@ -95,7 +115,8 @@ function generateExposes(config) {
95
115
  id: `${config.name}:${formatKey}`,
96
116
  name: formatKey,
97
117
  path: expose,
98
- assets
118
+ assets,
119
+ hash: (null == hashes ? void 0 : hashes.get(`expose:${formatKey}`)) ?? ''
99
120
  };
100
121
  });
101
122
  }
@@ -107,22 +128,86 @@ function generateRemotes(config) {
107
128
  entry: '*'
108
129
  }));
109
130
  }
110
- function generateShared(config) {
131
+ function generateShared(config, hashes, manifestOptions = {}) {
111
132
  return Object.keys(config.shared).map((sharedName)=>{
112
133
  const assets = getEmptyAssets();
113
134
  if (config.shared[sharedName].eager) assets.js.sync.push(config.filename);
114
- else if (false !== config.shared[sharedName].import) assets.js.sync.push(`shared/${sharedName}.bundle`);
135
+ else if (false !== config.shared[sharedName].import) assets.js.sync.push(getSharedAssetPath(sharedName, manifestOptions));
115
136
  return {
116
137
  id: sharedName,
117
138
  name: sharedName,
118
139
  version: getManifestVersion(config.shared[sharedName].version),
119
140
  requiredVersion: getManifestRequiredVersion(config.shared[sharedName].requiredVersion),
120
141
  singleton: config.shared[sharedName].singleton,
121
- hash: '',
142
+ hash: (null == hashes ? void 0 : hashes.get(`shared:${sharedName}`)) ?? '',
122
143
  assets
123
144
  };
124
145
  });
125
146
  }
147
+ function resolveBundleKey(entryPoint, projectRoot, config) {
148
+ const relPath = (0, external_helpers_js_namespaceObject.toPosixPath)(external_node_path_default().relative(projectRoot, entryPoint));
149
+ const normalizedRel = relPath.startsWith('./') ? relPath.slice(2) : relPath;
150
+ const normalizedRelNoExt = (0, external_helpers_js_namespaceObject.removeExtension)(normalizedRel);
151
+ for (const [exposeKey, exposePath] of Object.entries(config.exposes)){
152
+ const normalizedExpose = (0, external_helpers_js_namespaceObject.toPosixPath)(exposePath.startsWith('./') ? exposePath.slice(2) : exposePath);
153
+ if (normalizedRel === normalizedExpose || normalizedRelNoExt === (0, external_helpers_js_namespaceObject.removeExtension)(normalizedExpose)) return `expose:${exposeKey.replace('./', '')}`;
154
+ }
155
+ if ((0, external_helpers_js_namespaceObject.removeExtension)(external_node_path_default().basename(entryPoint)) === (0, external_helpers_js_namespaceObject.removeExtension)(external_node_path_default().basename(config.filename))) return `container:${config.name}`;
156
+ const virtualSharedKey = resolveDevVirtualSharedKey(entryPoint, projectRoot, config);
157
+ if (virtualSharedKey) return virtualSharedKey;
158
+ const absPath = (0, external_helpers_js_namespaceObject.toPosixPath)(external_node_path_default().resolve(entryPoint));
159
+ const nmMatch = absPath.match(/.*node_modules\/(.+)/);
160
+ if (nmMatch) {
161
+ const modulePath = (0, external_helpers_js_namespaceObject.removeExtension)(nmMatch[1]);
162
+ const sharedKey = findSharedKeyForModulePath(modulePath, config);
163
+ if (sharedKey) return `shared:${sharedKey}`;
164
+ }
165
+ return null;
166
+ }
167
+ function normalizeManifestArgs(hashesOrOptions, manifestOptions) {
168
+ if (hashesOrOptions instanceof Map) return {
169
+ hashes: hashesOrOptions,
170
+ options: manifestOptions ?? {}
171
+ };
172
+ return {
173
+ hashes: void 0,
174
+ options: hashesOrOptions ?? {}
175
+ };
176
+ }
177
+ function readManifest(manifestPath) {
178
+ if (!external_node_fs_default().existsSync(manifestPath)) return;
179
+ return JSON.parse(external_node_fs_default().readFileSync(manifestPath, 'utf-8'));
180
+ }
181
+ function getSharedVirtualModuleName(sharedName) {
182
+ return sharedName.replaceAll('/', '_');
183
+ }
184
+ function getSharedVirtualModulePath(tmpDirPath, sharedName) {
185
+ return external_node_path_default().join(tmpDirPath, 'shared', `${getSharedVirtualModuleName(sharedName)}.js`);
186
+ }
187
+ function getSharedAssetPath(sharedName, manifestOptions) {
188
+ if ('development' === manifestOptions.target && manifestOptions.projectRoot && manifestOptions.tmpDirPath) return (0, external_helpers_js_namespaceObject.toPosixPath)(external_node_path_default().relative(manifestOptions.projectRoot, getSharedVirtualModulePath(manifestOptions.tmpDirPath, sharedName)));
189
+ return `shared/${sharedName}.bundle`;
190
+ }
191
+ function resolveDevVirtualSharedKey(entryPoint, projectRoot, config) {
192
+ const relativePath = (0, external_helpers_js_namespaceObject.toPosixPath)(external_node_path_default().relative(projectRoot, entryPoint));
193
+ const virtualSharedPrefix = `node_modules/${external_constants_js_namespaceObject.TMP_DIR_NAME}/shared/`;
194
+ if (!relativePath.startsWith(virtualSharedPrefix)) return null;
195
+ const virtualModuleName = (0, external_helpers_js_namespaceObject.removeExtension)(relativePath.slice(virtualSharedPrefix.length));
196
+ const sharedKey = Object.keys(config.shared).find((sharedName)=>getSharedVirtualModuleName(sharedName) === virtualModuleName);
197
+ return sharedKey ? `shared:${sharedKey}` : null;
198
+ }
199
+ function findSharedKeyForModulePath(modulePath, config) {
200
+ const sharedEntries = Object.entries(config.shared).map(([sharedName, sharedConfig])=>{
201
+ const importName = 'string' == typeof sharedConfig.import ? sharedConfig.import : sharedName;
202
+ return {
203
+ importName,
204
+ sharedName
205
+ };
206
+ }).sort((a, b)=>b.importName.length - a.importName.length);
207
+ const normalizedModulePath = (0, external_helpers_js_namespaceObject.toPosixPath)(modulePath);
208
+ for (const { importName, sharedName } of sharedEntries)if (normalizedModulePath === importName || normalizedModulePath.startsWith(`${importName}/`)) return sharedName;
209
+ return null;
210
+ }
126
211
  function getManifestVersion(version) {
127
212
  return 'string' == typeof version ? version : '';
128
213
  }
@@ -142,9 +227,15 @@ function getEmptyAssets() {
142
227
  };
143
228
  }
144
229
  exports.createManifest = __webpack_exports__.createManifest;
230
+ exports.getSharedVirtualModuleName = __webpack_exports__.getSharedVirtualModuleName;
231
+ exports.getSharedVirtualModulePath = __webpack_exports__.getSharedVirtualModulePath;
232
+ exports.recordBundleHash = __webpack_exports__.recordBundleHash;
145
233
  exports.updateManifest = __webpack_exports__.updateManifest;
146
234
  for(var __webpack_i__ in __webpack_exports__)if (-1 === [
147
235
  "createManifest",
236
+ "getSharedVirtualModuleName",
237
+ "getSharedVirtualModulePath",
238
+ "recordBundleHash",
148
239
  "updateManifest"
149
240
  ].indexOf(__webpack_i__)) exports[__webpack_i__] = __webpack_exports__[__webpack_i__];
150
241
  Object.defineProperty(exports, '__esModule', {
@@ -1,36 +1,52 @@
1
1
  import 'module';
2
2
  /*#__PURE__*/ import.meta.url;
3
+ import node_crypto from "node:crypto";
3
4
  import node_fs from "node:fs";
4
5
  import node_path from "node:path";
5
- import { MANIFEST_FILENAME } from "./constants.mjs";
6
- function createManifest(options, mfMetroPath) {
6
+ import { MANIFEST_FILENAME, TMP_DIR_NAME } from "./constants.mjs";
7
+ import { removeExtension, toPosixPath } from "./helpers.mjs";
8
+ function createManifest(options, mfMetroPath, hashesOrOptions, manifestOptions) {
9
+ const { hashes, options: generationOptions } = normalizeManifestArgs(hashesOrOptions, manifestOptions);
7
10
  const manifestPath = node_path.join(mfMetroPath, MANIFEST_FILENAME);
8
- const manifest = generateManifest(options);
11
+ const manifest = generateManifest(options, hashes, generationOptions);
9
12
  node_fs.writeFileSync(manifestPath, JSON.stringify(manifest, void 0, 2));
10
13
  return manifestPath;
11
14
  }
12
- const updateManifest = (manifestPath, options)=>{
13
- const manifest = generateManifest(options);
15
+ function updateManifest(manifestPath, options, hashesOrOptions, manifestOptions) {
16
+ var _existingManifest_metaData;
17
+ const { hashes, options: generationOptions } = normalizeManifestArgs(hashesOrOptions, manifestOptions);
18
+ const manifest = generateManifest(options, hashes, generationOptions);
19
+ const existingManifest = readManifest(manifestPath);
20
+ if (null == existingManifest ? void 0 : null == (_existingManifest_metaData = existingManifest.metaData) ? void 0 : _existingManifest_metaData.types) manifest.metaData.types = {
21
+ ...manifest.metaData.types,
22
+ ...existingManifest.metaData.types
23
+ };
14
24
  node_fs.writeFileSync(manifestPath, JSON.stringify(manifest, void 0, 2));
15
25
  return manifestPath;
16
- };
17
- function generateManifest(config) {
26
+ }
27
+ function recordBundleHash(hashes, code, entryPoint, projectRoot, config) {
28
+ const hash = node_crypto.createHash('sha256').update(code).digest('hex');
29
+ const key = resolveBundleKey(entryPoint, projectRoot, config);
30
+ if (key) hashes.set(key, hash);
31
+ }
32
+ function generateManifest(config, hashes, manifestOptions = {}) {
18
33
  return {
19
34
  id: config.name,
20
35
  name: config.name,
21
- metaData: generateMetaData(config),
22
- exposes: generateExposes(config),
36
+ metaData: generateMetaData(config, hashes),
37
+ exposes: generateExposes(config, hashes),
23
38
  remotes: generateRemotes(config),
24
- shared: generateShared(config)
39
+ shared: generateShared(config, hashes, manifestOptions)
25
40
  };
26
41
  }
27
- function generateMetaData(config) {
42
+ function generateMetaData(config, hashes) {
28
43
  return {
29
44
  name: config.name,
30
45
  type: 'app',
31
46
  buildInfo: {
32
47
  buildVersion: '1.0.0',
33
- buildName: config.name
48
+ buildName: config.name,
49
+ hash: (null == hashes ? void 0 : hashes.get(`container:${config.name}`)) ?? ''
34
50
  },
35
51
  remoteEntry: {
36
52
  name: config.filename,
@@ -48,7 +64,7 @@ function generateMetaData(config) {
48
64
  publicPath: 'auto'
49
65
  };
50
66
  }
51
- function generateExposes(config) {
67
+ function generateExposes(config, hashes) {
52
68
  return Object.keys(config.exposes).map((expose)=>{
53
69
  const formatKey = expose.replace('./', '');
54
70
  const assets = getEmptyAssets();
@@ -57,7 +73,8 @@ function generateExposes(config) {
57
73
  id: `${config.name}:${formatKey}`,
58
74
  name: formatKey,
59
75
  path: expose,
60
- assets
76
+ assets,
77
+ hash: (null == hashes ? void 0 : hashes.get(`expose:${formatKey}`)) ?? ''
61
78
  };
62
79
  });
63
80
  }
@@ -69,22 +86,86 @@ function generateRemotes(config) {
69
86
  entry: '*'
70
87
  }));
71
88
  }
72
- function generateShared(config) {
89
+ function generateShared(config, hashes, manifestOptions = {}) {
73
90
  return Object.keys(config.shared).map((sharedName)=>{
74
91
  const assets = getEmptyAssets();
75
92
  if (config.shared[sharedName].eager) assets.js.sync.push(config.filename);
76
- else if (false !== config.shared[sharedName].import) assets.js.sync.push(`shared/${sharedName}.bundle`);
93
+ else if (false !== config.shared[sharedName].import) assets.js.sync.push(getSharedAssetPath(sharedName, manifestOptions));
77
94
  return {
78
95
  id: sharedName,
79
96
  name: sharedName,
80
97
  version: getManifestVersion(config.shared[sharedName].version),
81
98
  requiredVersion: getManifestRequiredVersion(config.shared[sharedName].requiredVersion),
82
99
  singleton: config.shared[sharedName].singleton,
83
- hash: '',
100
+ hash: (null == hashes ? void 0 : hashes.get(`shared:${sharedName}`)) ?? '',
84
101
  assets
85
102
  };
86
103
  });
87
104
  }
105
+ function resolveBundleKey(entryPoint, projectRoot, config) {
106
+ const relPath = toPosixPath(node_path.relative(projectRoot, entryPoint));
107
+ const normalizedRel = relPath.startsWith('./') ? relPath.slice(2) : relPath;
108
+ const normalizedRelNoExt = removeExtension(normalizedRel);
109
+ for (const [exposeKey, exposePath] of Object.entries(config.exposes)){
110
+ const normalizedExpose = toPosixPath(exposePath.startsWith('./') ? exposePath.slice(2) : exposePath);
111
+ if (normalizedRel === normalizedExpose || normalizedRelNoExt === removeExtension(normalizedExpose)) return `expose:${exposeKey.replace('./', '')}`;
112
+ }
113
+ if (removeExtension(node_path.basename(entryPoint)) === removeExtension(node_path.basename(config.filename))) return `container:${config.name}`;
114
+ const virtualSharedKey = resolveDevVirtualSharedKey(entryPoint, projectRoot, config);
115
+ if (virtualSharedKey) return virtualSharedKey;
116
+ const absPath = toPosixPath(node_path.resolve(entryPoint));
117
+ const nmMatch = absPath.match(/.*node_modules\/(.+)/);
118
+ if (nmMatch) {
119
+ const modulePath = removeExtension(nmMatch[1]);
120
+ const sharedKey = findSharedKeyForModulePath(modulePath, config);
121
+ if (sharedKey) return `shared:${sharedKey}`;
122
+ }
123
+ return null;
124
+ }
125
+ function normalizeManifestArgs(hashesOrOptions, manifestOptions) {
126
+ if (hashesOrOptions instanceof Map) return {
127
+ hashes: hashesOrOptions,
128
+ options: manifestOptions ?? {}
129
+ };
130
+ return {
131
+ hashes: void 0,
132
+ options: hashesOrOptions ?? {}
133
+ };
134
+ }
135
+ function readManifest(manifestPath) {
136
+ if (!node_fs.existsSync(manifestPath)) return;
137
+ return JSON.parse(node_fs.readFileSync(manifestPath, 'utf-8'));
138
+ }
139
+ function getSharedVirtualModuleName(sharedName) {
140
+ return sharedName.replaceAll('/', '_');
141
+ }
142
+ function getSharedVirtualModulePath(tmpDirPath, sharedName) {
143
+ return node_path.join(tmpDirPath, 'shared', `${getSharedVirtualModuleName(sharedName)}.js`);
144
+ }
145
+ function getSharedAssetPath(sharedName, manifestOptions) {
146
+ if ('development' === manifestOptions.target && manifestOptions.projectRoot && manifestOptions.tmpDirPath) return toPosixPath(node_path.relative(manifestOptions.projectRoot, getSharedVirtualModulePath(manifestOptions.tmpDirPath, sharedName)));
147
+ return `shared/${sharedName}.bundle`;
148
+ }
149
+ function resolveDevVirtualSharedKey(entryPoint, projectRoot, config) {
150
+ const relativePath = toPosixPath(node_path.relative(projectRoot, entryPoint));
151
+ const virtualSharedPrefix = `node_modules/${TMP_DIR_NAME}/shared/`;
152
+ if (!relativePath.startsWith(virtualSharedPrefix)) return null;
153
+ const virtualModuleName = removeExtension(relativePath.slice(virtualSharedPrefix.length));
154
+ const sharedKey = Object.keys(config.shared).find((sharedName)=>getSharedVirtualModuleName(sharedName) === virtualModuleName);
155
+ return sharedKey ? `shared:${sharedKey}` : null;
156
+ }
157
+ function findSharedKeyForModulePath(modulePath, config) {
158
+ const sharedEntries = Object.entries(config.shared).map(([sharedName, sharedConfig])=>{
159
+ const importName = 'string' == typeof sharedConfig.import ? sharedConfig.import : sharedName;
160
+ return {
161
+ importName,
162
+ sharedName
163
+ };
164
+ }).sort((a, b)=>b.importName.length - a.importName.length);
165
+ const normalizedModulePath = toPosixPath(modulePath);
166
+ for (const { importName, sharedName } of sharedEntries)if (normalizedModulePath === importName || normalizedModulePath.startsWith(`${importName}/`)) return sharedName;
167
+ return null;
168
+ }
88
169
  function getManifestVersion(version) {
89
170
  return 'string' == typeof version ? version : '';
90
171
  }
@@ -103,4 +184,4 @@ function getEmptyAssets() {
103
184
  }
104
185
  };
105
186
  }
106
- export { createManifest, updateManifest };
187
+ export { createManifest, getSharedVirtualModuleName, getSharedVirtualModulePath, recordBundleHash, updateManifest };
@@ -19,7 +19,7 @@ interface CreateResolveRequestOptions {
19
19
  tmpDir: string;
20
20
  };
21
21
  options: ModuleFederationConfigNormalized;
22
- vmManager: VirtualModuleManager;
22
+ vmManager: Pick<VirtualModuleManager, 'registerVirtualModule'>;
23
23
  customResolver?: CustomResolver;
24
24
  }
25
25
  export declare function createResolveRequest({ vmManager, options, hacks, paths, isRemote, customResolver, }: CreateResolveRequestOptions): CustomResolver;
@@ -128,8 +128,8 @@ function createResolveRequest({ vmManager, options, hacks, paths, isRemote, cust
128
128
  for (const sharedName of Object.keys(options.shared)){
129
129
  const importName = options.shared[sharedName].import || sharedName;
130
130
  if (moduleName === importName) {
131
- const sharedPath = getSharedPath(moduleName, paths.tmpDir);
132
- const sharedGenerator = ()=>(0, external_generators_js_namespaceObject.getRemoteModule)(moduleName);
131
+ const sharedPath = getSharedPath(sharedName, paths.tmpDir);
132
+ const sharedGenerator = ()=>(0, external_generators_js_namespaceObject.getRemoteModule)(importName);
133
133
  vmManager.registerVirtualModule(sharedPath, sharedGenerator);
134
134
  return {
135
135
  type: 'sourceFile',
@@ -95,8 +95,8 @@ function createResolveRequest({ vmManager, options, hacks, paths, isRemote, cust
95
95
  for (const sharedName of Object.keys(options.shared)){
96
96
  const importName = options.shared[sharedName].import || sharedName;
97
97
  if (moduleName === importName) {
98
- const sharedPath = getSharedPath(moduleName, paths.tmpDir);
99
- const sharedGenerator = ()=>getRemoteModule(moduleName);
98
+ const sharedPath = getSharedPath(sharedName, paths.tmpDir);
99
+ const sharedGenerator = ()=>getRemoteModule(importName);
100
100
  vmManager.registerVirtualModule(sharedPath, sharedGenerator);
101
101
  return {
102
102
  type: 'sourceFile',
@@ -1,5 +1,6 @@
1
1
  import type { SerializerConfigT } from 'metro-config';
2
2
  import type { ModuleFederationConfigNormalized } from '../types';
3
+ import { type ManifestGenerationOptions } from './manifest';
3
4
  type CustomSerializer = SerializerConfigT['customSerializer'];
4
- export declare function getModuleFederationSerializer(mfConfig: ModuleFederationConfigNormalized, isUsingMFBundleCommand: boolean): CustomSerializer;
5
+ export declare function getModuleFederationSerializer(mfConfig: ModuleFederationConfigNormalized, isUsingMFBundleCommand: boolean, manifestPath?: string, manifestOptions?: ManifestGenerationOptions): CustomSerializer;
5
6
  export {};
@@ -39,31 +39,39 @@ const external_node_path_namespaceObject = require("node:path");
39
39
  var external_node_path_default = /*#__PURE__*/ __webpack_require__.n(external_node_path_namespaceObject);
40
40
  const errors_js_namespaceObject = require("../utils/errors.js");
41
41
  const external_helpers_js_namespaceObject = require("./helpers.js");
42
+ const external_manifest_js_namespaceObject = require("./manifest.js");
42
43
  const metro_compat_js_namespaceObject = require("../utils/metro-compat.js");
43
- function getModuleFederationSerializer(mfConfig, isUsingMFBundleCommand) {
44
+ function getModuleFederationSerializer(mfConfig, isUsingMFBundleCommand, manifestPath, manifestOptions) {
45
+ const bundleHashes = new Map();
44
46
  return async (entryPoint, preModules, graph, options)=>{
45
47
  const syncRemoteModules = collectSyncRemoteModules(graph, mfConfig.remotes);
46
48
  const syncSharedModules = collectSyncSharedModules(graph, mfConfig.shared);
49
+ let code;
47
50
  if (true === options.runModule) {
48
51
  const finalPreModules = [
49
52
  getEarlyShared(syncSharedModules),
50
53
  getEarlyRemotes(syncRemoteModules),
51
54
  ...preModules
52
55
  ];
53
- return getBundleCode(entryPoint, finalPreModules, graph, options);
56
+ code = getBundleCode(entryPoint, finalPreModules, graph, options);
57
+ } else if (isProjectSource(entryPoint, options.projectRoot)) {
58
+ const bundlePath = getBundlePath(entryPoint, options.projectRoot, mfConfig.exposes, isUsingMFBundleCommand);
59
+ const finalPreModules = [
60
+ getSyncShared(syncSharedModules, bundlePath, mfConfig.name),
61
+ getSyncRemotes(syncRemoteModules, bundlePath, mfConfig.name)
62
+ ];
63
+ if (false === options.modulesOnly) finalPreModules.push(...preModules);
64
+ const finalOptions = {
65
+ ...options,
66
+ modulesOnly: false
67
+ };
68
+ code = getBundleCode(entryPoint, finalPreModules, graph, finalOptions);
69
+ } else code = getBundleCode(entryPoint, preModules, graph, options);
70
+ if (manifestPath) {
71
+ (0, external_manifest_js_namespaceObject.recordBundleHash)(bundleHashes, code, entryPoint, options.projectRoot, mfConfig);
72
+ (0, external_manifest_js_namespaceObject.updateManifest)(manifestPath, mfConfig, bundleHashes, manifestOptions);
54
73
  }
55
- if (!isProjectSource(entryPoint, options.projectRoot)) return getBundleCode(entryPoint, preModules, graph, options);
56
- const bundlePath = getBundlePath(entryPoint, options.projectRoot, mfConfig.exposes, isUsingMFBundleCommand);
57
- const finalPreModules = [
58
- getSyncShared(syncSharedModules, bundlePath, mfConfig.name),
59
- getSyncRemotes(syncRemoteModules, bundlePath, mfConfig.name)
60
- ];
61
- if (false === options.modulesOnly) finalPreModules.push(...preModules);
62
- const finalOptions = {
63
- ...options,
64
- modulesOnly: false
65
- };
66
- return getBundleCode(entryPoint, finalPreModules, graph, finalOptions);
74
+ return code;
67
75
  };
68
76
  }
69
77
  function collectSyncRemoteModules(graph, _remotes) {
@@ -3,31 +3,39 @@ import 'module';
3
3
  import node_path from "node:path";
4
4
  import { ConfigError } from "../utils/errors.mjs";
5
5
  import { toPosixPath } from "./helpers.mjs";
6
+ import { recordBundleHash, updateManifest } from "./manifest.mjs";
6
7
  import { CountingSet, baseJSBundle, bundleToString } from "../utils/metro-compat.mjs";
7
- function getModuleFederationSerializer(mfConfig, isUsingMFBundleCommand) {
8
+ function getModuleFederationSerializer(mfConfig, isUsingMFBundleCommand, manifestPath, manifestOptions) {
9
+ const bundleHashes = new Map();
8
10
  return async (entryPoint, preModules, graph, options)=>{
9
11
  const syncRemoteModules = collectSyncRemoteModules(graph, mfConfig.remotes);
10
12
  const syncSharedModules = collectSyncSharedModules(graph, mfConfig.shared);
13
+ let code;
11
14
  if (true === options.runModule) {
12
15
  const finalPreModules = [
13
16
  getEarlyShared(syncSharedModules),
14
17
  getEarlyRemotes(syncRemoteModules),
15
18
  ...preModules
16
19
  ];
17
- return getBundleCode(entryPoint, finalPreModules, graph, options);
20
+ code = getBundleCode(entryPoint, finalPreModules, graph, options);
21
+ } else if (isProjectSource(entryPoint, options.projectRoot)) {
22
+ const bundlePath = getBundlePath(entryPoint, options.projectRoot, mfConfig.exposes, isUsingMFBundleCommand);
23
+ const finalPreModules = [
24
+ getSyncShared(syncSharedModules, bundlePath, mfConfig.name),
25
+ getSyncRemotes(syncRemoteModules, bundlePath, mfConfig.name)
26
+ ];
27
+ if (false === options.modulesOnly) finalPreModules.push(...preModules);
28
+ const finalOptions = {
29
+ ...options,
30
+ modulesOnly: false
31
+ };
32
+ code = getBundleCode(entryPoint, finalPreModules, graph, finalOptions);
33
+ } else code = getBundleCode(entryPoint, preModules, graph, options);
34
+ if (manifestPath) {
35
+ recordBundleHash(bundleHashes, code, entryPoint, options.projectRoot, mfConfig);
36
+ updateManifest(manifestPath, mfConfig, bundleHashes, manifestOptions);
18
37
  }
19
- if (!isProjectSource(entryPoint, options.projectRoot)) return getBundleCode(entryPoint, preModules, graph, options);
20
- const bundlePath = getBundlePath(entryPoint, options.projectRoot, mfConfig.exposes, isUsingMFBundleCommand);
21
- const finalPreModules = [
22
- getSyncShared(syncSharedModules, bundlePath, mfConfig.name),
23
- getSyncRemotes(syncRemoteModules, bundlePath, mfConfig.name)
24
- ];
25
- if (false === options.modulesOnly) finalPreModules.push(...preModules);
26
- const finalOptions = {
27
- ...options,
28
- modulesOnly: false
29
- };
30
- return getBundleCode(entryPoint, finalPreModules, graph, finalOptions);
38
+ return code;
31
39
  };
32
40
  }
33
41
  function collectSyncRemoteModules(graph, _remotes) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@module-federation/metro",
3
- "version": "2.3.3",
3
+ "version": "2.5.0",
4
4
  "description": "Module Federation for Metro bundler",
5
5
  "keywords": [
6
6
  "module-federation",
@@ -57,9 +57,9 @@
57
57
  },
58
58
  "dependencies": {
59
59
  "@expo/metro-runtime": "^5.0.4",
60
- "@module-federation/dts-plugin": "2.3.3",
61
- "@module-federation/runtime": "2.3.3",
62
- "@module-federation/sdk": "2.3.3"
60
+ "@module-federation/dts-plugin": "2.5.0",
61
+ "@module-federation/runtime": "2.5.0",
62
+ "@module-federation/sdk": "2.5.0"
63
63
  },
64
64
  "peerDependencies": {
65
65
  "@babel/types": "^7.25.0",