@bleedingdev/modern-js-plugin-bff-extensions 3.9.0-ultramodern.17 → 3.9.0-ultramodern.18
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +12 -2
- package/dist/cjs/backend-federation/edge.js +1 -1
- package/dist/cjs/backend-federation/integrity-plugin.js +112 -0
- package/dist/cjs/backend-federation/node-shared.js +85 -0
- package/dist/cjs/backend-federation/node.js +8 -3
- package/dist/cjs/backend-federation/plugin.js +40 -21
- package/dist/cjs/backend-federation/runtime.js +16 -84
- package/dist/cjs/backend-federation-manifest/load.js +3 -0
- package/dist/cjs/backend-federation-manifest/node.js +5 -3
- package/dist/cjs/backend-federation-manifest/remote.js +1 -7
- package/dist/cjs/effect-source-loader/loader.js +5 -0
- package/dist/esm/backend-federation/edge.mjs +1 -1
- package/dist/esm/backend-federation/integrity-plugin.mjs +68 -0
- package/dist/esm/backend-federation/node-shared.mjs +41 -0
- package/dist/esm/backend-federation/node.mjs +6 -4
- package/dist/esm/backend-federation/plugin.mjs +37 -18
- package/dist/esm/backend-federation/runtime.mjs +16 -84
- package/dist/esm/backend-federation-manifest/load.mjs +3 -0
- package/dist/esm/backend-federation-manifest/node.mjs +5 -3
- package/dist/esm/backend-federation-manifest/remote.mjs +1 -7
- package/dist/esm/effect-source-loader/loader.mjs +5 -0
- package/dist/esm-node/backend-federation/edge.mjs +1 -1
- package/dist/esm-node/backend-federation/integrity-plugin.mjs +69 -0
- package/dist/esm-node/backend-federation/node-shared.mjs +42 -0
- package/dist/esm-node/backend-federation/node.mjs +6 -4
- package/dist/esm-node/backend-federation/plugin.mjs +37 -18
- package/dist/esm-node/backend-federation/runtime.mjs +16 -84
- package/dist/esm-node/backend-federation-manifest/load.mjs +3 -0
- package/dist/esm-node/backend-federation-manifest/node.mjs +5 -3
- package/dist/esm-node/backend-federation-manifest/remote.mjs +1 -7
- package/dist/esm-node/effect-source-loader/loader.mjs +5 -0
- package/dist/types/backend-federation/edge.d.ts +4 -2
- package/dist/types/backend-federation/integrity-plugin.d.ts +13 -0
- package/dist/types/backend-federation/node-shared.d.ts +20 -0
- package/dist/types/backend-federation/node.d.ts +3 -0
- package/dist/types/backend-federation/plugin.d.ts +4 -0
- package/dist/types/backend-federation/types.d.ts +2 -7
- package/dist/types/backend-federation-manifest/types.d.ts +2 -1
- package/package.json +10 -10
- package/src/backend-federation/edge.ts +7 -4
- package/src/backend-federation/integrity-plugin.ts +157 -0
- package/src/backend-federation/load.ts +0 -1
- package/src/backend-federation/node-shared.ts +76 -0
- package/src/backend-federation/node.ts +9 -4
- package/src/backend-federation/plugin.ts +73 -25
- package/src/backend-federation/runtime.ts +16 -90
- package/src/backend-federation/types.ts +2 -11
- package/src/backend-federation-manifest/load.ts +1 -0
- package/src/backend-federation-manifest/node.ts +5 -3
- package/src/backend-federation-manifest/remote.ts +0 -6
- package/src/backend-federation-manifest/types.ts +2 -0
- package/src/effect-source-loader/loader.ts +6 -0
- package/src/effect-source-loader/rspack-loader.ts +0 -1
- package/dist/cjs/backend-federation/entry.js +0 -94
- package/dist/cjs/backend-federation/node-evaluator.js +0 -48
- package/dist/esm/backend-federation/entry.mjs +0 -56
- package/dist/esm/backend-federation/node-evaluator.mjs +0 -7
- package/dist/esm-node/backend-federation/entry.mjs +0 -57
- package/dist/esm-node/backend-federation/node-evaluator.mjs +0 -8
- package/dist/types/backend-federation/entry.d.ts +0 -2
- package/dist/types/backend-federation/node-evaluator.d.ts +0 -2
- package/src/backend-federation/entry.ts +0 -125
- package/src/backend-federation/node-evaluator.ts +0 -13
|
@@ -1,12 +1,14 @@
|
|
|
1
|
+
import { evaluateNodeBackendFederationCommonJs } from "@modern-js/server-runtime-extensions/backend-federation-security/node";
|
|
1
2
|
import { loadBackendFederatedEffectApi } from "./load.mjs";
|
|
2
|
-
import {
|
|
3
|
-
function node_loadBackendFederatedEffectApi(options) {
|
|
3
|
+
import { effectBffHostShared, withEffectBffHostShared } from "./node-shared.mjs";
|
|
4
|
+
async function node_loadBackendFederatedEffectApi(options) {
|
|
4
5
|
return loadBackendFederatedEffectApi({
|
|
5
6
|
...options,
|
|
7
|
+
shared: withEffectBffHostShared(options.shared),
|
|
6
8
|
entryPolicy: {
|
|
7
9
|
...options.entryPolicy,
|
|
8
|
-
evaluateCommonJs: options.entryPolicy?.evaluateCommonJs ??
|
|
10
|
+
evaluateCommonJs: options.entryPolicy?.evaluateCommonJs ?? evaluateNodeBackendFederationCommonJs
|
|
9
11
|
}
|
|
10
12
|
});
|
|
11
13
|
}
|
|
12
|
-
export { node_loadBackendFederatedEffectApi as loadBackendFederatedEffectApi };
|
|
14
|
+
export { effectBffHostShared, node_loadBackendFederatedEffectApi as loadBackendFederatedEffectApi };
|
|
@@ -1,24 +1,43 @@
|
|
|
1
|
+
import { PROVIDED_ENTRY_SCHEME, registerRemoteContainer } from "./integrity-plugin.mjs";
|
|
2
|
+
function toContainer(entry) {
|
|
3
|
+
return {
|
|
4
|
+
get (id) {
|
|
5
|
+
return ()=>Promise.resolve(entry.get(id)).then((factory)=>factory());
|
|
6
|
+
},
|
|
7
|
+
init (...args) {
|
|
8
|
+
return entry.init?.(...args);
|
|
9
|
+
}
|
|
10
|
+
};
|
|
11
|
+
}
|
|
1
12
|
function createBackendFederationLoadEntryPlugin(options) {
|
|
13
|
+
const provided = new WeakMap();
|
|
2
14
|
return {
|
|
3
15
|
name: 'modernjs-backend-federation-load-entry',
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
16
|
+
afterMatchRemote ({ origin, remoteInfo }) {
|
|
17
|
+
if (void 0 === remoteInfo || !PROVIDED_ENTRY_SCHEME.test(remoteInfo.entry)) return;
|
|
18
|
+
let entries = provided.get(origin);
|
|
19
|
+
if (entries?.has(remoteInfo.name) !== true && origin.moduleCache.get(remoteInfo.name)?.remoteEntryExports !== void 0) return;
|
|
20
|
+
if (void 0 === entries) {
|
|
21
|
+
entries = new Map();
|
|
22
|
+
provided.set(origin, entries);
|
|
23
|
+
}
|
|
24
|
+
const runtimeEntries = entries;
|
|
25
|
+
let pending = runtimeEntries.get(remoteInfo.name);
|
|
26
|
+
if (void 0 === pending) {
|
|
27
|
+
pending = Promise.resolve(options.resolveEntry({
|
|
28
|
+
name: remoteInfo.name,
|
|
29
|
+
entry: remoteInfo.entry,
|
|
30
|
+
type: remoteInfo.type
|
|
31
|
+
})).then((entry)=>{
|
|
32
|
+
if (void 0 !== entry) registerRemoteContainer(origin, remoteInfo, toContainer(entry));
|
|
33
|
+
});
|
|
34
|
+
const settled = pending;
|
|
35
|
+
runtimeEntries.set(remoteInfo.name, settled);
|
|
36
|
+
settled.catch(()=>{
|
|
37
|
+
if (runtimeEntries.get(remoteInfo.name) === settled) runtimeEntries.delete(remoteInfo.name);
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
return pending;
|
|
22
41
|
}
|
|
23
42
|
};
|
|
24
43
|
}
|
|
@@ -1,90 +1,22 @@
|
|
|
1
1
|
import { ModuleFederation } from "@module-federation/runtime";
|
|
2
|
-
import {
|
|
3
|
-
import { createBackendFederationLoadEntryPlugin } from "./plugin.mjs";
|
|
2
|
+
import { createBackendFederationIntegrityPlugin } from "./integrity-plugin.mjs";
|
|
4
3
|
import { collectRemotes } from "./remotes.mjs";
|
|
5
|
-
function _check_private_redeclaration(obj, privateCollection) {
|
|
6
|
-
if (privateCollection.has(obj)) throw new TypeError("Cannot initialize the same private elements twice on an object");
|
|
7
|
-
}
|
|
8
|
-
function _class_apply_descriptor_get(receiver, descriptor) {
|
|
9
|
-
if (descriptor.get) return descriptor.get.call(receiver);
|
|
10
|
-
return descriptor.value;
|
|
11
|
-
}
|
|
12
|
-
function _class_apply_descriptor_set(receiver, descriptor, value) {
|
|
13
|
-
if (descriptor.set) descriptor.set.call(receiver, value);
|
|
14
|
-
else {
|
|
15
|
-
if (!descriptor.writable) throw new TypeError("attempted to set read only private field");
|
|
16
|
-
descriptor.value = value;
|
|
17
|
-
}
|
|
18
|
-
}
|
|
19
|
-
function _class_extract_field_descriptor(receiver, privateMap, action) {
|
|
20
|
-
if (!privateMap.has(receiver)) throw new TypeError("attempted to " + action + " private field on non-instance");
|
|
21
|
-
return privateMap.get(receiver);
|
|
22
|
-
}
|
|
23
|
-
function _class_private_field_get(receiver, privateMap) {
|
|
24
|
-
var descriptor = _class_extract_field_descriptor(receiver, privateMap, "get");
|
|
25
|
-
return _class_apply_descriptor_get(receiver, descriptor);
|
|
26
|
-
}
|
|
27
|
-
function _class_private_field_init(obj, privateMap, value) {
|
|
28
|
-
_check_private_redeclaration(obj, privateMap);
|
|
29
|
-
privateMap.set(obj, value);
|
|
30
|
-
}
|
|
31
|
-
function _class_private_field_set(receiver, privateMap, value) {
|
|
32
|
-
var descriptor = _class_extract_field_descriptor(receiver, privateMap, "set");
|
|
33
|
-
_class_apply_descriptor_set(receiver, descriptor, value);
|
|
34
|
-
return value;
|
|
35
|
-
}
|
|
36
|
-
let nextRuntimeId = 0;
|
|
37
|
-
function parseRemoteRequest(request) {
|
|
38
|
-
const [remoteName, ...exposeParts] = request.split('/');
|
|
39
|
-
if (void 0 === remoteName || 0 === remoteName.length || 0 === exposeParts.length) return;
|
|
40
|
-
return remoteName;
|
|
41
|
-
}
|
|
42
|
-
function createLocalEntryPlugin(options, remotes) {
|
|
43
|
-
return createBackendFederationLoadEntryPlugin({
|
|
44
|
-
resolveEntry (resolvedRemote) {
|
|
45
|
-
const remote = remotes.find((candidate)=>candidate.name === resolvedRemote.name);
|
|
46
|
-
if (void 0 === remote) return;
|
|
47
|
-
return {
|
|
48
|
-
get (expose) {
|
|
49
|
-
return ()=>loadBackendFederationExpose(remote, expose, options);
|
|
50
|
-
}
|
|
51
|
-
};
|
|
52
|
-
}
|
|
53
|
-
});
|
|
54
|
-
}
|
|
55
|
-
function isolateRegisteredRemoteEntries(remotes) {
|
|
56
|
-
const runtimeId = nextRuntimeId;
|
|
57
|
-
nextRuntimeId += 1;
|
|
58
|
-
return remotes.map((remote)=>({
|
|
59
|
-
...remote,
|
|
60
|
-
entry: `${remote.entry}#modernjs-backend-runtime-${runtimeId}`
|
|
61
|
-
}));
|
|
62
|
-
}
|
|
63
|
-
var _remoteNames = /*#__PURE__*/ new WeakMap();
|
|
64
|
-
class BackendFederationRuntime extends ModuleFederation {
|
|
65
|
-
loadRemote(request, loadOptions) {
|
|
66
|
-
const remoteName = parseRemoteRequest(request);
|
|
67
|
-
if (void 0 === remoteName) return Promise.reject(new Error(`[BFF][Effect] Invalid backend federation request ${request}.`));
|
|
68
|
-
if (!_class_private_field_get(this, _remoteNames).has(remoteName)) return Promise.reject(new Error(`[BFF][Effect] Missing backend federation remote ${remoteName}.`));
|
|
69
|
-
return super.loadRemote(request, loadOptions);
|
|
70
|
-
}
|
|
71
|
-
constructor(options, remotes){
|
|
72
|
-
super({
|
|
73
|
-
name: options.hostName,
|
|
74
|
-
remotes: isolateRegisteredRemoteEntries(remotes),
|
|
75
|
-
shared: {},
|
|
76
|
-
plugins: [
|
|
77
|
-
createLocalEntryPlugin(options, remotes)
|
|
78
|
-
]
|
|
79
|
-
}), _class_private_field_init(this, _remoteNames, {
|
|
80
|
-
writable: true,
|
|
81
|
-
value: void 0
|
|
82
|
-
});
|
|
83
|
-
_class_private_field_set(this, _remoteNames, new Set(remotes.map((remote)=>remote.name)));
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
4
|
function createBackendFederationRuntime(options) {
|
|
87
5
|
const remotes = collectRemotes(options);
|
|
88
|
-
return new
|
|
6
|
+
return new ModuleFederation({
|
|
7
|
+
name: options.hostName,
|
|
8
|
+
remotes: remotes.map(({ name, entry, type })=>({
|
|
9
|
+
name,
|
|
10
|
+
entry,
|
|
11
|
+
...type ? {
|
|
12
|
+
type
|
|
13
|
+
} : {}
|
|
14
|
+
})),
|
|
15
|
+
shared: options.shared ?? {},
|
|
16
|
+
plugins: [
|
|
17
|
+
...options.plugins ?? [],
|
|
18
|
+
createBackendFederationIntegrityPlugin(options, remotes)
|
|
19
|
+
]
|
|
20
|
+
});
|
|
89
21
|
}
|
|
90
22
|
export { createBackendFederationRuntime };
|
|
@@ -129,6 +129,9 @@ async function loadBackendFederatedEffectApiFromManifest(options) {
|
|
|
129
129
|
...options.plugins ? {
|
|
130
130
|
plugins: options.plugins
|
|
131
131
|
} : {},
|
|
132
|
+
...options.shared ? {
|
|
133
|
+
shared: options.shared
|
|
134
|
+
} : {},
|
|
132
135
|
entryPolicy: {
|
|
133
136
|
...options.entryPolicy,
|
|
134
137
|
signal: scope.signal,
|
|
@@ -1,11 +1,13 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { evaluateNodeBackendFederationCommonJs } from "@modern-js/server-runtime-extensions/backend-federation-security/node";
|
|
2
|
+
import { withEffectBffHostShared } from "../backend-federation/node-shared.mjs";
|
|
2
3
|
import { loadBackendFederatedEffectApiFromManifest } from "./load.mjs";
|
|
3
|
-
function node_loadBackendFederatedEffectApiFromManifest(options) {
|
|
4
|
+
async function node_loadBackendFederatedEffectApiFromManifest(options) {
|
|
4
5
|
return loadBackendFederatedEffectApiFromManifest({
|
|
5
6
|
...options,
|
|
7
|
+
shared: withEffectBffHostShared(options.shared),
|
|
6
8
|
entryPolicy: {
|
|
7
9
|
...options.entryPolicy,
|
|
8
|
-
evaluateCommonJs: options.entryPolicy?.evaluateCommonJs ??
|
|
10
|
+
evaluateCommonJs: options.entryPolicy?.evaluateCommonJs ?? evaluateNodeBackendFederationCommonJs
|
|
9
11
|
}
|
|
10
12
|
});
|
|
11
13
|
}
|
|
@@ -31,13 +31,7 @@ function resolveBackendFederationRemoteFromManifest(manifest, remoteOverride = {
|
|
|
31
31
|
...verification ? {
|
|
32
32
|
verification
|
|
33
33
|
} : {},
|
|
34
|
-
|
|
35
|
-
entryGlobalName: remoteOverride.entryGlobalName
|
|
36
|
-
} : {},
|
|
37
|
-
expose,
|
|
38
|
-
...remoteOverride.shareScope ? {
|
|
39
|
-
shareScope: remoteOverride.shareScope
|
|
40
|
-
} : {}
|
|
34
|
+
expose
|
|
41
35
|
};
|
|
42
36
|
}
|
|
43
37
|
export { resolveBackendFederationRemoteFromManifest };
|
|
@@ -79,6 +79,7 @@ const nodeBuiltins = new Set([
|
|
|
79
79
|
...builtinModules.map((name)=>`node:${name}`)
|
|
80
80
|
]);
|
|
81
81
|
const dependencyResolutionMarker = 'modern-js-effect-dependency-resolution';
|
|
82
|
+
const BFF_RUNTIME_PACKAGE = /^@modern-js\/(?:bff-effect|plugin-bff)(?:\/|$)/;
|
|
82
83
|
async function isEsmOnlyFile(filename) {
|
|
83
84
|
const extension = node_path.extname(filename).toLowerCase();
|
|
84
85
|
if ('.mjs' === extension || '.mts' === extension) return true;
|
|
@@ -107,6 +108,10 @@ function externalizeInstalledDependencies(options) {
|
|
|
107
108
|
buildApi.onResolve({
|
|
108
109
|
filter: /.*/
|
|
109
110
|
}, async (args)=>{
|
|
111
|
+
if (BFF_RUNTIME_PACKAGE.test(args.path)) return {
|
|
112
|
+
external: true,
|
|
113
|
+
path: args.path
|
|
114
|
+
};
|
|
110
115
|
if (!isBareSpecifier(args.path) || nodeBuiltins.has(args.path) || args.pluginData?.[dependencyResolutionMarker] === true) return nodeBuiltins.has(args.path) ? {
|
|
111
116
|
external: true,
|
|
112
117
|
path: args.path
|
|
@@ -3,7 +3,7 @@ import { BACKEND_FEDERATION_CONTRACT_VERSION, BACKEND_FEDERATION_EFFECT_EXPOSE,
|
|
|
3
3
|
import { createBackendFederationLoadEntryPlugin, createBackendFederationRuntime } from "./edge-runtime.mjs";
|
|
4
4
|
import { normalizeExpose } from "./utils.mjs";
|
|
5
5
|
function loadBackendFederatedEffectApi(options) {
|
|
6
|
-
if (void 0 !== options.runtime || void 0 !== options.entryPolicy) return Promise.reject(new Error('[BFF][Effect] Edge backend federation does not execute custom runtimes or
|
|
6
|
+
if (void 0 !== options.runtime || void 0 !== options.entryPolicy || void 0 !== options.shared) return Promise.reject(new Error('[BFF][Effect] Edge backend federation does not execute custom runtimes, entry evaluators or host shares. Register a static or service-binding entry provider.'));
|
|
7
7
|
const remoteName = options.remote?.name ?? options.remoteName;
|
|
8
8
|
if (void 0 === remoteName || 0 === remoteName.length) return Promise.reject(new Error('[BFF][Effect] Missing backend federation remote name.'));
|
|
9
9
|
if (!options.expected?.unitId || !options.expected.buildMarker) return Promise.reject(new Error('[BFF][Effect] Backend federation requires expected.unitId and expected.buildMarker.'));
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import "node:module";
|
|
2
|
+
import { evaluateBackendFederationCommonJsEntry, loadVerifiedBackendFederationEntry } from "@modern-js/server-runtime-extensions/backend-federation-security";
|
|
3
|
+
import { Module } from "@module-federation/runtime";
|
|
4
|
+
const PROVIDED_ENTRY_SCHEME = /^(?:binding|service|static):/u;
|
|
5
|
+
function decodeDataUrl(remote) {
|
|
6
|
+
const commaIndex = remote.entry.indexOf(',');
|
|
7
|
+
if (commaIndex < 0) throw new Error(`[BFF][Effect] Backend federation remote ${remote.name} has an invalid data URL entry.`);
|
|
8
|
+
return new TextEncoder().encode(decodeURIComponent(remote.entry.slice(commaIndex + 1)));
|
|
9
|
+
}
|
|
10
|
+
async function importContainer(remote) {
|
|
11
|
+
const namespace = await import(remote.entry);
|
|
12
|
+
return namespace.default ?? namespace;
|
|
13
|
+
}
|
|
14
|
+
function loadContainer(remote, options) {
|
|
15
|
+
const verified = Boolean(remote.verification || options.entryPolicy?.expected);
|
|
16
|
+
const scheme = /^[a-z][a-z0-9+.-]*:/iu.exec(remote.entry)?.[0].toLowerCase();
|
|
17
|
+
if ('https:' === scheme || 'http:' === scheme) {
|
|
18
|
+
if (!verified) throw new Error(`[BFF][Effect] Backend federation remote ${remote.name} requires verified entry bytes before network execution.`);
|
|
19
|
+
return loadVerifiedBackendFederationEntry({
|
|
20
|
+
...options.entryPolicy,
|
|
21
|
+
remote,
|
|
22
|
+
...remote.verification ? {
|
|
23
|
+
verification: remote.verification
|
|
24
|
+
} : {}
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
if (verified) throw new Error(`[BFF][Effect] Backend federation remote ${remote.name} declares entry verification, but only http(s) entries are fetched and verified.`);
|
|
28
|
+
if ('data:' === scheme && 'module' !== remote.type) return Promise.resolve(evaluateBackendFederationCommonJsEntry(remote, decodeDataUrl(remote), options.entryPolicy?.evaluateCommonJs));
|
|
29
|
+
if ('data:' === scheme || 'file:' === scheme) return importContainer(remote);
|
|
30
|
+
throw new Error(`[BFF][Effect] Backend federation remote ${remote.name} uses unsupported entry ${remote.entry}. Use a verified http(s) entry, a data: or file: entry, or a binding:, service: or static: entry served by createBackendFederationLoadEntryPlugin().`);
|
|
31
|
+
}
|
|
32
|
+
function registerRemoteContainer(origin, remoteInfo, container) {
|
|
33
|
+
const module = new Module({
|
|
34
|
+
host: origin,
|
|
35
|
+
remoteInfo
|
|
36
|
+
});
|
|
37
|
+
module.remoteEntryExports = container;
|
|
38
|
+
origin.moduleCache.set(remoteInfo.name, module);
|
|
39
|
+
}
|
|
40
|
+
function createBackendFederationIntegrityPlugin(options, remotes) {
|
|
41
|
+
const loading = new Map();
|
|
42
|
+
const register = async (origin, remoteInfo)=>{
|
|
43
|
+
const remote = remotes.find((candidate)=>candidate.name === remoteInfo.name);
|
|
44
|
+
if (void 0 === remote) throw new Error(`[BFF][Effect] Missing backend federation remote ${remoteInfo.name}.`);
|
|
45
|
+
if (PROVIDED_ENTRY_SCHEME.test(remote.entry)) {
|
|
46
|
+
if (origin.moduleCache.get(remote.name)?.remoteEntryExports === void 0) throw new Error(`[BFF][Effect] Backend federation remote ${remote.name} has no entry provider for ${remote.entry}. Pass createBackendFederationLoadEntryPlugin() in plugins.`);
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
const container = await loadContainer(remote, options);
|
|
50
|
+
if ('function' != typeof container?.get) throw new Error(`[BFF][Effect] Backend federation remote ${remote.name} entry must expose get().`);
|
|
51
|
+
registerRemoteContainer(origin, remoteInfo, container);
|
|
52
|
+
};
|
|
53
|
+
return {
|
|
54
|
+
name: 'modernjs-backend-federation-integrity',
|
|
55
|
+
afterMatchRemote ({ origin, remoteInfo }) {
|
|
56
|
+
if (void 0 === remoteInfo) return;
|
|
57
|
+
let pending = loading.get(remoteInfo.name);
|
|
58
|
+
if (void 0 === pending) {
|
|
59
|
+
pending = register(origin, remoteInfo);
|
|
60
|
+
loading.set(remoteInfo.name, pending);
|
|
61
|
+
pending.catch(()=>{
|
|
62
|
+
if (loading.get(remoteInfo.name) === pending) loading.delete(remoteInfo.name);
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
return pending;
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
export { PROVIDED_ENTRY_SCHEME, createBackendFederationIntegrityPlugin, registerRemoteContainer };
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import "node:module";
|
|
2
|
+
import { createRequire } from "node:module";
|
|
3
|
+
const fromAdapter = createRequire(import.meta.url);
|
|
4
|
+
const effectPackageJson = fromAdapter.resolve('@modern-js/bff-effect/package.json');
|
|
5
|
+
const fromEffectPackage = createRequire(effectPackageJson);
|
|
6
|
+
let hostShared;
|
|
7
|
+
function effectBffHostShared() {
|
|
8
|
+
if (void 0 !== hostShared) return hostShared;
|
|
9
|
+
const { EFFECT_BFF_CONTEXT_STORAGE_SHARE, EFFECT_BFF_HANDLER_FACTORY_REGISTRY_SHARE } = fromEffectPackage('@modern-js/bff-effect/effect-edge');
|
|
10
|
+
const { getEffectContextStorage } = fromEffectPackage('@modern-js/bff-effect/context');
|
|
11
|
+
const handlerFactoryRegistry = fromEffectPackage("#effect-entry-shape-registry");
|
|
12
|
+
const version = fromAdapter(effectPackageJson).version;
|
|
13
|
+
hostShared = {
|
|
14
|
+
[EFFECT_BFF_HANDLER_FACTORY_REGISTRY_SHARE]: {
|
|
15
|
+
version,
|
|
16
|
+
lib: ()=>handlerFactoryRegistry,
|
|
17
|
+
shareConfig: {
|
|
18
|
+
singleton: true,
|
|
19
|
+
requiredVersion: false
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
[EFFECT_BFF_CONTEXT_STORAGE_SHARE]: {
|
|
23
|
+
version,
|
|
24
|
+
lib: getEffectContextStorage,
|
|
25
|
+
shareConfig: {
|
|
26
|
+
singleton: true,
|
|
27
|
+
requiredVersion: false
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
return hostShared;
|
|
32
|
+
}
|
|
33
|
+
function withEffectBffHostShared(shared) {
|
|
34
|
+
const host = effectBffHostShared();
|
|
35
|
+
const reserved = Object.keys(host).find((name)=>shared && name in shared);
|
|
36
|
+
if (void 0 !== reserved) throw new Error(`[BFF][Effect] Share ${reserved} is reserved for the Node host. Remove it from options.shared.`);
|
|
37
|
+
return {
|
|
38
|
+
...shared,
|
|
39
|
+
...host
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
export { effectBffHostShared, withEffectBffHostShared };
|
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
import "node:module";
|
|
2
|
+
import { evaluateNodeBackendFederationCommonJs } from "@modern-js/server-runtime-extensions/backend-federation-security/node";
|
|
2
3
|
import { loadBackendFederatedEffectApi } from "./load.mjs";
|
|
3
|
-
import {
|
|
4
|
-
function node_loadBackendFederatedEffectApi(options) {
|
|
4
|
+
import { effectBffHostShared, withEffectBffHostShared } from "./node-shared.mjs";
|
|
5
|
+
async function node_loadBackendFederatedEffectApi(options) {
|
|
5
6
|
return loadBackendFederatedEffectApi({
|
|
6
7
|
...options,
|
|
8
|
+
shared: withEffectBffHostShared(options.shared),
|
|
7
9
|
entryPolicy: {
|
|
8
10
|
...options.entryPolicy,
|
|
9
|
-
evaluateCommonJs: options.entryPolicy?.evaluateCommonJs ??
|
|
11
|
+
evaluateCommonJs: options.entryPolicy?.evaluateCommonJs ?? evaluateNodeBackendFederationCommonJs
|
|
10
12
|
}
|
|
11
13
|
});
|
|
12
14
|
}
|
|
13
|
-
export { node_loadBackendFederatedEffectApi as loadBackendFederatedEffectApi };
|
|
15
|
+
export { effectBffHostShared, node_loadBackendFederatedEffectApi as loadBackendFederatedEffectApi };
|
|
@@ -1,25 +1,44 @@
|
|
|
1
1
|
import "node:module";
|
|
2
|
+
import { PROVIDED_ENTRY_SCHEME, registerRemoteContainer } from "./integrity-plugin.mjs";
|
|
3
|
+
function toContainer(entry) {
|
|
4
|
+
return {
|
|
5
|
+
get (id) {
|
|
6
|
+
return ()=>Promise.resolve(entry.get(id)).then((factory)=>factory());
|
|
7
|
+
},
|
|
8
|
+
init (...args) {
|
|
9
|
+
return entry.init?.(...args);
|
|
10
|
+
}
|
|
11
|
+
};
|
|
12
|
+
}
|
|
2
13
|
function createBackendFederationLoadEntryPlugin(options) {
|
|
14
|
+
const provided = new WeakMap();
|
|
3
15
|
return {
|
|
4
16
|
name: 'modernjs-backend-federation-load-entry',
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
17
|
+
afterMatchRemote ({ origin, remoteInfo }) {
|
|
18
|
+
if (void 0 === remoteInfo || !PROVIDED_ENTRY_SCHEME.test(remoteInfo.entry)) return;
|
|
19
|
+
let entries = provided.get(origin);
|
|
20
|
+
if (entries?.has(remoteInfo.name) !== true && origin.moduleCache.get(remoteInfo.name)?.remoteEntryExports !== void 0) return;
|
|
21
|
+
if (void 0 === entries) {
|
|
22
|
+
entries = new Map();
|
|
23
|
+
provided.set(origin, entries);
|
|
24
|
+
}
|
|
25
|
+
const runtimeEntries = entries;
|
|
26
|
+
let pending = runtimeEntries.get(remoteInfo.name);
|
|
27
|
+
if (void 0 === pending) {
|
|
28
|
+
pending = Promise.resolve(options.resolveEntry({
|
|
29
|
+
name: remoteInfo.name,
|
|
30
|
+
entry: remoteInfo.entry,
|
|
31
|
+
type: remoteInfo.type
|
|
32
|
+
})).then((entry)=>{
|
|
33
|
+
if (void 0 !== entry) registerRemoteContainer(origin, remoteInfo, toContainer(entry));
|
|
34
|
+
});
|
|
35
|
+
const settled = pending;
|
|
36
|
+
runtimeEntries.set(remoteInfo.name, settled);
|
|
37
|
+
settled.catch(()=>{
|
|
38
|
+
if (runtimeEntries.get(remoteInfo.name) === settled) runtimeEntries.delete(remoteInfo.name);
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
return pending;
|
|
23
42
|
}
|
|
24
43
|
};
|
|
25
44
|
}
|
|
@@ -1,91 +1,23 @@
|
|
|
1
1
|
import "node:module";
|
|
2
2
|
import { ModuleFederation } from "@module-federation/runtime";
|
|
3
|
-
import {
|
|
4
|
-
import { createBackendFederationLoadEntryPlugin } from "./plugin.mjs";
|
|
3
|
+
import { createBackendFederationIntegrityPlugin } from "./integrity-plugin.mjs";
|
|
5
4
|
import { collectRemotes } from "./remotes.mjs";
|
|
6
|
-
function _check_private_redeclaration(obj, privateCollection) {
|
|
7
|
-
if (privateCollection.has(obj)) throw new TypeError("Cannot initialize the same private elements twice on an object");
|
|
8
|
-
}
|
|
9
|
-
function _class_apply_descriptor_get(receiver, descriptor) {
|
|
10
|
-
if (descriptor.get) return descriptor.get.call(receiver);
|
|
11
|
-
return descriptor.value;
|
|
12
|
-
}
|
|
13
|
-
function _class_apply_descriptor_set(receiver, descriptor, value) {
|
|
14
|
-
if (descriptor.set) descriptor.set.call(receiver, value);
|
|
15
|
-
else {
|
|
16
|
-
if (!descriptor.writable) throw new TypeError("attempted to set read only private field");
|
|
17
|
-
descriptor.value = value;
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
function _class_extract_field_descriptor(receiver, privateMap, action) {
|
|
21
|
-
if (!privateMap.has(receiver)) throw new TypeError("attempted to " + action + " private field on non-instance");
|
|
22
|
-
return privateMap.get(receiver);
|
|
23
|
-
}
|
|
24
|
-
function _class_private_field_get(receiver, privateMap) {
|
|
25
|
-
var descriptor = _class_extract_field_descriptor(receiver, privateMap, "get");
|
|
26
|
-
return _class_apply_descriptor_get(receiver, descriptor);
|
|
27
|
-
}
|
|
28
|
-
function _class_private_field_init(obj, privateMap, value) {
|
|
29
|
-
_check_private_redeclaration(obj, privateMap);
|
|
30
|
-
privateMap.set(obj, value);
|
|
31
|
-
}
|
|
32
|
-
function _class_private_field_set(receiver, privateMap, value) {
|
|
33
|
-
var descriptor = _class_extract_field_descriptor(receiver, privateMap, "set");
|
|
34
|
-
_class_apply_descriptor_set(receiver, descriptor, value);
|
|
35
|
-
return value;
|
|
36
|
-
}
|
|
37
|
-
let nextRuntimeId = 0;
|
|
38
|
-
function parseRemoteRequest(request) {
|
|
39
|
-
const [remoteName, ...exposeParts] = request.split('/');
|
|
40
|
-
if (void 0 === remoteName || 0 === remoteName.length || 0 === exposeParts.length) return;
|
|
41
|
-
return remoteName;
|
|
42
|
-
}
|
|
43
|
-
function createLocalEntryPlugin(options, remotes) {
|
|
44
|
-
return createBackendFederationLoadEntryPlugin({
|
|
45
|
-
resolveEntry (resolvedRemote) {
|
|
46
|
-
const remote = remotes.find((candidate)=>candidate.name === resolvedRemote.name);
|
|
47
|
-
if (void 0 === remote) return;
|
|
48
|
-
return {
|
|
49
|
-
get (expose) {
|
|
50
|
-
return ()=>loadBackendFederationExpose(remote, expose, options);
|
|
51
|
-
}
|
|
52
|
-
};
|
|
53
|
-
}
|
|
54
|
-
});
|
|
55
|
-
}
|
|
56
|
-
function isolateRegisteredRemoteEntries(remotes) {
|
|
57
|
-
const runtimeId = nextRuntimeId;
|
|
58
|
-
nextRuntimeId += 1;
|
|
59
|
-
return remotes.map((remote)=>({
|
|
60
|
-
...remote,
|
|
61
|
-
entry: `${remote.entry}#modernjs-backend-runtime-${runtimeId}`
|
|
62
|
-
}));
|
|
63
|
-
}
|
|
64
|
-
var _remoteNames = /*#__PURE__*/ new WeakMap();
|
|
65
|
-
class BackendFederationRuntime extends ModuleFederation {
|
|
66
|
-
loadRemote(request, loadOptions) {
|
|
67
|
-
const remoteName = parseRemoteRequest(request);
|
|
68
|
-
if (void 0 === remoteName) return Promise.reject(new Error(`[BFF][Effect] Invalid backend federation request ${request}.`));
|
|
69
|
-
if (!_class_private_field_get(this, _remoteNames).has(remoteName)) return Promise.reject(new Error(`[BFF][Effect] Missing backend federation remote ${remoteName}.`));
|
|
70
|
-
return super.loadRemote(request, loadOptions);
|
|
71
|
-
}
|
|
72
|
-
constructor(options, remotes){
|
|
73
|
-
super({
|
|
74
|
-
name: options.hostName,
|
|
75
|
-
remotes: isolateRegisteredRemoteEntries(remotes),
|
|
76
|
-
shared: {},
|
|
77
|
-
plugins: [
|
|
78
|
-
createLocalEntryPlugin(options, remotes)
|
|
79
|
-
]
|
|
80
|
-
}), _class_private_field_init(this, _remoteNames, {
|
|
81
|
-
writable: true,
|
|
82
|
-
value: void 0
|
|
83
|
-
});
|
|
84
|
-
_class_private_field_set(this, _remoteNames, new Set(remotes.map((remote)=>remote.name)));
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
5
|
function createBackendFederationRuntime(options) {
|
|
88
6
|
const remotes = collectRemotes(options);
|
|
89
|
-
return new
|
|
7
|
+
return new ModuleFederation({
|
|
8
|
+
name: options.hostName,
|
|
9
|
+
remotes: remotes.map(({ name, entry, type })=>({
|
|
10
|
+
name,
|
|
11
|
+
entry,
|
|
12
|
+
...type ? {
|
|
13
|
+
type
|
|
14
|
+
} : {}
|
|
15
|
+
})),
|
|
16
|
+
shared: options.shared ?? {},
|
|
17
|
+
plugins: [
|
|
18
|
+
...options.plugins ?? [],
|
|
19
|
+
createBackendFederationIntegrityPlugin(options, remotes)
|
|
20
|
+
]
|
|
21
|
+
});
|
|
90
22
|
}
|
|
91
23
|
export { createBackendFederationRuntime };
|
|
@@ -130,6 +130,9 @@ async function loadBackendFederatedEffectApiFromManifest(options) {
|
|
|
130
130
|
...options.plugins ? {
|
|
131
131
|
plugins: options.plugins
|
|
132
132
|
} : {},
|
|
133
|
+
...options.shared ? {
|
|
134
|
+
shared: options.shared
|
|
135
|
+
} : {},
|
|
133
136
|
entryPolicy: {
|
|
134
137
|
...options.entryPolicy,
|
|
135
138
|
signal: scope.signal,
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import "node:module";
|
|
2
|
-
import {
|
|
2
|
+
import { evaluateNodeBackendFederationCommonJs } from "@modern-js/server-runtime-extensions/backend-federation-security/node";
|
|
3
|
+
import { withEffectBffHostShared } from "../backend-federation/node-shared.mjs";
|
|
3
4
|
import { loadBackendFederatedEffectApiFromManifest } from "./load.mjs";
|
|
4
|
-
function node_loadBackendFederatedEffectApiFromManifest(options) {
|
|
5
|
+
async function node_loadBackendFederatedEffectApiFromManifest(options) {
|
|
5
6
|
return loadBackendFederatedEffectApiFromManifest({
|
|
6
7
|
...options,
|
|
8
|
+
shared: withEffectBffHostShared(options.shared),
|
|
7
9
|
entryPolicy: {
|
|
8
10
|
...options.entryPolicy,
|
|
9
|
-
evaluateCommonJs: options.entryPolicy?.evaluateCommonJs ??
|
|
11
|
+
evaluateCommonJs: options.entryPolicy?.evaluateCommonJs ?? evaluateNodeBackendFederationCommonJs
|
|
10
12
|
}
|
|
11
13
|
});
|
|
12
14
|
}
|
|
@@ -32,13 +32,7 @@ function resolveBackendFederationRemoteFromManifest(manifest, remoteOverride = {
|
|
|
32
32
|
...verification ? {
|
|
33
33
|
verification
|
|
34
34
|
} : {},
|
|
35
|
-
|
|
36
|
-
entryGlobalName: remoteOverride.entryGlobalName
|
|
37
|
-
} : {},
|
|
38
|
-
expose,
|
|
39
|
-
...remoteOverride.shareScope ? {
|
|
40
|
-
shareScope: remoteOverride.shareScope
|
|
41
|
-
} : {}
|
|
35
|
+
expose
|
|
42
36
|
};
|
|
43
37
|
}
|
|
44
38
|
export { resolveBackendFederationRemoteFromManifest };
|
|
@@ -80,6 +80,7 @@ const nodeBuiltins = new Set([
|
|
|
80
80
|
...builtinModules.map((name)=>`node:${name}`)
|
|
81
81
|
]);
|
|
82
82
|
const dependencyResolutionMarker = 'modern-js-effect-dependency-resolution';
|
|
83
|
+
const BFF_RUNTIME_PACKAGE = /^@modern-js\/(?:bff-effect|plugin-bff)(?:\/|$)/;
|
|
83
84
|
async function isEsmOnlyFile(filename) {
|
|
84
85
|
const extension = node_path.extname(filename).toLowerCase();
|
|
85
86
|
if ('.mjs' === extension || '.mts' === extension) return true;
|
|
@@ -108,6 +109,10 @@ function externalizeInstalledDependencies(options) {
|
|
|
108
109
|
buildApi.onResolve({
|
|
109
110
|
filter: /.*/
|
|
110
111
|
}, async (args)=>{
|
|
112
|
+
if (BFF_RUNTIME_PACKAGE.test(args.path)) return {
|
|
113
|
+
external: true,
|
|
114
|
+
path: args.path
|
|
115
|
+
};
|
|
111
116
|
if (!isBareSpecifier(args.path) || nodeBuiltins.has(args.path) || args.pluginData?.[dependencyResolutionMarker] === true) return nodeBuiltins.has(args.path) ? {
|
|
112
117
|
external: true,
|
|
113
118
|
path: args.path
|