@bleedingdev/modern-js-federation-runtime 3.9.0-ultramodern.9 → 3.9.3-ultramodern.10
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/dist/cjs/module-federation/consume-surface.js +6 -16
- package/dist/cjs/module-federation/distributed-ssr.js +6 -1
- package/dist/cjs/module-federation/index.js +13 -239
- package/dist/cjs/module-federation/manifest-recovery-runtime-plugin.js +1 -1
- package/dist/cjs/module-federation/native-browser-dev-client.js +320 -0
- package/dist/cjs/module-federation/native-browser-dev-refresh.js +226 -0
- package/dist/cjs/module-federation/native-browser-dev-runtime-plugin.js +233 -0
- package/dist/cjs/module-federation/native-dev-refresh-runtime-plugin.js +267 -0
- package/dist/cjs/module-federation/renderer-contract.js +159 -0
- package/dist/cjs/module-federation/renderer-runtime-plugin.js +268 -0
- package/dist/cjs/module-federation/telemetry.js +214 -0
- package/dist/esm/module-federation/consume-surface.mjs +3 -13
- package/dist/esm/module-federation/distributed-ssr.mjs +6 -1
- package/dist/esm/module-federation/index.mjs +1 -111
- package/dist/esm/module-federation/manifest-recovery-runtime-plugin.mjs +1 -1
- package/dist/esm/module-federation/native-browser-dev-client.mjs +276 -0
- package/dist/esm/module-federation/native-browser-dev-refresh.mjs +182 -0
- package/dist/esm/module-federation/native-browser-dev-runtime-plugin.mjs +183 -0
- package/dist/esm/module-federation/native-dev-refresh-runtime-plugin.mjs +229 -0
- package/dist/esm/module-federation/renderer-contract.mjs +103 -0
- package/dist/esm/module-federation/renderer-runtime-plugin.mjs +225 -0
- package/dist/esm/module-federation/telemetry.mjs +142 -0
- package/dist/esm-node/module-federation/consume-surface.mjs +3 -13
- package/dist/esm-node/module-federation/distributed-ssr.mjs +6 -1
- package/dist/esm-node/module-federation/index.mjs +1 -111
- package/dist/esm-node/module-federation/manifest-recovery-runtime-plugin.mjs +1 -1
- package/dist/esm-node/module-federation/native-browser-dev-client.mjs +277 -0
- package/dist/esm-node/module-federation/native-browser-dev-refresh.mjs +183 -0
- package/dist/esm-node/module-federation/native-browser-dev-runtime-plugin.mjs +184 -0
- package/dist/esm-node/module-federation/native-dev-refresh-runtime-plugin.mjs +230 -0
- package/dist/esm-node/module-federation/renderer-contract.mjs +104 -0
- package/dist/esm-node/module-federation/renderer-runtime-plugin.mjs +226 -0
- package/dist/esm-node/module-federation/telemetry.mjs +143 -0
- package/dist/types/module-federation/consume-surface.d.ts +5 -5
- package/dist/types/module-federation/distributed-ssr.d.ts +18 -1
- package/dist/types/module-federation/index.d.ts +1 -67
- package/dist/types/module-federation/native-browser-dev-client.d.ts +41 -0
- package/dist/types/module-federation/native-browser-dev-refresh.d.ts +86 -0
- package/dist/types/module-federation/native-browser-dev-runtime-plugin.d.ts +40 -0
- package/dist/types/module-federation/native-dev-refresh-runtime-plugin.d.ts +90 -0
- package/dist/types/module-federation/renderer-contract.d.ts +20 -0
- package/dist/types/module-federation/renderer-runtime-plugin.d.ts +98 -0
- package/dist/types/module-federation/telemetry.d.ts +71 -0
- package/package.json +102 -19
- package/src/module-federation/consume-surface.ts +18 -33
- package/src/module-federation/distributed-ssr.tsx +27 -2
- package/src/module-federation/index.ts +1 -267
- package/src/module-federation/manifest-recovery-runtime-plugin.ts +5 -1
- package/src/module-federation/native-browser-dev-client.ts +424 -0
- package/src/module-federation/native-browser-dev-refresh.ts +373 -0
- package/src/module-federation/native-browser-dev-runtime-plugin.ts +276 -0
- package/src/module-federation/native-dev-refresh-runtime-plugin.ts +445 -0
- package/src/module-federation/renderer-contract.ts +176 -0
- package/src/module-federation/renderer-runtime-plugin.ts +401 -0
- package/src/module-federation/telemetry.ts +297 -0
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
2
|
+
const LOAD_WITH_CAPTURE = Symbol.for('ultramodern.federation.load-with-capture');
|
|
3
|
+
function nativeDevRefreshRuntimePlugin(options) {
|
|
4
|
+
if (!options.revisionHeader || !Number.isSafeInteger(options.requestTimeout) || options.requestTimeout <= 0) throw new TypeError('Native federation dev refresh requires a bounded revision probe.');
|
|
5
|
+
const hosts = new WeakMap();
|
|
6
|
+
const calls = new AsyncLocalStorage();
|
|
7
|
+
const probe = (host, lane, expose)=>{
|
|
8
|
+
if (lane.probe) return lane.probe;
|
|
9
|
+
const info = lane.info;
|
|
10
|
+
if (!info) throw new Error('Native federation dev remote has no admitted entry.');
|
|
11
|
+
const pending = (async ()=>{
|
|
12
|
+
const init = {
|
|
13
|
+
method: 'HEAD',
|
|
14
|
+
cache: 'no-store',
|
|
15
|
+
signal: AbortSignal.timeout(options.requestTimeout)
|
|
16
|
+
};
|
|
17
|
+
let clearDeadline;
|
|
18
|
+
const signal = init.signal;
|
|
19
|
+
const deadline = new Promise((_resolve, reject)=>{
|
|
20
|
+
const abort = ()=>reject(signal.reason);
|
|
21
|
+
signal.addEventListener('abort', abort, {
|
|
22
|
+
once: true
|
|
23
|
+
});
|
|
24
|
+
clearDeadline = ()=>signal.removeEventListener('abort', abort);
|
|
25
|
+
});
|
|
26
|
+
const request = (async ()=>{
|
|
27
|
+
const hooked = await host.loaderHook.lifecycle.fetch.emit(info.entry, init, info, {
|
|
28
|
+
initiator: 'loadRemote',
|
|
29
|
+
id: expose && '.' !== expose ? `${info.name}/${expose.replace(/^\.\//u, '')}` : info.name,
|
|
30
|
+
resourceType: 'remoteEntry',
|
|
31
|
+
url: info.entry,
|
|
32
|
+
expose
|
|
33
|
+
});
|
|
34
|
+
const response = hooked instanceof Response ? hooked : await globalThis.fetch(info.entry, init);
|
|
35
|
+
if (signal.aborted) {
|
|
36
|
+
response.body?.cancel().catch(()=>{});
|
|
37
|
+
throw signal.reason;
|
|
38
|
+
}
|
|
39
|
+
return response;
|
|
40
|
+
})();
|
|
41
|
+
let response;
|
|
42
|
+
try {
|
|
43
|
+
response = await Promise.race([
|
|
44
|
+
request,
|
|
45
|
+
deadline
|
|
46
|
+
]);
|
|
47
|
+
} finally{
|
|
48
|
+
clearDeadline();
|
|
49
|
+
}
|
|
50
|
+
response.body?.cancel().catch(()=>{});
|
|
51
|
+
if (!response.ok) throw Object.assign(new Error(`Native federation dev revision request failed: ${response.status} ${info.entry}`), {
|
|
52
|
+
status: response.status
|
|
53
|
+
});
|
|
54
|
+
const revision = response.headers.get(options.revisionHeader);
|
|
55
|
+
if (null !== revision && !/^[a-f0-9]{64}$/u.test(revision)) throw new Error('Native federation dev remote returned an invalid graph revision.');
|
|
56
|
+
if (null === revision && 'string' == typeof lane.revision) throw new Error('Native federation dev remote lost its completed graph revision.');
|
|
57
|
+
return revision;
|
|
58
|
+
})();
|
|
59
|
+
lane.probe = pending;
|
|
60
|
+
const clear = ()=>{
|
|
61
|
+
if (lane.probe === pending) lane.probe = void 0;
|
|
62
|
+
};
|
|
63
|
+
pending.then(clear, clear);
|
|
64
|
+
return pending;
|
|
65
|
+
};
|
|
66
|
+
return {
|
|
67
|
+
name: 'ultramodern-native-federation-dev-refresh',
|
|
68
|
+
beforeInit (args) {
|
|
69
|
+
const host = args.origin;
|
|
70
|
+
if (hosts.has(host)) return args;
|
|
71
|
+
const lanes = new Map(options.remotes.map((remote)=>[
|
|
72
|
+
remote.name,
|
|
73
|
+
{
|
|
74
|
+
remote: {
|
|
75
|
+
...remote
|
|
76
|
+
},
|
|
77
|
+
active: new Set()
|
|
78
|
+
}
|
|
79
|
+
]));
|
|
80
|
+
hosts.set(host, lanes);
|
|
81
|
+
const load = host.loadRemote.bind(host);
|
|
82
|
+
const loadWithCapture = async (id, loadOptions, capture)=>{
|
|
83
|
+
const parent = calls.getStore();
|
|
84
|
+
const lease = {
|
|
85
|
+
host,
|
|
86
|
+
...parent?.host === host ? {
|
|
87
|
+
parent
|
|
88
|
+
} : {}
|
|
89
|
+
};
|
|
90
|
+
return calls.run(lease, async ()=>{
|
|
91
|
+
let success = false;
|
|
92
|
+
try {
|
|
93
|
+
const module = await load(id, loadOptions);
|
|
94
|
+
const captured = capture?.(module, lease.resolution);
|
|
95
|
+
success = null !== module && true === lease.admitted;
|
|
96
|
+
return {
|
|
97
|
+
module,
|
|
98
|
+
captured
|
|
99
|
+
};
|
|
100
|
+
} finally{
|
|
101
|
+
lease.active = false;
|
|
102
|
+
lease.release?.(success);
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
};
|
|
106
|
+
host.loadRemote = async (id, loadOptions)=>(await loadWithCapture(id, loadOptions)).module;
|
|
107
|
+
host[LOAD_WITH_CAPTURE] = (id, capture)=>loadWithCapture(id, void 0, capture);
|
|
108
|
+
return args;
|
|
109
|
+
},
|
|
110
|
+
async beforeRequest (args) {
|
|
111
|
+
const lease = calls.getStore();
|
|
112
|
+
if (!lease || lease.host !== args.origin) return args;
|
|
113
|
+
const lane = [
|
|
114
|
+
...hosts.get(args.origin)?.values() ?? []
|
|
115
|
+
].find(({ remote })=>[
|
|
116
|
+
remote.alias,
|
|
117
|
+
remote.name
|
|
118
|
+
].some((name)=>args.id === name || args.id.startsWith(`${name}/`)));
|
|
119
|
+
if (!lane || lease.lane) return args;
|
|
120
|
+
lease.lane = lane;
|
|
121
|
+
const prefix = [
|
|
122
|
+
lane.remote.alias,
|
|
123
|
+
lane.remote.name
|
|
124
|
+
].find((name)=>args.id === name || args.id.startsWith(`${name}/`));
|
|
125
|
+
const expose = args.id === prefix ? '.' : `./${args.id.slice(prefix.length + 1)}`;
|
|
126
|
+
for(let ancestor = lease.parent; ancestor; ancestor = ancestor.parent)if (ancestor.lane === lane && ancestor.active) {
|
|
127
|
+
let release;
|
|
128
|
+
const finished = new Promise((resolve)=>{
|
|
129
|
+
release = resolve;
|
|
130
|
+
});
|
|
131
|
+
lane.active.add(finished);
|
|
132
|
+
lease.active = true;
|
|
133
|
+
lease.snapshot = ancestor.snapshot;
|
|
134
|
+
lease.release = ()=>{
|
|
135
|
+
lane.active.delete(finished);
|
|
136
|
+
release();
|
|
137
|
+
};
|
|
138
|
+
return args;
|
|
139
|
+
}
|
|
140
|
+
for(;;){
|
|
141
|
+
if (lane.transition) {
|
|
142
|
+
await lane.transition;
|
|
143
|
+
continue;
|
|
144
|
+
}
|
|
145
|
+
const revision = void 0 === lane.revision ? void 0 : null === lane.revision ? null : await probe(args.origin, lane, expose);
|
|
146
|
+
if (lane.transition) {
|
|
147
|
+
await lane.transition;
|
|
148
|
+
continue;
|
|
149
|
+
}
|
|
150
|
+
let finish;
|
|
151
|
+
const finished = new Promise((resolve)=>{
|
|
152
|
+
finish = resolve;
|
|
153
|
+
});
|
|
154
|
+
if (void 0 === lane.revision || revision !== lane.revision) {
|
|
155
|
+
lane.transition = finished;
|
|
156
|
+
lease.release = (success)=>{
|
|
157
|
+
if (success) lane.revision = lane.candidate ?? null;
|
|
158
|
+
if (lane.transition === finished) lane.transition = void 0;
|
|
159
|
+
finish();
|
|
160
|
+
};
|
|
161
|
+
while(lane.active.size)await Promise.allSettled([
|
|
162
|
+
...lane.active
|
|
163
|
+
]);
|
|
164
|
+
lane.candidate = revision;
|
|
165
|
+
if (void 0 !== lane.revision) {
|
|
166
|
+
lease.active = true;
|
|
167
|
+
lease.snapshot = lane.snapshot;
|
|
168
|
+
const retained = new Set();
|
|
169
|
+
for (const scope of Object.values(args.origin.shareScopeMap ?? {}))for (const [pkgName, versions] of Object.entries(scope))if (options.shared?.some((key)=>key === pkgName || key.endsWith('/') && pkgName.startsWith(key)) && Object.values(versions).some((shared)=>shared.loaded || shared.loading || shared.lib)) retained.add(pkgName);
|
|
170
|
+
for (const pkgName of retained){
|
|
171
|
+
if (!args.origin.loadShare) throw new TypeError('Native federation dev refresh requires the host share lifecycle.');
|
|
172
|
+
await args.origin.loadShare(pkgName);
|
|
173
|
+
}
|
|
174
|
+
while(lane.active.size)await Promise.allSettled([
|
|
175
|
+
...lane.active
|
|
176
|
+
]);
|
|
177
|
+
args.origin.registerRemotes([
|
|
178
|
+
{
|
|
179
|
+
...lane.remote
|
|
180
|
+
}
|
|
181
|
+
], {
|
|
182
|
+
force: true
|
|
183
|
+
});
|
|
184
|
+
lease.snapshot = void 0;
|
|
185
|
+
}
|
|
186
|
+
} else {
|
|
187
|
+
lane.active.add(finished);
|
|
188
|
+
lease.release = ()=>{
|
|
189
|
+
lane.active.delete(finished);
|
|
190
|
+
finish();
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
lease.active = true;
|
|
194
|
+
return args;
|
|
195
|
+
}
|
|
196
|
+
},
|
|
197
|
+
loadSnapshot (args) {
|
|
198
|
+
const lease = calls.getStore();
|
|
199
|
+
if (!lease || args.options !== lease.host.options || args.moduleInfo.name !== lease.lane?.remote.name) return args;
|
|
200
|
+
if (void 0 !== lease.snapshot) return {
|
|
201
|
+
...args,
|
|
202
|
+
remoteSnapshot: lease.snapshot
|
|
203
|
+
};
|
|
204
|
+
lease.snapshot = args.remoteSnapshot;
|
|
205
|
+
return args;
|
|
206
|
+
},
|
|
207
|
+
afterLoadSnapshot (args) {
|
|
208
|
+
const lease = calls.getStore();
|
|
209
|
+
if (lease?.host === args.host && args.moduleInfo.name === lease.lane?.remote.name) lease.snapshot = args.remoteSnapshot;
|
|
210
|
+
return args;
|
|
211
|
+
},
|
|
212
|
+
async afterResolve (args) {
|
|
213
|
+
const lane = hosts.get(args.origin)?.get(args.remote.name);
|
|
214
|
+
const lease = calls.getStore();
|
|
215
|
+
if (!lane || lease?.host !== args.origin || lease.lane !== lane || !lease.active) return args;
|
|
216
|
+
if (lane.info?.entry !== args.remoteInfo.entry) lane.candidate = void 0;
|
|
217
|
+
lane.info = args.remoteInfo;
|
|
218
|
+
if (void 0 === lane.candidate) lane.candidate = await probe(args.origin, lane, args.expose);
|
|
219
|
+
lease.admitted = true;
|
|
220
|
+
lane.snapshot = args.remoteSnapshot;
|
|
221
|
+
lease.resolution = {
|
|
222
|
+
remoteSnapshot: args.remoteSnapshot,
|
|
223
|
+
expose: args.expose
|
|
224
|
+
};
|
|
225
|
+
return args;
|
|
226
|
+
}
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
export default nativeDevRefreshRuntimePlugin;
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { assertRendererProfileCompatibility, formatBackendFederationValidationErrors, validateRendererIdentity, validateRendererProfile } from "@modern-js/backend-federation-contracts";
|
|
2
|
+
const RENDERER_FEDERATION_METADATA_KEY = 'ultramodernRenderer';
|
|
3
|
+
const RENDERER_FEDERATION_SCHEMA = 'ultramodern.renderer-federation';
|
|
4
|
+
const RENDERER_FEDERATION_SCHEMA_VERSION = 1;
|
|
5
|
+
const rendererFederationError = (message)=>new Error(`Renderer federation manifest contract: ${message}`);
|
|
6
|
+
const record = (value)=>null !== value && 'object' == typeof value && !Array.isArray(value);
|
|
7
|
+
const exactFields = (value, fields, label)=>{
|
|
8
|
+
const unknown = Object.keys(value).filter((key)=>!fields.includes(key));
|
|
9
|
+
const missing = fields.filter((key)=>!Object.hasOwn(value, key));
|
|
10
|
+
if (unknown.length || missing.length) throw rendererFederationError(`${label} fields differ (unknown=${unknown.join(',')}; missing=${missing.join(',')}).`);
|
|
11
|
+
};
|
|
12
|
+
const assertProfile = (value)=>{
|
|
13
|
+
const result = validateRendererProfile(value, 'rendererProfile');
|
|
14
|
+
if (!result.ok) throw rendererFederationError(formatBackendFederationValidationErrors(result.errors));
|
|
15
|
+
const profile = value;
|
|
16
|
+
return Object.freeze({
|
|
17
|
+
...profile,
|
|
18
|
+
compiler: Object.freeze({
|
|
19
|
+
...profile.compiler
|
|
20
|
+
}),
|
|
21
|
+
hydration: Object.freeze({
|
|
22
|
+
...profile.hydration
|
|
23
|
+
}),
|
|
24
|
+
router: Object.freeze({
|
|
25
|
+
...profile.router
|
|
26
|
+
})
|
|
27
|
+
});
|
|
28
|
+
};
|
|
29
|
+
const assertPackage = (value, profile, label)=>{
|
|
30
|
+
const result = validateRendererProfile({
|
|
31
|
+
...profile,
|
|
32
|
+
compiler: value
|
|
33
|
+
}, label);
|
|
34
|
+
if (!result.ok) throw rendererFederationError(formatBackendFederationValidationErrors(result.errors));
|
|
35
|
+
return Object.freeze({
|
|
36
|
+
...value
|
|
37
|
+
});
|
|
38
|
+
};
|
|
39
|
+
function readRendererFederationCompatibility(value) {
|
|
40
|
+
if (!record(value)) throw rendererFederationError('the consuming renderer tuple is absent.');
|
|
41
|
+
exactFields(value, [
|
|
42
|
+
'profile',
|
|
43
|
+
'runtime',
|
|
44
|
+
'bootstrap'
|
|
45
|
+
], 'compatibility');
|
|
46
|
+
const profile = assertProfile(value.profile);
|
|
47
|
+
return Object.freeze({
|
|
48
|
+
profile,
|
|
49
|
+
runtime: assertPackage(value.runtime, profile, 'runtime'),
|
|
50
|
+
bootstrap: assertPackage(value.bootstrap, profile, 'bootstrap')
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
function readRendererFederationContract(value) {
|
|
54
|
+
if (!record(value)) throw rendererFederationError('renderer publication metadata is absent.');
|
|
55
|
+
exactFields(value, [
|
|
56
|
+
'schema',
|
|
57
|
+
'schemaVersion',
|
|
58
|
+
'profile',
|
|
59
|
+
'runtime',
|
|
60
|
+
'bootstrap',
|
|
61
|
+
'identities'
|
|
62
|
+
], 'publication');
|
|
63
|
+
if (value.schema !== RENDERER_FEDERATION_SCHEMA || value.schemaVersion !== RENDERER_FEDERATION_SCHEMA_VERSION) throw rendererFederationError('unsupported renderer publication schema.');
|
|
64
|
+
const compatibility = readRendererFederationCompatibility({
|
|
65
|
+
profile: value.profile,
|
|
66
|
+
runtime: value.runtime,
|
|
67
|
+
bootstrap: value.bootstrap
|
|
68
|
+
});
|
|
69
|
+
if (!record(value.identities) || !Object.keys(value.identities).length) throw rendererFederationError('finalized application entry identities are absent.');
|
|
70
|
+
const identities = Object.create(null);
|
|
71
|
+
let appId;
|
|
72
|
+
for (const [entryName, valueIdentity] of Object.entries(value.identities)){
|
|
73
|
+
const result = validateRendererIdentity(valueIdentity, `identities.${entryName}`);
|
|
74
|
+
if (!result.ok) throw rendererFederationError(formatBackendFederationValidationErrors(result.errors));
|
|
75
|
+
const identity = valueIdentity;
|
|
76
|
+
if (identity.entryName !== entryName || identity.renderer !== compatibility.profile.renderer || identity.protocolVersion !== compatibility.profile.protocolVersion || void 0 !== appId && identity.appId !== appId) throw rendererFederationError(`incoherent publication identity for ${entryName}.`);
|
|
77
|
+
appId = identity.appId;
|
|
78
|
+
identities[entryName] = Object.freeze({
|
|
79
|
+
...identity
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
return Object.freeze({
|
|
83
|
+
schema: RENDERER_FEDERATION_SCHEMA,
|
|
84
|
+
schemaVersion: RENDERER_FEDERATION_SCHEMA_VERSION,
|
|
85
|
+
...compatibility,
|
|
86
|
+
identities: Object.freeze(identities)
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
function assertRendererFederationCompatibility(expected, actual) {
|
|
90
|
+
try {
|
|
91
|
+
assertRendererProfileCompatibility(expected.profile, actual.profile);
|
|
92
|
+
} catch (error) {
|
|
93
|
+
throw rendererFederationError(error instanceof Error ? error.message : String(error));
|
|
94
|
+
}
|
|
95
|
+
for (const field of [
|
|
96
|
+
'runtime',
|
|
97
|
+
'bootstrap'
|
|
98
|
+
])for (const key of [
|
|
99
|
+
'name',
|
|
100
|
+
'version'
|
|
101
|
+
])if (expected[field][key] !== actual[field][key]) throw rendererFederationError(`${field}.${key} must match the consuming renderer tuple.`);
|
|
102
|
+
}
|
|
103
|
+
export { RENDERER_FEDERATION_METADATA_KEY, RENDERER_FEDERATION_SCHEMA, RENDERER_FEDERATION_SCHEMA_VERSION, assertRendererFederationCompatibility, readRendererFederationCompatibility, readRendererFederationContract, rendererFederationError };
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
import { getResourceUrl, isBrowserEnvValue, isReactNativeEnv } from "@module-federation/sdk";
|
|
2
|
+
import { RENDERER_FEDERATION_METADATA_KEY, assertRendererFederationCompatibility, readRendererFederationCompatibility, readRendererFederationContract, rendererFederationError } from "./renderer-contract.mjs";
|
|
3
|
+
const ATTESTATION = Symbol.for('ultramodern.renderer-federation.attestation.v1');
|
|
4
|
+
const record = (value)=>null !== value && 'object' == typeof value && !Array.isArray(value);
|
|
5
|
+
const coordinates = (snapshot)=>JSON.stringify([
|
|
6
|
+
'globalName',
|
|
7
|
+
'buildVersion',
|
|
8
|
+
'remoteEntry',
|
|
9
|
+
'remoteEntryType',
|
|
10
|
+
'ssrRemoteEntry',
|
|
11
|
+
'ssrRemoteEntryType',
|
|
12
|
+
'publicPath',
|
|
13
|
+
'ssrPublicPath',
|
|
14
|
+
'getPublicPath'
|
|
15
|
+
].map((key)=>[
|
|
16
|
+
key,
|
|
17
|
+
snapshot[key]
|
|
18
|
+
]));
|
|
19
|
+
function attestation(snapshot) {
|
|
20
|
+
const descriptor = Object.getOwnPropertyDescriptor(snapshot, ATTESTATION);
|
|
21
|
+
const value = descriptor?.value;
|
|
22
|
+
if (!descriptor || descriptor.writable || descriptor.configurable || !record(value) || 'string' != typeof value.coordinates || value.coordinates !== coordinates(snapshot)) throw rendererFederationError('cached remote snapshot is missing or has changed its attestation.');
|
|
23
|
+
return {
|
|
24
|
+
contract: readRendererFederationContract(value.contract),
|
|
25
|
+
coordinates: value.coordinates
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
const assertManifestRemote = (remote)=>{
|
|
29
|
+
if (!record(remote)) throw rendererFederationError('remote registration is absent.');
|
|
30
|
+
if ('entry' in remote && ('string' != typeof remote.entry || !remote.entry.includes('.json'))) throw rendererFederationError('renderer components require a native JSON manifest remote.');
|
|
31
|
+
};
|
|
32
|
+
function sameFactory(left, right) {
|
|
33
|
+
if (left === right) return true;
|
|
34
|
+
if ('function' == typeof left.lib && 'function' == typeof right.lib) return left.lib === right.lib;
|
|
35
|
+
return 'function' == typeof left.get && left.get === right.get;
|
|
36
|
+
}
|
|
37
|
+
const containerCoordinates = (info)=>JSON.stringify([
|
|
38
|
+
'name',
|
|
39
|
+
'entry',
|
|
40
|
+
'type',
|
|
41
|
+
'entryGlobalName',
|
|
42
|
+
'buildVersion'
|
|
43
|
+
].map((key)=>[
|
|
44
|
+
key,
|
|
45
|
+
info[key]
|
|
46
|
+
]));
|
|
47
|
+
const entryCoordinates = (info)=>JSON.stringify([
|
|
48
|
+
'name',
|
|
49
|
+
'entry',
|
|
50
|
+
'type',
|
|
51
|
+
'entryGlobalName'
|
|
52
|
+
].map((key)=>[
|
|
53
|
+
key,
|
|
54
|
+
info[key]
|
|
55
|
+
]));
|
|
56
|
+
function rendererShareVersions(expected) {
|
|
57
|
+
const { profile, runtime, bootstrap } = expected;
|
|
58
|
+
if ('react' === profile.renderer) return new Map([
|
|
59
|
+
[
|
|
60
|
+
'react',
|
|
61
|
+
runtime.version
|
|
62
|
+
],
|
|
63
|
+
[
|
|
64
|
+
'react-dom',
|
|
65
|
+
profile.hydration.version
|
|
66
|
+
],
|
|
67
|
+
[
|
|
68
|
+
'react-dom/client',
|
|
69
|
+
profile.hydration.version
|
|
70
|
+
],
|
|
71
|
+
[
|
|
72
|
+
'@modern-js/runtime',
|
|
73
|
+
bootstrap.version
|
|
74
|
+
]
|
|
75
|
+
]);
|
|
76
|
+
const versions = new Map();
|
|
77
|
+
for (const [name, version] of [
|
|
78
|
+
[
|
|
79
|
+
runtime.name,
|
|
80
|
+
runtime.version
|
|
81
|
+
],
|
|
82
|
+
[
|
|
83
|
+
profile.hydration.name,
|
|
84
|
+
profile.hydration.version
|
|
85
|
+
],
|
|
86
|
+
[
|
|
87
|
+
bootstrap.name,
|
|
88
|
+
bootstrap.version
|
|
89
|
+
],
|
|
90
|
+
[
|
|
91
|
+
profile.router.name,
|
|
92
|
+
profile.router.version
|
|
93
|
+
],
|
|
94
|
+
[
|
|
95
|
+
profile.router.coreName,
|
|
96
|
+
profile.router.coreVersion
|
|
97
|
+
]
|
|
98
|
+
]){
|
|
99
|
+
const prior = versions.get(name);
|
|
100
|
+
if (void 0 !== prior && prior !== version) throw rendererFederationError(`renderer tuple names ${name} with conflicting versions.`);
|
|
101
|
+
versions.set(name, version);
|
|
102
|
+
}
|
|
103
|
+
return versions;
|
|
104
|
+
}
|
|
105
|
+
function createRendererFederationRuntimePlugin(options) {
|
|
106
|
+
const expected = readRendererFederationCompatibility(options);
|
|
107
|
+
const selected = new Map();
|
|
108
|
+
const approvedRemoteInfos = new WeakMap();
|
|
109
|
+
const observedEntries = new WeakMap();
|
|
110
|
+
const acceptedEntries = new Set();
|
|
111
|
+
const versions = rendererShareVersions(expected);
|
|
112
|
+
const sharedVersion = (name)=>versions.get(name) ?? ('react' === expected.profile.renderer ? void 0 : [
|
|
113
|
+
...versions
|
|
114
|
+
].find(([owner])=>name.startsWith(`${owner}/`))?.[1]);
|
|
115
|
+
return {
|
|
116
|
+
name: 'ultramodern-renderer-federation-contract',
|
|
117
|
+
beforeRegisterRemote (args) {
|
|
118
|
+
assertManifestRemote(args.remote);
|
|
119
|
+
return args;
|
|
120
|
+
},
|
|
121
|
+
async afterMatchRemote (args) {
|
|
122
|
+
if (!args.error) assertManifestRemote(args.remote);
|
|
123
|
+
return args;
|
|
124
|
+
},
|
|
125
|
+
async loadRemoteSnapshot (args) {
|
|
126
|
+
const snapshot = args.remoteSnapshot;
|
|
127
|
+
if ('manifest' === args.from) {
|
|
128
|
+
const manifest = args.manifestJson;
|
|
129
|
+
const metadata = record(manifest) && record(manifest.metaData) ? manifest.metaData[RENDERER_FEDERATION_METADATA_KEY] : void 0;
|
|
130
|
+
const contract = readRendererFederationContract(metadata);
|
|
131
|
+
if (!record(snapshot) || 'string' != typeof snapshot.remoteEntry) throw rendererFederationError('native remote entry coordinates are absent.');
|
|
132
|
+
const existing = Object.getOwnPropertyDescriptor(snapshot, ATTESTATION);
|
|
133
|
+
if (existing) {
|
|
134
|
+
const prior = attestation(snapshot);
|
|
135
|
+
if (JSON.stringify(prior.contract) !== JSON.stringify(contract)) throw rendererFederationError('remote snapshot has conflicting publication ownership.');
|
|
136
|
+
} else Object.defineProperty(snapshot, ATTESTATION, {
|
|
137
|
+
value: Object.freeze({
|
|
138
|
+
contract,
|
|
139
|
+
coordinates: coordinates(snapshot)
|
|
140
|
+
})
|
|
141
|
+
});
|
|
142
|
+
} else assertRendererFederationCompatibility(expected, attestation(snapshot).contract);
|
|
143
|
+
return args;
|
|
144
|
+
},
|
|
145
|
+
async afterLoadSnapshot (args) {
|
|
146
|
+
assertRendererFederationCompatibility(expected, attestation(args.remoteSnapshot).contract);
|
|
147
|
+
const snapshot = args.remoteSnapshot;
|
|
148
|
+
const browser = isBrowserEnvValue || isReactNativeEnv() || !('ssrRemoteEntry' in snapshot);
|
|
149
|
+
const entry = browser ? snapshot.remoteEntry : snapshot.ssrRemoteEntry;
|
|
150
|
+
if ('string' != typeof entry || !entry) throw rendererFederationError('native projected remote entry is absent.');
|
|
151
|
+
let entryUrl = getResourceUrl(snapshot, entry);
|
|
152
|
+
if (!isBrowserEnvValue && !entryUrl.startsWith('http')) entryUrl = `https:${entryUrl}`;
|
|
153
|
+
acceptedEntries.add(entryCoordinates({
|
|
154
|
+
name: args.moduleInfo?.name ?? snapshot.globalName,
|
|
155
|
+
entry: entryUrl,
|
|
156
|
+
type: browser ? snapshot.remoteEntryType : snapshot.ssrRemoteEntryType || 'global',
|
|
157
|
+
entryGlobalName: snapshot.globalName
|
|
158
|
+
}));
|
|
159
|
+
return args;
|
|
160
|
+
},
|
|
161
|
+
async afterResolve (args) {
|
|
162
|
+
if (!args.remoteSnapshot) throw rendererFederationError('native remote resolution has no attested snapshot.');
|
|
163
|
+
assertRendererFederationCompatibility(expected, attestation(args.remoteSnapshot).contract);
|
|
164
|
+
const cached = args.origin.moduleCache.get(args.remote.name);
|
|
165
|
+
if (cached && (approvedRemoteInfos.get(cached.remoteInfo) !== containerCoordinates(cached.remoteInfo) || containerCoordinates(cached.remoteInfo) !== containerCoordinates(args.remoteInfo))) throw rendererFederationError('cached remote container has unattested or conflicting ownership.');
|
|
166
|
+
const priorCoordinates = approvedRemoteInfos.get(args.remoteInfo);
|
|
167
|
+
if (void 0 !== priorCoordinates && priorCoordinates !== containerCoordinates(args.remoteInfo)) throw rendererFederationError('native remote container coordinates changed after attestation.');
|
|
168
|
+
approvedRemoteInfos.set(args.remoteInfo, containerCoordinates(args.remoteInfo));
|
|
169
|
+
return args;
|
|
170
|
+
},
|
|
171
|
+
loadEntry (args) {
|
|
172
|
+
if (!acceptedEntries.has(entryCoordinates(args.remoteInfo))) throw rendererFederationError('remote entry coordinates differ from the attested native snapshot.');
|
|
173
|
+
},
|
|
174
|
+
async afterLoadEntry (args) {
|
|
175
|
+
if (args.error && !args.recovered) return args;
|
|
176
|
+
const binding = containerCoordinates(args.remoteInfo);
|
|
177
|
+
const entryBinding = entryCoordinates(args.remoteInfo);
|
|
178
|
+
if (args.cached && approvedRemoteInfos.get(args.remoteInfo) !== binding) throw rendererFederationError('remote entry has no attested native container resolution.');
|
|
179
|
+
if (!acceptedEntries.has(entryBinding)) throw rendererFederationError('remote entry coordinates differ from the attested native snapshot.');
|
|
180
|
+
const entry = args.remoteEntryExports;
|
|
181
|
+
if (('object' != typeof entry || null === entry) && 'function' != typeof entry) throw rendererFederationError('native container exports are absent.');
|
|
182
|
+
if (args.cached && observedEntries.get(entry) !== entryBinding) throw rendererFederationError('cached native container exports have unattested ownership.');
|
|
183
|
+
approvedRemoteInfos.set(args.remoteInfo, binding);
|
|
184
|
+
observedEntries.set(entry, entryBinding);
|
|
185
|
+
return args;
|
|
186
|
+
},
|
|
187
|
+
resolveShare (args) {
|
|
188
|
+
const version = sharedVersion(args.pkgName);
|
|
189
|
+
if (void 0 === version) return args;
|
|
190
|
+
const resolve = args.resolver;
|
|
191
|
+
return {
|
|
192
|
+
...args,
|
|
193
|
+
resolver () {
|
|
194
|
+
const result = resolve();
|
|
195
|
+
if (!result) return result;
|
|
196
|
+
const shared = result.useTreesShaking && record(result.shared.treeShaking) ? result.shared.treeShaking : result.shared;
|
|
197
|
+
if (result.shared.version !== version) throw rendererFederationError(`shared ${args.pkgName} version must be ${version}.`);
|
|
198
|
+
const prior = selected.get(args.pkgName);
|
|
199
|
+
if (prior && !sameFactory(prior, shared)) throw rendererFederationError(`shared ${args.pkgName} has conflicting runtime ownership.`);
|
|
200
|
+
for (const scope of Object.values(args.shareScopeMap))for (const candidate of Object.values(scope[args.pkgName] ?? {})){
|
|
201
|
+
const active = record(candidate.treeShaking) && candidate.treeShaking.loaded ? candidate.treeShaking : candidate;
|
|
202
|
+
if ((active.loaded || active.lib) && !sameFactory(active, shared)) throw rendererFederationError(`shared ${args.pkgName} has duplicate loaded runtime identities.`);
|
|
203
|
+
}
|
|
204
|
+
selected.set(args.pkgName, Object.freeze({
|
|
205
|
+
lib: shared.lib,
|
|
206
|
+
get: shared.get
|
|
207
|
+
}));
|
|
208
|
+
return result;
|
|
209
|
+
}
|
|
210
|
+
};
|
|
211
|
+
},
|
|
212
|
+
afterLoadShare (args) {
|
|
213
|
+
if (void 0 !== sharedVersion(args.pkgName) && args.selectedShared) {
|
|
214
|
+
const shared = record(args.selectedShared.treeShaking) && args.selectedShared.treeShaking.loaded ? args.selectedShared.treeShaking : args.selectedShared;
|
|
215
|
+
selected.set(args.pkgName, Object.freeze({
|
|
216
|
+
lib: shared.lib,
|
|
217
|
+
get: shared.get
|
|
218
|
+
}));
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
const renderer_runtime_plugin = createRendererFederationRuntimePlugin;
|
|
224
|
+
export default renderer_runtime_plugin;
|
|
225
|
+
export { createRendererFederationRuntimePlugin, rendererShareVersions };
|