@softarc/native-federation-runtime 3.5.0 → 4.0.0-RC1

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.
Files changed (35) hide show
  1. package/README.md +6 -2
  2. package/dist/index.d.ts +6 -0
  3. package/dist/index.d.ts.map +1 -0
  4. package/dist/index.js +320 -0
  5. package/dist/lib/get-shared.d.ts +20 -0
  6. package/dist/lib/get-shared.d.ts.map +1 -0
  7. package/dist/lib/init-federation.d.ts +105 -0
  8. package/dist/lib/init-federation.d.ts.map +1 -0
  9. package/dist/lib/load-remote-module.d.ts +83 -0
  10. package/dist/lib/load-remote-module.d.ts.map +1 -0
  11. package/dist/lib/model/build-notifications-options.d.ts +2 -0
  12. package/dist/lib/model/build-notifications-options.d.ts.map +1 -0
  13. package/dist/lib/model/externals.d.ts +4 -0
  14. package/dist/lib/model/externals.d.ts.map +1 -0
  15. package/dist/lib/model/federation-info.d.ts +7 -0
  16. package/dist/lib/model/federation-info.d.ts.map +1 -0
  17. package/dist/lib/model/global-cache.d.ts +12 -0
  18. package/dist/lib/model/global-cache.d.ts.map +1 -0
  19. package/dist/lib/model/import-map.d.ts +8 -0
  20. package/dist/lib/model/import-map.d.ts.map +1 -0
  21. package/dist/lib/model/remotes.d.ts +10 -0
  22. package/dist/lib/model/remotes.d.ts.map +1 -0
  23. package/dist/lib/utils/add-import-map.d.ts +3 -0
  24. package/dist/lib/utils/add-import-map.d.ts.map +1 -0
  25. package/dist/lib/utils/path-utils.d.ts +14 -0
  26. package/dist/lib/utils/path-utils.d.ts.map +1 -0
  27. package/dist/lib/utils/trusted-types.d.ts +2 -0
  28. package/dist/lib/utils/trusted-types.d.ts.map +1 -0
  29. package/dist/lib/watch-federation-build.d.ts +10 -0
  30. package/dist/lib/watch-federation-build.d.ts.map +1 -0
  31. package/package.json +35 -16
  32. package/LICENSE +0 -8
  33. package/fesm2022/softarc-native-federation-runtime.mjs +0 -568
  34. package/fesm2022/softarc-native-federation-runtime.mjs.map +0 -1
  35. package/index.d.ts +0 -230
