@softarc/native-federation-runtime 4.0.0-RC2 → 4.0.0-RC3
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/index.d.ts +5 -0
- package/index.js +336 -0
- package/package.json +6 -1
package/index.d.ts
ADDED
package/index.js
ADDED
|
@@ -0,0 +1,336 @@
|
|
|
1
|
+
// packages/runtime/src/lib/init-federation.ts
|
|
2
|
+
import { toChunkImport } from "@softarc/native-federation/domain";
|
|
3
|
+
|
|
4
|
+
// packages/runtime/src/lib/model/global-cache.ts
|
|
5
|
+
var nfNamespace = "__NATIVE_FEDERATION__";
|
|
6
|
+
var global = globalThis;
|
|
7
|
+
global[nfNamespace] ??= {
|
|
8
|
+
externals: /* @__PURE__ */ new Map(),
|
|
9
|
+
remoteNamesToRemote: /* @__PURE__ */ new Map(),
|
|
10
|
+
baseUrlToRemoteNames: /* @__PURE__ */ new Map()
|
|
11
|
+
};
|
|
12
|
+
var globalCache = global[nfNamespace];
|
|
13
|
+
|
|
14
|
+
// packages/runtime/src/lib/model/externals.ts
|
|
15
|
+
var externals = globalCache.externals;
|
|
16
|
+
function getExternalKey(shared) {
|
|
17
|
+
return `${shared.packageName}@${shared.version}`;
|
|
18
|
+
}
|
|
19
|
+
function getExternalUrl(shared) {
|
|
20
|
+
const packageKey = getExternalKey(shared);
|
|
21
|
+
return externals.get(packageKey);
|
|
22
|
+
}
|
|
23
|
+
function setExternalUrl(shared, url) {
|
|
24
|
+
const packageKey = getExternalKey(shared);
|
|
25
|
+
externals.set(packageKey, url);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// packages/runtime/src/lib/model/import-map.ts
|
|
29
|
+
function mergeImportMaps(map1, map2) {
|
|
30
|
+
return {
|
|
31
|
+
imports: { ...map1.imports, ...map2.imports },
|
|
32
|
+
scopes: { ...map1.scopes, ...map2.scopes }
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// packages/runtime/src/lib/model/remotes.ts
|
|
37
|
+
var remoteNamesToRemote = globalCache.remoteNamesToRemote;
|
|
38
|
+
var baseUrlToRemoteNames = globalCache.baseUrlToRemoteNames;
|
|
39
|
+
function addRemote(remoteName, remote) {
|
|
40
|
+
remoteNamesToRemote.set(remoteName, remote);
|
|
41
|
+
baseUrlToRemoteNames.set(remote.baseUrl, remoteName);
|
|
42
|
+
}
|
|
43
|
+
function getRemoteNameByBaseUrl(baseUrl) {
|
|
44
|
+
return baseUrlToRemoteNames.get(baseUrl);
|
|
45
|
+
}
|
|
46
|
+
function isRemoteInitialized(baseUrl) {
|
|
47
|
+
return baseUrlToRemoteNames.has(baseUrl);
|
|
48
|
+
}
|
|
49
|
+
function getRemote(remoteName) {
|
|
50
|
+
return remoteNamesToRemote.get(remoteName);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// packages/runtime/src/lib/utils/trusted-types.ts
|
|
54
|
+
var global2 = globalThis;
|
|
55
|
+
var policy;
|
|
56
|
+
function createPolicy() {
|
|
57
|
+
if (policy === void 0) {
|
|
58
|
+
policy = null;
|
|
59
|
+
if (global2.trustedTypes) {
|
|
60
|
+
try {
|
|
61
|
+
policy = global2.trustedTypes.createPolicy(
|
|
62
|
+
"native-federation",
|
|
63
|
+
{
|
|
64
|
+
createHTML: (html) => html,
|
|
65
|
+
createScript: (script) => script,
|
|
66
|
+
createScriptURL: (url) => url
|
|
67
|
+
}
|
|
68
|
+
);
|
|
69
|
+
} catch {
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return policy;
|
|
74
|
+
}
|
|
75
|
+
function tryCreateTrustedScript(script) {
|
|
76
|
+
return createPolicy()?.createScript(script) ?? script;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// packages/runtime/src/lib/utils/add-import-map.ts
|
|
80
|
+
function appendImportMap(importMap) {
|
|
81
|
+
document.head.appendChild(
|
|
82
|
+
Object.assign(document.createElement("script"), {
|
|
83
|
+
type: tryCreateTrustedScript("importmap-shim"),
|
|
84
|
+
textContent: tryCreateTrustedScript(JSON.stringify(importMap))
|
|
85
|
+
})
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// packages/runtime/src/lib/utils/path-utils.ts
|
|
90
|
+
function getDirectory(url) {
|
|
91
|
+
const parts = url.split("/");
|
|
92
|
+
parts.pop();
|
|
93
|
+
return parts.join("/");
|
|
94
|
+
}
|
|
95
|
+
function joinPaths(path1, path2) {
|
|
96
|
+
while (path1.endsWith("/")) {
|
|
97
|
+
path1 = path1.substring(0, path1.length - 1);
|
|
98
|
+
}
|
|
99
|
+
if (path2.startsWith("./")) {
|
|
100
|
+
path2 = path2.substring(2, path2.length);
|
|
101
|
+
}
|
|
102
|
+
return `${path1}/${path2}`;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// packages/runtime/src/lib/watch-federation-build.ts
|
|
106
|
+
import { BuildNotificationType } from "@softarc/native-federation/domain";
|
|
107
|
+
function watchFederationBuildCompletion(endpoint) {
|
|
108
|
+
const eventSource = new EventSource(endpoint);
|
|
109
|
+
eventSource.onmessage = function(event) {
|
|
110
|
+
const data = JSON.parse(event.data);
|
|
111
|
+
if (data.type === BuildNotificationType.COMPLETED) {
|
|
112
|
+
console.log("[Federation] Rebuild completed, reloading...");
|
|
113
|
+
window.location.reload();
|
|
114
|
+
}
|
|
115
|
+
};
|
|
116
|
+
eventSource.onerror = function(event) {
|
|
117
|
+
console.warn("[Federation] SSE connection error:", event);
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// packages/runtime/src/lib/init-federation.ts
|
|
122
|
+
async function initFederation(remotesOrManifestUrl = {}, options) {
|
|
123
|
+
const cacheTag = options?.cacheTag ? `?t=${options.cacheTag}` : "";
|
|
124
|
+
const normalizedRemotes = typeof remotesOrManifestUrl === "string" ? await loadManifest(remotesOrManifestUrl + cacheTag) : remotesOrManifestUrl;
|
|
125
|
+
const hostInfo = await loadFederationInfo(`./remoteEntry.json${cacheTag}`);
|
|
126
|
+
const hostImportMap = await processHostInfo(hostInfo);
|
|
127
|
+
const remotesImportMap = await fetchAndRegisterRemotes(normalizedRemotes, {
|
|
128
|
+
throwIfRemoteNotFound: false,
|
|
129
|
+
...options
|
|
130
|
+
});
|
|
131
|
+
const mergedImportMap = mergeImportMaps(hostImportMap, remotesImportMap);
|
|
132
|
+
appendImportMap(mergedImportMap);
|
|
133
|
+
return mergedImportMap;
|
|
134
|
+
}
|
|
135
|
+
async function loadManifest(manifestUrl) {
|
|
136
|
+
const manifest = await fetch(manifestUrl).then((r) => r.json());
|
|
137
|
+
return manifest;
|
|
138
|
+
}
|
|
139
|
+
function applyCacheTag(url, cacheTag) {
|
|
140
|
+
if (!cacheTag) return url;
|
|
141
|
+
const separator = url.includes("?") ? "&" : "?";
|
|
142
|
+
return `${url}${separator}t=${cacheTag}`;
|
|
143
|
+
}
|
|
144
|
+
function handleRemoteLoadError(remoteName, remoteUrl, options, originalError) {
|
|
145
|
+
const errorMessage = `Error loading remote entry for ${remoteName} from file ${remoteUrl}`;
|
|
146
|
+
if (options.throwIfRemoteNotFound) {
|
|
147
|
+
throw new Error(errorMessage);
|
|
148
|
+
}
|
|
149
|
+
console.error(errorMessage);
|
|
150
|
+
console.error(originalError);
|
|
151
|
+
return null;
|
|
152
|
+
}
|
|
153
|
+
async function fetchAndRegisterRemotes(remotes, options = { throwIfRemoteNotFound: false }) {
|
|
154
|
+
const fetchAndRegisterRemotePromises = Object.entries(remotes).map(
|
|
155
|
+
async ([remoteName, remoteUrl]) => {
|
|
156
|
+
try {
|
|
157
|
+
const urlWithCache = applyCacheTag(remoteUrl, options.cacheTag);
|
|
158
|
+
return await fetchAndRegisterRemote(urlWithCache, remoteName);
|
|
159
|
+
} catch (e) {
|
|
160
|
+
return handleRemoteLoadError(remoteName, remoteUrl, options, e);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
);
|
|
164
|
+
const remoteImportMaps = await Promise.all(fetchAndRegisterRemotePromises);
|
|
165
|
+
const importMap = remoteImportMaps.reduce(
|
|
166
|
+
(acc, remoteImportMap) => remoteImportMap ? mergeImportMaps(acc, remoteImportMap) : acc,
|
|
167
|
+
{ imports: {}, scopes: {} }
|
|
168
|
+
);
|
|
169
|
+
return importMap;
|
|
170
|
+
}
|
|
171
|
+
async function processRemoteInfos(remotes, options = { throwIfRemoteNotFound: false }) {
|
|
172
|
+
return fetchAndRegisterRemotes(remotes, options);
|
|
173
|
+
}
|
|
174
|
+
async function fetchAndRegisterRemote(federationInfoUrl, remoteName) {
|
|
175
|
+
const baseUrl = getDirectory(federationInfoUrl);
|
|
176
|
+
const remoteInfo = await loadFederationInfo(federationInfoUrl);
|
|
177
|
+
if (!remoteName) {
|
|
178
|
+
remoteName = remoteInfo.name;
|
|
179
|
+
}
|
|
180
|
+
if (remoteInfo.buildNotificationsEndpoint) {
|
|
181
|
+
watchFederationBuildCompletion(baseUrl + remoteInfo.buildNotificationsEndpoint);
|
|
182
|
+
}
|
|
183
|
+
const importMap = createRemoteImportMap(remoteInfo, remoteName, baseUrl);
|
|
184
|
+
addRemote(remoteName, { ...remoteInfo, baseUrl });
|
|
185
|
+
return importMap;
|
|
186
|
+
}
|
|
187
|
+
function createRemoteImportMap(remoteInfo, remoteName, baseUrl) {
|
|
188
|
+
const imports = processExposed(remoteInfo, remoteName, baseUrl);
|
|
189
|
+
const scopes = processRemoteImports(remoteInfo, baseUrl);
|
|
190
|
+
return { imports, scopes };
|
|
191
|
+
}
|
|
192
|
+
async function loadFederationInfo(remoteEntryUrl) {
|
|
193
|
+
const info = await fetch(remoteEntryUrl).then((r) => r.json());
|
|
194
|
+
return info;
|
|
195
|
+
}
|
|
196
|
+
function processRemoteImports(remoteInfo, baseUrl) {
|
|
197
|
+
const scopes = {};
|
|
198
|
+
const scopedImports = {};
|
|
199
|
+
for (const shared of remoteInfo.shared) {
|
|
200
|
+
const outFileName = getExternalUrl(shared) ?? joinPaths(baseUrl, shared.outFileName);
|
|
201
|
+
setExternalUrl(shared, outFileName);
|
|
202
|
+
scopedImports[shared.packageName] = outFileName;
|
|
203
|
+
}
|
|
204
|
+
if (remoteInfo.chunks) {
|
|
205
|
+
Object.values(remoteInfo.chunks).forEach((c) => {
|
|
206
|
+
c.forEach((e) => {
|
|
207
|
+
const key = toChunkImport(e);
|
|
208
|
+
scopedImports[key] = joinPaths(baseUrl, e);
|
|
209
|
+
});
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
scopes[baseUrl + "/"] = scopedImports;
|
|
213
|
+
return scopes;
|
|
214
|
+
}
|
|
215
|
+
function processExposed(remoteInfo, remoteName, baseUrl) {
|
|
216
|
+
const imports = {};
|
|
217
|
+
for (const exposed of remoteInfo.exposes) {
|
|
218
|
+
const key = joinPaths(remoteName, exposed.key);
|
|
219
|
+
const value = joinPaths(baseUrl, exposed.outFileName);
|
|
220
|
+
imports[key] = value;
|
|
221
|
+
}
|
|
222
|
+
return imports;
|
|
223
|
+
}
|
|
224
|
+
async function processHostInfo(hostInfo, relBundlesPath = "./") {
|
|
225
|
+
const imports = hostInfo.shared.reduce(
|
|
226
|
+
(acc, cur) => ({
|
|
227
|
+
...acc,
|
|
228
|
+
[cur.packageName]: relBundlesPath + cur.outFileName
|
|
229
|
+
}),
|
|
230
|
+
{}
|
|
231
|
+
);
|
|
232
|
+
if (hostInfo.chunks) {
|
|
233
|
+
Object.values(hostInfo.chunks).forEach((c) => {
|
|
234
|
+
c.forEach((e) => {
|
|
235
|
+
const key = toChunkImport(e);
|
|
236
|
+
imports[key] = relBundlesPath + e;
|
|
237
|
+
});
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
for (const shared of hostInfo.shared) {
|
|
241
|
+
setExternalUrl(shared, relBundlesPath + shared.outFileName);
|
|
242
|
+
}
|
|
243
|
+
return { imports, scopes: {} };
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// packages/runtime/src/lib/load-remote-module.ts
|
|
247
|
+
async function loadRemoteModule(optionsOrRemoteName, exposedModule) {
|
|
248
|
+
const options = normalizeOptions(optionsOrRemoteName, exposedModule);
|
|
249
|
+
await ensureRemoteInitialized(options);
|
|
250
|
+
const remoteName = getRemoteNameByOptions(options);
|
|
251
|
+
const remote = getRemote(remoteName);
|
|
252
|
+
const fallback = options.fallback;
|
|
253
|
+
const remoteError = !remote ? "unknown remote " + remoteName : "";
|
|
254
|
+
if (!remote && !fallback) throw new Error(remoteError);
|
|
255
|
+
if (!remote) {
|
|
256
|
+
logClientError(remoteError);
|
|
257
|
+
return Promise.resolve(fallback);
|
|
258
|
+
}
|
|
259
|
+
const exposedModuleInfo = remote.exposes.find((e) => e.key === options.exposedModule);
|
|
260
|
+
const exposedError = !exposedModuleInfo ? `Unknown exposed module ${options.exposedModule} in remote ${remoteName}` : "";
|
|
261
|
+
if (!exposedModuleInfo && !fallback) throw new Error(exposedError);
|
|
262
|
+
if (!exposedModuleInfo) {
|
|
263
|
+
logClientError(exposedError);
|
|
264
|
+
return Promise.resolve(fallback);
|
|
265
|
+
}
|
|
266
|
+
const moduleUrl = joinPaths(remote.baseUrl, exposedModuleInfo.outFileName);
|
|
267
|
+
try {
|
|
268
|
+
const module = _import(moduleUrl);
|
|
269
|
+
return module;
|
|
270
|
+
} catch (e) {
|
|
271
|
+
if (fallback) {
|
|
272
|
+
console.error("error loading remote module", e);
|
|
273
|
+
return fallback;
|
|
274
|
+
}
|
|
275
|
+
throw e;
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
function _import(moduleUrl) {
|
|
279
|
+
return typeof importShim !== "undefined" ? importShim(moduleUrl) : import(
|
|
280
|
+
/* @vite-ignore */
|
|
281
|
+
moduleUrl
|
|
282
|
+
);
|
|
283
|
+
}
|
|
284
|
+
function getRemoteNameByOptions(options) {
|
|
285
|
+
let remoteName;
|
|
286
|
+
if (options.remoteName) {
|
|
287
|
+
remoteName = options.remoteName;
|
|
288
|
+
} else if (options.remoteEntry) {
|
|
289
|
+
const baseUrl = getDirectory(options.remoteEntry);
|
|
290
|
+
remoteName = getRemoteNameByBaseUrl(baseUrl);
|
|
291
|
+
} else {
|
|
292
|
+
throw new Error("unexpected arguments: Please pass remoteName or remoteEntry");
|
|
293
|
+
}
|
|
294
|
+
if (!remoteName) {
|
|
295
|
+
throw new Error("unknown remoteName " + remoteName);
|
|
296
|
+
}
|
|
297
|
+
return remoteName;
|
|
298
|
+
}
|
|
299
|
+
async function ensureRemoteInitialized(options) {
|
|
300
|
+
if (options.remoteEntry && !isRemoteInitialized(getDirectory(options.remoteEntry))) {
|
|
301
|
+
const importMap = await fetchAndRegisterRemote(options.remoteEntry);
|
|
302
|
+
appendImportMap(importMap);
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
function normalizeOptions(optionsOrRemoteName, exposedModule) {
|
|
306
|
+
let options;
|
|
307
|
+
if (typeof optionsOrRemoteName === "string" && exposedModule) {
|
|
308
|
+
options = {
|
|
309
|
+
remoteName: optionsOrRemoteName,
|
|
310
|
+
exposedModule
|
|
311
|
+
};
|
|
312
|
+
} else if (typeof optionsOrRemoteName === "object" && !exposedModule) {
|
|
313
|
+
options = optionsOrRemoteName;
|
|
314
|
+
} else {
|
|
315
|
+
throw new Error("unexpected arguments: please pass options or a remoteName/exposedModule-pair");
|
|
316
|
+
}
|
|
317
|
+
return options;
|
|
318
|
+
}
|
|
319
|
+
function logClientError(error) {
|
|
320
|
+
if (typeof window !== "undefined") {
|
|
321
|
+
console.error(error);
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
// packages/runtime/src/lib/model/build-notifications-options.ts
|
|
326
|
+
var BUILD_NOTIFICATIONS_ENDPOINT = "/@angular-architects/native-federation:build-notifications";
|
|
327
|
+
export {
|
|
328
|
+
BUILD_NOTIFICATIONS_ENDPOINT,
|
|
329
|
+
fetchAndRegisterRemote,
|
|
330
|
+
fetchAndRegisterRemotes,
|
|
331
|
+
initFederation,
|
|
332
|
+
loadRemoteModule,
|
|
333
|
+
mergeImportMaps,
|
|
334
|
+
processHostInfo,
|
|
335
|
+
processRemoteInfos
|
|
336
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@softarc/native-federation-runtime",
|
|
3
|
-
"version": "4.0.0-
|
|
3
|
+
"version": "4.0.0-RC3",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"main": "./index.js",
|
|
@@ -16,8 +16,13 @@
|
|
|
16
16
|
},
|
|
17
17
|
"files": [
|
|
18
18
|
"lib",
|
|
19
|
+
"index.d.ts",
|
|
20
|
+
"index.js",
|
|
19
21
|
"!**/*.tsbuildinfo"
|
|
20
22
|
],
|
|
23
|
+
"dependencies": {
|
|
24
|
+
"@softarc/native-federation": "4.0.0-RC3"
|
|
25
|
+
},
|
|
21
26
|
"devDependencies": {
|
|
22
27
|
"@types/node": "^22.5.4",
|
|
23
28
|
"vitest": "^3.0.0",
|