@@ -0,0 +1 @@
1
+ {"version":3,"file":"path-utils.d.ts","sourceRoot":"","sources":["../../../src/lib/utils/path-utils.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,UAIvC;AAED;;;;;GAKG;AACH,wBAAgB,SAAS,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,UASrD"}
@@ -0,0 +1,2 @@
1
+ export declare function tryCreateTrustedScript(script: string): string | TrustedScript;
2
+ //# sourceMappingURL=trusted-types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"trusted-types.d.ts","sourceRoot":"","sources":["../../../src/lib/utils/trusted-types.ts"],"names":[],"mappings":"AA2BA,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,aAAa,CAE7E"}
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Watches for federation build completion events and automatically reloads the page.
3
+ *
4
+ * This function establishes a Server-Sent Events (SSE) connection to listen for
5
+ * 'federation-rebuild-complete' notifications. When a build completes successfully,
6
+ * it triggers a page reload to reflect the latest changes.
7
+ * @param endpoint - The SSE endpoint URL to watch for build notifications.
8
+ */
9
+ export declare function watchFederationBuildCompletion(endpoint: string): void;
10
+ //# sourceMappingURL=watch-federation-build.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"watch-federation-build.d.ts","sourceRoot":"","sources":["../../src/lib/watch-federation-build.ts"],"names":[],"mappings":"AAEA;;;;;;;GAOG;AACH,wBAAgB,8BAA8B,CAAC,QAAQ,EAAE,MAAM,QAc9D"}
package/package.json CHANGED
@@ -1,24 +1,43 @@
1
1
  {
2
2
  "name": "@softarc/native-federation-runtime",
3
- "version": "3.5.0",
3
+ "version": "4.0.0-RC1",
4
+ "type": "module",
5
+ "license": "MIT",
6
+ "main": "./dist/index.js",
7
+ "module": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ "./package.json": "./package.json",
11
+ ".": {
12
+ "types": "./dist/index.d.ts",
13
+ "import": "./dist/index.js",
14
+ "default": "./dist/index.js"
15
+ }
16
+ },
17
+ "files": [
18
+ "dist",
19
+ "!**/*.tsbuildinfo"
20
+ ],
4
21
  "dependencies": {
5
- "tslib": "^2.3.0"
22
+ "@softarc/native-federation": "4.0.0-RC1"
23
+ },
24
+ "devDependencies": {
25
+ "@types/node": "^22.5.4",
26
+ "vitest": "^3.0.0",
27
+ "@vitest/ui": "^3.0.0",
28
+ "@types/trusted-types": "^2.0.7"
29
+ },
30
+ "scripts": {
31
+ "test": "vitest run",
32
+ "test:watch": "vitest",
33
+ "test:ui": "vitest --ui",
34
+ "test:coverage": "vitest run --coverage",
35
+ "test:unit": "vitest run --project=unit",
36
+ "test:integration": "vitest run --project=integration"
6
37
  },
7
38
  "msw": {
8
39
  "workerDirectory": [
9
40
  "public"
10
41
  ]
11
- },
12
- "module": "fesm2022/softarc-native-federation-runtime.mjs",
13
- "typings": "index.d.ts",
14
- "exports": {
15
- "./package.json": {
16
- "default": "./package.json"
17
- },
18
- ".": {
19
- "types": "./index.d.ts",
20
- "default": "./fesm2022/softarc-native-federation-runtime.mjs"
21
- }
22
- },
23
- "sideEffects": false
24
- }
42
+ }
43
+ }
package/LICENSE DELETED
@@ -1,8 +0,0 @@
1
- Copyright 2021 Softarc Consulting GmbH
2
-
3
- Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4
-
5
- The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6
-
7
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
8
-
@@ -1,568 +0,0 @@
1
- const nfNamespace = '__NATIVE_FEDERATION__';
2
- const global = globalThis;
3
- global[nfNamespace] ??= {
4
- externals: new Map(),
5
- remoteNamesToRemote: new Map(),
6
- baseUrlToRemoteNames: new Map(),
7
- };
8
- const globalCache = global[nfNamespace];
9
-
10
- const externals = globalCache.externals;
11
- function getExternalKey(shared) {
12
- return `${shared.packageName}@${shared.version}`;
13
- }
14
- function getExternalUrl(shared) {
15
- const packageKey = getExternalKey(shared);
16
- return externals.get(packageKey);
17
- }
18
- function setExternalUrl(shared, url) {
19
- const packageKey = getExternalKey(shared);
20
- externals.set(packageKey, url);
21
- }
22
-
23
- function mergeImportMaps(map1, map2) {
24
- return {
25
- imports: { ...map1.imports, ...map2.imports },
26
- scopes: { ...map1.scopes, ...map2.scopes },
27
- };
28
- }
29
-
30
- const remoteNamesToRemote = globalCache.remoteNamesToRemote;
31
- const baseUrlToRemoteNames = globalCache.baseUrlToRemoteNames;
32
- function addRemote(remoteName, remote) {
33
- remoteNamesToRemote.set(remoteName, remote);
34
- baseUrlToRemoteNames.set(remote.baseUrl, remoteName);
35
- }
36
- function getRemoteNameByBaseUrl(baseUrl) {
37
- return baseUrlToRemoteNames.get(baseUrl);
38
- }
39
- function isRemoteInitialized(baseUrl) {
40
- return baseUrlToRemoteNames.has(baseUrl);
41
- }
42
- function getRemote(remoteName) {
43
- return remoteNamesToRemote.get(remoteName);
44
- }
45
- function hasRemote(remoteName) {
46
- return remoteNamesToRemote.has(remoteName);
47
- }
48
-
49
- function appendImportMap(importMap) {
50
- document.head.appendChild(Object.assign(document.createElement('script'), {
51
- type: 'importmap-shim',
52
- innerHTML: JSON.stringify(importMap),
53
- }));
54
- }
55
-
56
- /**
57
- * Returns the full directory of a given path.
58
- * @param url - The path to get the directory of.
59
- * @returns The full directory of the path.
60
- */
61
- function getDirectory(url) {
62
- const parts = url.split('/');
63
- parts.pop();
64
- return parts.join('/');
65
- }
66
- /**
67
- * Joins two paths together taking into account trailing slashes and "./" prefixes.
68
- * @param path1 - The first path to join.
69
- * @param path2 - The second path to join.
70
- * @returns The joined path.
71
- */
72
- function joinPaths(path1, path2) {
73
- while (path1.endsWith('/')) {
74
- path1 = path1.substring(0, path1.length - 1);
75
- }
76
- if (path2.startsWith('./')) {
77
- path2 = path2.substring(2, path2.length);
78
- }
79
- return `${path1}/${path2}`;
80
- }
81
-
82
- const BUILD_NOTIFICATIONS_ENDPOINT = '/@angular-architects/native-federation:build-notifications';
83
- var BuildNotificationType;
84
- (function (BuildNotificationType) {
85
- BuildNotificationType["COMPLETED"] = "federation-rebuild-complete";
86
- BuildNotificationType["ERROR"] = "federation-rebuild-error";
87
- BuildNotificationType["CANCELLED"] = "federation-rebuild-cancelled";
88
- })(BuildNotificationType || (BuildNotificationType = {}));
89
-
90
- /**
91
- * Watches for federation build completion events and automatically reloads the page.
92
- *
93
- * This function establishes a Server-Sent Events (SSE) connection to listen for
94
- * 'federation-rebuild-complete' notifications. When a build completes successfully,
95
- * it triggers a page reload to reflect the latest changes.
96
- * @param endpoint - The SSE endpoint URL to watch for build notifications.
97
- */
98
- function watchFederationBuildCompletion(endpoint) {
99
- const eventSource = new EventSource(endpoint);
100
- eventSource.onmessage = function (event) {
101
- const data = JSON.parse(event.data);
102
- if (data.type === BuildNotificationType.COMPLETED) {
103
- console.log('[Federation] Rebuild completed, reloading...');
104
- window.location.reload();
105
- }
106
- };
107
- eventSource.onerror = function (event) {
108
- console.warn('[Federation] SSE connection error:', event);
109
- };
110
- }
111
-
112
- /**
113
- * Initializes the Native Federation runtime for the host application.
114
- *
115
- * This is the main entry point for setting up federation. It performs the following:
116
- * 1. Loads the host's remoteEntry.json to discover shared dependencies
117
- * 2. Loads each remote's remoteEntry.json to discover exposed modules
118
- * 3. Creates an ES Module import map with proper scoping
119
- * 4. Injects the import map into the DOM as a <script type="importmap-shim">
120
- *
121
- * The import map allows dynamic imports to resolve correctly:
122
- * - Host shared deps go in root imports (e.g., "angular": "./angular.js")
123
- * - Remote exposed modules go in root imports (e.g., "mfe1/Component": "http://...")
124
- * - Remote shared deps go in scoped imports for proper resolution
125
- *
126
- * @param remotesOrManifestUrl - Either:
127
- * - A record of remote names to their remoteEntry.json URLs
128
- * Example: { mfe1: 'http://localhost:3000/remoteEntry.json' }
129
- * - A URL to a manifest.json that contains the remotes record
130
- * Example: 'http://localhost:3000/federation-manifest.json'
131
- *
132
- * @param options - Configuration options:
133
- * - cacheTag: A version string to append as query param for cache busting
134
- * Example: { cacheTag: 'v1.0.0' } results in '?t=v1.0.0' on all requests
135
- *
136
- * @returns The final merged ImportMap that was injected into the DOM
137
- *
138
- */
139
- async function initFederation(remotesOrManifestUrl = {}, options) {
140
- const cacheTag = options?.cacheTag ? `?t=${options.cacheTag}` : '';
141
- const normalizedRemotes = typeof remotesOrManifestUrl === 'string'
142
- ? await loadManifest(remotesOrManifestUrl + cacheTag)
143
- : remotesOrManifestUrl;
144
- const hostInfo = await loadFederationInfo(`./remoteEntry.json${cacheTag}`);
145
- const hostImportMap = await processHostInfo(hostInfo);
146
- // Host application is fully loaded, now we can process the remotes
147
- // Each remote contributes:
148
- // - Exposed modules to root imports
149
- // - Shared dependencies to scoped imports
150
- const remotesImportMap = await fetchAndRegisterRemotes(normalizedRemotes, {
151
- throwIfRemoteNotFound: false,
152
- ...options,
153
- });
154
- const mergedImportMap = mergeImportMaps(hostImportMap, remotesImportMap);
155
- // Inject the final import map into the DOM with importmap-shim
156
- appendImportMap(mergedImportMap);
157
- return mergedImportMap;
158
- }
159
- /**
160
- * Loads a federation manifest file (JSON) from the given URL.
161
- *
162
- * The manifest should map remote names to their remoteEntry.json URLs.
163
- *
164
- * @param manifestUrl - The URL to the manifest.json file.
165
- * @returns A promise resolving to an object mapping remote names to their remoteEntry.json URLs.
166
- */
167
- async function loadManifest(manifestUrl) {
168
- const manifest = (await fetch(manifestUrl).then((r) => r.json()));
169
- return manifest;
170
- }
171
- /**
172
- * Adds cache busting query parameter to a URL if cacheTag is provided.
173
- */
174
- function applyCacheTag(url, cacheTag) {
175
- if (!cacheTag)
176
- return url;
177
- const separator = url.includes('?') ? '&' : '?';
178
- return `${url}${separator}t=${cacheTag}`;
179
- }
180
- /**
181
- * Handles errors when loading a remote entry.
182
- * Either throws or logs based on options.
183
- */
184
- function handleRemoteLoadError(remoteName, remoteUrl, options, originalError) {
185
- const errorMessage = `Error loading remote entry for ${remoteName} from file ${remoteUrl}`;
186
- if (options.throwIfRemoteNotFound) {
187
- throw new Error(errorMessage);
188
- }
189
- console.error(errorMessage);
190
- console.error(originalError);
191
- return null;
192
- }
193
- /**
194
- * Fetches and registers multiple remote applications in parallel and merges their import maps.
195
- *
196
- * This function is the orchestrator for loading all remotes. It:
197
- * 1. Creates a promise for each remote to load its remoteEntry.json
198
- * 2. Applies cache busting to each remote URL
199
- * 3. Handles errors gracefully (logs or throws based on options)
200
- * 4. Merges all successful remote import maps into one
201
- *
202
- * Each remote contributes:
203
- * - Its exposed modules to the root imports
204
- * - Its shared dependencies to scoped imports
205
- *
206
- * @param remotes - Record of remote names to their remoteEntry.json URLs
207
- * @param options - Processing options including:
208
- * - throwIfRemoteNotFound: Whether to throw or log on remote load failure
209
- * - cacheTag: Cache busting tag to append to URLs
210
- *
211
- * @returns Merged import map containing all remotes' contributions
212
- *
213
- */
214
- async function fetchAndRegisterRemotes(remotes, options = { throwIfRemoteNotFound: false }) {
215
- // Each promise will independently fetch and process its remoteEntry.json
216
- const fetchAndRegisterRemotePromises = Object.entries(remotes).map(async ([remoteName, remoteUrl]) => {
217
- try {
218
- const urlWithCache = applyCacheTag(remoteUrl, options.cacheTag);
219
- return await fetchAndRegisterRemote(urlWithCache, remoteName);
220
- }
221
- catch (e) {
222
- return handleRemoteLoadError(remoteName, remoteUrl, options, e);
223
- }
224
- });
225
- const remoteImportMaps = await Promise.all(fetchAndRegisterRemotePromises);
226
- // Filter out failed remotes (null values) and merge successful ones
227
- const importMap = remoteImportMaps.reduce((acc, remoteImportMap) => remoteImportMap ? mergeImportMaps(acc, remoteImportMap) : acc, { imports: {}, scopes: {} });
228
- return importMap;
229
- }
230
- /**
231
- * Fetches a single remote application's remoteEntry.json file and registers it in the system (global registry).
232
- *
233
- * This function handles everything needed to integrate one remote:
234
- * 1. Fetches the remote's remoteEntry.json file
235
- * 2. Extracts the base URL from the remoteEntry path
236
- * 3. Creates import map entries for exposed modules and shared deps
237
- * 4. Registers the remote in the global remotes registry
238
- * 5. Sets up hot reload watching if configured (development mode)
239
- *
240
- * @param federationInfoUrl - Full URL to the remote's remoteEntry.json
241
- * @param remoteName - Name to use for this remote (optional, uses info.name if not provided)
242
- *
243
- * @returns Import map containing this remote's exposed modules and shared dependencies
244
- *
245
- * @example
246
- * ```typescript
247
- * const importMap = await fetchAndRegisterRemote(
248
- * 'http://localhost:3000/mfe1/remoteEntry.json',
249
- * 'mfe1'
250
- * );
251
- * // Result: {
252
- * // imports: { 'mfe1/Component': 'http://localhost:3000/mfe1/Component.js' },
253
- * // scopes: { 'http://localhost:3000/mfe1/': { 'lodash': '...' } }
254
- * // }
255
- * ```
256
- */
257
- async function fetchAndRegisterRemote(federationInfoUrl, remoteName) {
258
- const baseUrl = getDirectory(federationInfoUrl);
259
- const remoteInfo = await loadFederationInfo(federationInfoUrl);
260
- // Uses the name from the remote's remoteEntry.json if not explicitly provided
261
- if (!remoteName) {
262
- remoteName = remoteInfo.name;
263
- }
264
- // Setup hot reload watching for development mode and in case it has a build notifications endpoint
265
- if (remoteInfo.buildNotificationsEndpoint) {
266
- watchFederationBuildCompletion(baseUrl + remoteInfo.buildNotificationsEndpoint);
267
- }
268
- const importMap = createRemoteImportMap(remoteInfo, remoteName, baseUrl);
269
- // Register this remote in the global registry
270
- addRemote(remoteName, { ...remoteInfo, baseUrl });
271
- return importMap;
272
- }
273
- /**
274
- * Creates an import map for a remote application.
275
- *
276
- * The import map has two parts:
277
- * 1. Imports (root level): Maps remote's exposed modules
278
- * Example: "mfe1/Component" -> "http://localhost:3000/mfe1/Component.js"
279
- *
280
- * 2. Scopes: Maps remote's shared dependencies within its scope
281
- * Example: "http://localhost:3000/mfe1/": { "lodash": "http://localhost:3000/mfe1/lodash.js" }
282
- *
283
- * Scoping ensures that when a module from this remote imports 'lodash',
284
- * it gets the version from this remote's bundle, not another version.
285
- *
286
- * @param remoteInfo - Federation info from the remote's remoteEntry.json
287
- * @param remoteName - Name used to prefix exposed module keys
288
- * @param baseUrl - Base URL where the remote is hosted
289
- *
290
- * @returns Import map with imports and scopes for this remote
291
- */
292
- function createRemoteImportMap(remoteInfo, remoteName, baseUrl) {
293
- const imports = processExposed(remoteInfo, remoteName, baseUrl);
294
- const scopes = processRemoteImports(remoteInfo, baseUrl);
295
- return { imports, scopes };
296
- }
297
- /**
298
- * Fetches and parses a remoteEntry.json file.
299
- *
300
- * The remoteEntry.json contains metadata about a federated module:
301
- * - name: The application name
302
- * - exposes: Array of modules this app exposes to others
303
- * - shared: Array of dependencies this app shares
304
- * - buildNotificationsEndpoint: Optional SSE endpoint for hot reload (development mode)
305
- *
306
- * @param remoteEntryUrl - URL to the remoteEntry.json file (can be relative or absolute)
307
- * @returns Parsed federation info object
308
- */
309
- async function loadFederationInfo(remoteEntryUrl) {
310
- const info = (await fetch(remoteEntryUrl).then((r) => r.json()));
311
- return info;
312
- }
313
- /**
314
- * Processes a remote's shared dependencies into scoped import map entries.
315
- *
316
- * Shared dependencies need to be scoped to avoid version conflicts.
317
- * When a module from "http://localhost:3000/mfe1/" imports "lodash",
318
- * the import map scope ensures it gets the correct version.
319
- *
320
- * Scope structure:
321
- * {
322
- * "http://localhost:3000/mfe1/": {
323
- * "lodash": "http://localhost:3000/mfe1/lodash.js",
324
- * "rxjs": "http://localhost:3000/mfe1/rxjs.js"
325
- * }
326
- * }
327
- *
328
- * This function also manages external URLs - if a shared dependency
329
- * has already been loaded from another location, it can reuse that URL.
330
- *
331
- * @param remoteInfo - Federation info containing shared dependencies
332
- * @param baseUrl - Base URL of the remote (used as the scope key)
333
- *
334
- * @returns Scopes object mapping baseUrl to its shared dependencies
335
- */
336
- function processRemoteImports(remoteInfo, baseUrl) {
337
- const scopes = {};
338
- const scopedImports = {};
339
- for (const shared of remoteInfo.shared) {
340
- // Check if this dependency already has an external URL registered
341
- // If not, construct the URL from the base path and output filename
342
- const outFileName = getExternalUrl(shared) ?? joinPaths(baseUrl, shared.outFileName);
343
- // Register this URL as the external location for this shared dependency
344
- // This allows other remotes to potentially reuse this version
345
- setExternalUrl(shared, outFileName);
346
- // Add to the scoped imports: package name -> full URL
347
- scopedImports[shared.packageName] = outFileName;
348
- }
349
- scopes[baseUrl + '/'] = scopedImports;
350
- return scopes;
351
- }
352
- /**
353
- * Processes a remote's exposed modules into root-level import map entries.
354
- *
355
- * Exposed modules are what the remote makes available to other applications.
356
- * They go in the root imports (not scoped) so any app can import them.
357
- *
358
- * Example exposed module:
359
- * - Remote 'mfe1' exposes './Component' from file 'Component.js'
360
- * - Results in: "mfe1/Component" -> "http://localhost:3000/mfe1/Component.js"
361
- *
362
- * This allows other apps to do:
363
- * ```typescript
364
- * import { Component } from 'mfe1/Component';
365
- * ```
366
- *
367
- * @param remoteInfo - Federation info containing exposed modules
368
- * @param remoteName - Name to prefix the exposed keys with
369
- * @param baseUrl - Base URL where the remote's files are hosted
370
- *
371
- * @returns Imports object mapping remote module keys to their URLs
372
- */
373
- function processExposed(remoteInfo, remoteName, baseUrl) {
374
- const imports = {};
375
- for (const exposed of remoteInfo.exposes) {
376
- // Create the import key by joining remote name with the exposed key
377
- // Example: 'mfe1' + './Component' -> 'mfe1/Component'
378
- const key = joinPaths(remoteName, exposed.key);
379
- // Create the full URL to the exposed module's output file
380
- // Example: 'http://localhost:3000/mfe1' + 'Component.js' -> 'http://localhost:3000/mfe1/Component.js'
381
- const value = joinPaths(baseUrl, exposed.outFileName);
382
- imports[key] = value;
383
- }
384
- return imports;
385
- }
386
- /**
387
- * Processes the host application's federation info into an import map.
388
- *
389
- * The host app typically doesn't expose modules (it's the consumer),
390
- * but it does share dependencies that should be available to remotes.
391
- *
392
- * Host shared dependencies go in root-level imports (not scoped) because:
393
- * 1. The host loads first and establishes the base environment
394
- * 2. Remotes should prefer host versions to avoid duplication
395
- *
396
- * @param hostInfo - Federation info from the host's remoteEntry.json
397
- * @param relBundlesPath - Relative path to the host's bundle directory (default: './')
398
- *
399
- * @returns Import map with host's shared dependencies in root imports
400
- */
401
- async function processHostInfo(hostInfo, relBundlesPath = './') {
402
- // Transform shared array into imports object
403
- const imports = hostInfo.shared.reduce((acc, cur) => ({
404
- ...acc,
405
- [cur.packageName]: relBundlesPath + cur.outFileName,
406
- }), {});
407
- // Register external URLs for host's shared dependencies
408
- // This allows remotes to discover and potentially reuse these versions
409
- for (const shared of hostInfo.shared) {
410
- setExternalUrl(shared, relBundlesPath + shared.outFileName);
411
- }
412
- // Host doesn't have scopes - its shared deps are at root level
413
- return { imports, scopes: {} };
414
- }
415
-
416
- /* eslint-disable @typescript-eslint/no-explicit-any */
417
- async function loadRemoteModule(optionsOrRemoteName, exposedModule) {
418
- const options = normalizeOptions(optionsOrRemoteName, exposedModule);
419
- await ensureRemoteInitialized(options);
420
- const remoteName = getRemoteNameByOptions(options);
421
- const remote = getRemote(remoteName);
422
- const fallback = options.fallback;
423
- // Handles errors when the remote is missing
424
- const remoteError = !remote ? 'unknown remote ' + remoteName : '';
425
- if (!remote && !fallback)
426
- throw new Error(remoteError);
427
- if (!remote) {
428
- logClientError(remoteError);
429
- return Promise.resolve(fallback);
430
- }
431
- const exposedModuleInfo = remote.exposes.find((e) => e.key === options.exposedModule);
432
- // Handles errors when the exposed module is missing
433
- const exposedError = !exposedModuleInfo
434
- ? `Unknown exposed module ${options.exposedModule} in remote ${remoteName}`
435
- : '';
436
- if (!exposedModuleInfo && !fallback)
437
- throw new Error(exposedError);
438
- if (!exposedModuleInfo) {
439
- logClientError(exposedError);
440
- return Promise.resolve(fallback);
441
- }
442
- const moduleUrl = joinPaths(remote.baseUrl, exposedModuleInfo.outFileName);
443
- try {
444
- const module = _import(moduleUrl);
445
- return module;
446
- }
447
- catch (e) {
448
- // Handles errors when the module import fails
449
- if (fallback) {
450
- console.error('error loading remote module', e);
451
- return fallback;
452
- }
453
- throw e;
454
- }
455
- }
456
- /**
457
- * Internal helper function to perform the dynamic import.
458
- *
459
- * @template T - The expected type of the module's exports
460
- * @param moduleUrl - Full URL to the module file to import
461
- * @returns Promise resolving to the imported module
462
- */
463
- function _import(moduleUrl) {
464
- return typeof importShim !== 'undefined'
465
- ? importShim(moduleUrl)
466
- : import(/* @vite-ignore */ moduleUrl);
467
- }
468
- /**
469
- * Resolves the remote name from the provided options.
470
- *
471
- * The remote name can be determined in two ways:
472
- * 1. If options.remoteName is provided, use it directly
473
- * 2. If only remoteEntry is provided, extract the baseUrl
474
- * and look up the remote name from the registry using that baseUrl
475
- *
476
- * @param options - Load options containing remoteName and/or remoteEntry
477
- * @returns The resolved remote name
478
- *
479
- * @throws Error if neither remoteName nor remoteEntry is provided
480
- * @throws Error if the remote name cannot be determined
481
- */
482
- function getRemoteNameByOptions(options) {
483
- let remoteName;
484
- if (options.remoteName) {
485
- remoteName = options.remoteName;
486
- }
487
- else if (options.remoteEntry) {
488
- const baseUrl = getDirectory(options.remoteEntry);
489
- remoteName = getRemoteNameByBaseUrl(baseUrl);
490
- }
491
- else {
492
- throw new Error('unexpected arguments: Please pass remoteName or remoteEntry');
493
- }
494
- if (!remoteName) {
495
- throw new Error('unknown remoteName ' + remoteName);
496
- }
497
- return remoteName;
498
- }
499
- /**
500
- * Ensures that the remote is initialized before attempting to load a module from it.
501
- *
502
- * This function enables lazy-loading of remotes that weren't registered during
503
- * the initial `initFederation()` call. It checks if:
504
- * 1. A remoteEntry URL is provided in the options
505
- * 2. The remote at that URL hasn't been initialized yet
506
- *
507
- * If both conditions are true, it:
508
- * 1. Fetches the remote's remoteEntry.json file
509
- * 2. Registers the remote in the global registry
510
- * 3. Creates and appends the remote's import map to the DOM
511
- *
512
- * @param options - Load options containing optional remoteEntry URL
513
- * @returns Promise that resolves when the remote is initialized (or immediately if already initialized)
514
- *
515
- */
516
- async function ensureRemoteInitialized(options) {
517
- if (options.remoteEntry &&
518
- !isRemoteInitialized(getDirectory(options.remoteEntry))) {
519
- const importMap = await fetchAndRegisterRemote(options.remoteEntry);
520
- appendImportMap(importMap);
521
- }
522
- }
523
- /**
524
- * Normalizes the function arguments into a standard LoadRemoteModuleOptions object.
525
- *
526
- * The function detects which pattern is being used and converts it to the
527
- * standard options object format for consistent internal processing.
528
- *
529
- * @param optionsOrRemoteName - Either an options object or the remote name string
530
- * @param exposedModule - The exposed module key
531
- * @returns Normalized options object
532
- *
533
- * @throws Error if arguments don't match either supported pattern
534
- */
535
- function normalizeOptions(optionsOrRemoteName, exposedModule) {
536
- let options;
537
- if (typeof optionsOrRemoteName === 'string' && exposedModule) {
538
- options = {
539
- remoteName: optionsOrRemoteName,
540
- exposedModule,
541
- };
542
- }
543
- else if (typeof optionsOrRemoteName === 'object' && !exposedModule) {
544
- options = optionsOrRemoteName;
545
- }
546
- else {
547
- throw new Error('unexpected arguments: please pass options or a remoteName/exposedModule-pair');
548
- }
549
- return options;
550
- }
551
- /**
552
- * Logs an error message to the console, but only in browser environments.
553
- *
554
- * @param error - The error message to log
555
- *
556
- */
557
- function logClientError(error) {
558
- if (typeof window !== 'undefined') {
559
- console.error(error);
560
- }
561
- }
562
-
563
- /**
564
- * Generated bundle index. Do not edit.
565
- */
566
-
567
- export { BUILD_NOTIFICATIONS_ENDPOINT, BuildNotificationType, fetchAndRegisterRemote, fetchAndRegisterRemotes, initFederation, loadRemoteModule, mergeImportMaps, processHostInfo };
568
- //# sourceMappingURL=softarc-native-federation-runtime.mjs.map