@bleedingdev/modern-js-federation-runtime 3.9.3-ultramodern.7 → 3.9.3-ultramodern.9
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/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-runtime-plugin.js +5 -2
- 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-runtime-plugin.mjs +5 -2
- 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-runtime-plugin.mjs +5 -2
- 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/package.json +60 -13
- 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-runtime-plugin.ts +7 -2
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
import "node:module";
|
|
2
|
+
const COMMIT_ERROR = Symbol.for('ultramodern.federation.browser-dev-commit-error.v1');
|
|
3
|
+
class NativeBrowserDevCommitError extends Error {
|
|
4
|
+
constructor(revision, cause){
|
|
5
|
+
super(`Native browser refresh failed after ownership transfer began: ${cause instanceof Error ? cause.message : String(cause)}`, {
|
|
6
|
+
cause
|
|
7
|
+
}), this[COMMIT_ERROR] = true;
|
|
8
|
+
this.name = 'NativeBrowserDevCommitError';
|
|
9
|
+
Object.defineProperty(this, 'revision', {
|
|
10
|
+
value: revision,
|
|
11
|
+
enumerable: true
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
function isNativeBrowserDevCommitError(error, revision) {
|
|
16
|
+
return error instanceof Error && true === Reflect.get(error, COMMIT_ERROR) && Reflect.get(error, 'revision') === revision;
|
|
17
|
+
}
|
|
18
|
+
function nativeBrowserDevCommitFailure(revision, cause) {
|
|
19
|
+
return new NativeBrowserDevCommitError(revision, cause);
|
|
20
|
+
}
|
|
21
|
+
const selectedShares = (host)=>Object.entries(host.shareScopeMap).flatMap(([scope, packages])=>Object.entries(packages).flatMap(([name, versions])=>Object.entries(versions).filter(([, provider])=>provider.loaded || provider.loading).map(([version, provider])=>({
|
|
22
|
+
scope,
|
|
23
|
+
name,
|
|
24
|
+
version,
|
|
25
|
+
provider,
|
|
26
|
+
lib: provider.lib,
|
|
27
|
+
get: provider.get
|
|
28
|
+
}))));
|
|
29
|
+
function assertSelectedShares(host, shares) {
|
|
30
|
+
for (const share of shares){
|
|
31
|
+
const provider = host.shareScopeMap[share.scope]?.[share.name]?.[share.version];
|
|
32
|
+
if (provider !== share.provider || provider.lib !== share.lib || provider.get !== share.get) throw new Error(`Native browser refresh changed the selected shared provider ${share.name}.`);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
function createNativeBrowserDevRefresh(options) {
|
|
36
|
+
let active;
|
|
37
|
+
let pending;
|
|
38
|
+
let disposed = false;
|
|
39
|
+
const consumed = new Set();
|
|
40
|
+
const recovery = new Set();
|
|
41
|
+
const released = new WeakSet();
|
|
42
|
+
const release = (stage)=>{
|
|
43
|
+
if (released.has(stage)) return;
|
|
44
|
+
released.add(stage);
|
|
45
|
+
try {
|
|
46
|
+
stage.release();
|
|
47
|
+
} finally{
|
|
48
|
+
stage.host.moduleCache.clear();
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
return {
|
|
52
|
+
replace (graph) {
|
|
53
|
+
if (disposed) return Promise.reject(new Error('Native browser refresh is disposed.'));
|
|
54
|
+
if (pending) return Promise.reject(new Error('Native browser refresh already has an active replacement.'));
|
|
55
|
+
if (!/^[a-f0-9]{64}$/u.test(graph.revision)) return Promise.reject(new Error('Native browser graph revision is invalid.'));
|
|
56
|
+
if (consumed.has(graph.revision)) return Promise.reject(new Error('Native browser graph was already consumed; rebuild before retrying.'));
|
|
57
|
+
const commitErrors = new WeakSet();
|
|
58
|
+
const task = (async ()=>{
|
|
59
|
+
const host = options.host;
|
|
60
|
+
const shares = selectedShares(host);
|
|
61
|
+
const replacement = options.prepareReplacement(options.boundary);
|
|
62
|
+
const stage = options.createHost({
|
|
63
|
+
...host.options,
|
|
64
|
+
name: `${host.name}:native-dev:${graph.revision}`,
|
|
65
|
+
id: `${host.name}:native-dev:${graph.revision}`,
|
|
66
|
+
remotes: [
|
|
67
|
+
{
|
|
68
|
+
...graph.remote
|
|
69
|
+
}
|
|
70
|
+
],
|
|
71
|
+
shared: {}
|
|
72
|
+
});
|
|
73
|
+
if (stage.host === host || stage.host === active?.host) throw new Error('Native browser refresh requires an isolated staging host.');
|
|
74
|
+
let accepted = false;
|
|
75
|
+
let module;
|
|
76
|
+
let failed = false;
|
|
77
|
+
let failure;
|
|
78
|
+
try {
|
|
79
|
+
for (const [name, scope] of Object.entries(host.shareScopeMap)){
|
|
80
|
+
stage.host.initShareScopeMap(name, scope, {
|
|
81
|
+
hostShareScopeMap: host.shareScopeMap
|
|
82
|
+
});
|
|
83
|
+
if (stage.host.shareScopeMap[name] !== scope) throw new Error('Native browser refresh lost the actual shared scope.');
|
|
84
|
+
}
|
|
85
|
+
const factory = await stage.host.loadRemote(options.id, {
|
|
86
|
+
loadFactory: false
|
|
87
|
+
});
|
|
88
|
+
if (disposed) throw new Error('Native browser refresh was disposed while loading.');
|
|
89
|
+
if ('function' != typeof factory) throw new Error('Native browser refresh did not receive an exposed factory.');
|
|
90
|
+
assertSelectedShares(host, shares);
|
|
91
|
+
stage.prepareFactory?.();
|
|
92
|
+
consumed.add(graph.revision);
|
|
93
|
+
try {
|
|
94
|
+
module = replacement.execute(factory);
|
|
95
|
+
} catch (error) {
|
|
96
|
+
if (replacement.commitStarted) {
|
|
97
|
+
if (active) recovery.add(active);
|
|
98
|
+
active = stage;
|
|
99
|
+
accepted = true;
|
|
100
|
+
}
|
|
101
|
+
throw error;
|
|
102
|
+
}
|
|
103
|
+
const previous = active;
|
|
104
|
+
active = stage;
|
|
105
|
+
accepted = true;
|
|
106
|
+
assertSelectedShares(host, shares);
|
|
107
|
+
const retired = new Set(recovery);
|
|
108
|
+
recovery.clear();
|
|
109
|
+
if (previous) retired.add(previous);
|
|
110
|
+
const errors = [];
|
|
111
|
+
for (const retained of retired)try {
|
|
112
|
+
release(retained);
|
|
113
|
+
} catch (error) {
|
|
114
|
+
errors.push(error);
|
|
115
|
+
}
|
|
116
|
+
if (errors.length) throw new AggregateError(errors, 'Native browser refresh retirement failed.');
|
|
117
|
+
} catch (error) {
|
|
118
|
+
failed = true;
|
|
119
|
+
failure = error;
|
|
120
|
+
}
|
|
121
|
+
const retired = new Set();
|
|
122
|
+
if (disposed && active === stage) {
|
|
123
|
+
active = void 0;
|
|
124
|
+
for (const retained of recovery)retired.add(retained);
|
|
125
|
+
recovery.clear();
|
|
126
|
+
retired.add(stage);
|
|
127
|
+
} else if (!accepted) retired.add(stage);
|
|
128
|
+
const cleanupErrors = [];
|
|
129
|
+
for (const retained of retired)try {
|
|
130
|
+
release(retained);
|
|
131
|
+
} catch (error) {
|
|
132
|
+
cleanupErrors.push(error);
|
|
133
|
+
}
|
|
134
|
+
if (cleanupErrors.length) {
|
|
135
|
+
failure = new AggregateError(failed ? [
|
|
136
|
+
failure,
|
|
137
|
+
...cleanupErrors
|
|
138
|
+
] : cleanupErrors, 'Native browser refresh cleanup failed.');
|
|
139
|
+
failed = true;
|
|
140
|
+
}
|
|
141
|
+
if (failed) {
|
|
142
|
+
if (accepted) {
|
|
143
|
+
const committedFailure = nativeBrowserDevCommitFailure(graph.revision, failure);
|
|
144
|
+
commitErrors.add(committedFailure);
|
|
145
|
+
throw committedFailure;
|
|
146
|
+
}
|
|
147
|
+
throw failure;
|
|
148
|
+
}
|
|
149
|
+
return module;
|
|
150
|
+
})().catch((error)=>{
|
|
151
|
+
if (error instanceof Error && true === Reflect.get(error, COMMIT_ERROR) && !commitErrors.has(error)) throw new Error(`Native browser graph failed before its own ownership transfer: ${error.message}`, {
|
|
152
|
+
cause: error
|
|
153
|
+
});
|
|
154
|
+
throw error;
|
|
155
|
+
});
|
|
156
|
+
pending = task;
|
|
157
|
+
task.finally(()=>{
|
|
158
|
+
if (pending === task) pending = void 0;
|
|
159
|
+
}).catch(()=>{});
|
|
160
|
+
return task;
|
|
161
|
+
},
|
|
162
|
+
get activeHost () {
|
|
163
|
+
return active?.host ?? options.host;
|
|
164
|
+
},
|
|
165
|
+
dispose () {
|
|
166
|
+
if (disposed) return;
|
|
167
|
+
disposed = true;
|
|
168
|
+
const previous = active;
|
|
169
|
+
active = void 0;
|
|
170
|
+
const retained = new Set(recovery);
|
|
171
|
+
recovery.clear();
|
|
172
|
+
if (previous) retained.add(previous);
|
|
173
|
+
const errors = [];
|
|
174
|
+
for (const stage of retained)try {
|
|
175
|
+
release(stage);
|
|
176
|
+
} catch (error) {
|
|
177
|
+
errors.push(error);
|
|
178
|
+
}
|
|
179
|
+
if (errors.length) throw new AggregateError(errors, 'Native browser refresh cleanup failed.');
|
|
180
|
+
}
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
export { createNativeBrowserDevRefresh, isNativeBrowserDevCommitError, nativeBrowserDevCommitFailure };
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import "node:module";
|
|
2
|
+
import { createNativeBrowserDevRefresh, isNativeBrowserDevCommitError, nativeBrowserDevCommitFailure } from "./native-browser-dev-refresh.mjs";
|
|
3
|
+
import { readRendererFederationContract } from "./renderer-contract.mjs";
|
|
4
|
+
const NATIVE_BROWSER_DEV_BOUNDARY = Symbol.for('ultramodern.federation.browser-dev-boundary.v1');
|
|
5
|
+
const REGISTRY = Symbol.for('ultramodern.federation.browser-dev-routing.v1');
|
|
6
|
+
function registry() {
|
|
7
|
+
const root = globalThis;
|
|
8
|
+
if (!root[REGISTRY]) Object.defineProperty(root, REGISTRY, {
|
|
9
|
+
value: {
|
|
10
|
+
sources: new WeakMap(),
|
|
11
|
+
bindings: new Set()
|
|
12
|
+
}
|
|
13
|
+
});
|
|
14
|
+
return root[REGISTRY];
|
|
15
|
+
}
|
|
16
|
+
function firstReference(binding) {
|
|
17
|
+
const reference = binding.references.values().next().value;
|
|
18
|
+
if (!reference) throw new Error('Native browser refresh has no mounted boundary owner.');
|
|
19
|
+
return reference;
|
|
20
|
+
}
|
|
21
|
+
function createNativeBrowserDevRuntimePlugin(options) {
|
|
22
|
+
return {
|
|
23
|
+
name: 'ultramodern-native-browser-dev-refresh',
|
|
24
|
+
beforeInit (args) {
|
|
25
|
+
const host = args.origin;
|
|
26
|
+
if (Object.hasOwn(host, NATIVE_BROWSER_DEV_BOUNDARY)) return args;
|
|
27
|
+
Object.defineProperty(host, NATIVE_BROWSER_DEV_BOUNDARY, {
|
|
28
|
+
value (id, source, boundary) {
|
|
29
|
+
const remote = options.remotes.find((remote)=>id.startsWith(`${remote.alias}/`));
|
|
30
|
+
if (!remote) throw new Error(`Native browser refresh has no registered remote for ${id}.`);
|
|
31
|
+
const state = registry();
|
|
32
|
+
let sources = state.sources.get(source);
|
|
33
|
+
if (!sources) state.sources.set(source, sources = new Map());
|
|
34
|
+
let binding = sources.get(id);
|
|
35
|
+
if (binding && binding.remoteName !== remote.name) throw new Error('Native browser refresh source has conflicting remote ownership.');
|
|
36
|
+
if (!binding) {
|
|
37
|
+
const references = new Set();
|
|
38
|
+
const current = ()=>firstReference(created);
|
|
39
|
+
const created = {
|
|
40
|
+
id,
|
|
41
|
+
remoteName: remote.name,
|
|
42
|
+
references,
|
|
43
|
+
coordinator: createNativeBrowserDevRefresh({
|
|
44
|
+
get host () {
|
|
45
|
+
return current().host;
|
|
46
|
+
},
|
|
47
|
+
id,
|
|
48
|
+
boundary: references,
|
|
49
|
+
prepareReplacement () {
|
|
50
|
+
const reference = current();
|
|
51
|
+
return reference.prepare(reference.boundary);
|
|
52
|
+
},
|
|
53
|
+
createHost: options.createHost
|
|
54
|
+
})
|
|
55
|
+
};
|
|
56
|
+
binding = created;
|
|
57
|
+
sources.set(id, binding);
|
|
58
|
+
state.bindings.add(binding);
|
|
59
|
+
}
|
|
60
|
+
const reference = {
|
|
61
|
+
host,
|
|
62
|
+
remote,
|
|
63
|
+
boundary,
|
|
64
|
+
prepare: options.prepareReplacement
|
|
65
|
+
};
|
|
66
|
+
binding.references.add(reference);
|
|
67
|
+
const owned = binding;
|
|
68
|
+
let released = false;
|
|
69
|
+
return ()=>{
|
|
70
|
+
if (released) return;
|
|
71
|
+
released = true;
|
|
72
|
+
owned.references.delete(reference);
|
|
73
|
+
if (owned.references.size) return;
|
|
74
|
+
sources.delete(id);
|
|
75
|
+
state.bindings.delete(owned);
|
|
76
|
+
owned.coordinator.dispose();
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
return args;
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
async function replaceNativeBrowserDevRemote(graph) {
|
|
85
|
+
const bindings = [
|
|
86
|
+
...registry().bindings
|
|
87
|
+
].filter((binding)=>binding.remoteName === graph.remote.name);
|
|
88
|
+
if (!bindings.length) throw new Error('Native browser refresh has no mounted publisher for this remote.');
|
|
89
|
+
let committed = 0;
|
|
90
|
+
try {
|
|
91
|
+
for (const binding of bindings){
|
|
92
|
+
await binding.coordinator.replace(graph);
|
|
93
|
+
committed++;
|
|
94
|
+
}
|
|
95
|
+
} catch (error) {
|
|
96
|
+
if (committed && !isNativeBrowserDevCommitError(error, graph.revision)) throw nativeBrowserDevCommitFailure(graph.revision, error);
|
|
97
|
+
throw error;
|
|
98
|
+
}
|
|
99
|
+
return bindings.length;
|
|
100
|
+
}
|
|
101
|
+
const record = (value)=>null !== value && 'object' == typeof value && !Array.isArray(value);
|
|
102
|
+
async function readNativeBrowserDevPublication(remoteName, options) {
|
|
103
|
+
const binding = [
|
|
104
|
+
...registry().bindings
|
|
105
|
+
].find((binding)=>binding.remoteName === remoteName);
|
|
106
|
+
if (!binding) throw new Error('Native browser refresh has no mounted publisher for discovery.');
|
|
107
|
+
const reference = firstReference(binding);
|
|
108
|
+
const remote = reference.remote;
|
|
109
|
+
const host = reference.host;
|
|
110
|
+
const init = {
|
|
111
|
+
cache: 'no-store',
|
|
112
|
+
signal: options.signal
|
|
113
|
+
};
|
|
114
|
+
const context = {
|
|
115
|
+
initiator: 'loadRemote',
|
|
116
|
+
id: binding.id,
|
|
117
|
+
resourceType: 'manifest',
|
|
118
|
+
url: remote.entry
|
|
119
|
+
};
|
|
120
|
+
const hook = host.loaderHook?.lifecycle.fetch;
|
|
121
|
+
const request = (async ()=>{
|
|
122
|
+
const hooked = await hook?.emit(remote.entry, init, remote, context);
|
|
123
|
+
const response = hooked instanceof Response ? hooked : await globalThis.fetch(remote.entry, init);
|
|
124
|
+
if (options.signal.aborted) {
|
|
125
|
+
response.body?.cancel().catch(()=>{});
|
|
126
|
+
throw options.signal.reason;
|
|
127
|
+
}
|
|
128
|
+
if (!response.ok) {
|
|
129
|
+
response.body?.cancel().catch(()=>{});
|
|
130
|
+
throw new Error(`Native browser manifest discovery failed: ${response.status}`);
|
|
131
|
+
}
|
|
132
|
+
const manifest = await response.json();
|
|
133
|
+
const revision = response.headers.get(options.revisionHeader);
|
|
134
|
+
const links = [
|
|
135
|
+
...(response.headers.get('link') ?? '').matchAll(/<([^>]+)>\s*;([^,]*)/gu)
|
|
136
|
+
].filter(([, , parameters])=>/\brel\s*=\s*"?ultramodern-completed-graph"?(?:\s|;|$)/u.test(parameters));
|
|
137
|
+
if (!revision || !/^[a-f0-9]{64}$/u.test(revision) || 1 !== links.length) throw new Error('Native browser manifest has no unique completed graph coordinate.');
|
|
138
|
+
const entry = new URL(links[0][1], remote.entry);
|
|
139
|
+
if (![
|
|
140
|
+
'http:',
|
|
141
|
+
'https:'
|
|
142
|
+
].includes(entry.protocol) || entry.username || entry.password || entry.hash) throw new Error('Native browser completed graph URL is invalid.');
|
|
143
|
+
const namespace = /\/__ultramodern_mf__\/([a-f0-9]+)\//u.exec(entry.pathname);
|
|
144
|
+
if (!namespace) throw new Error('Native browser graph is not in the immutable runtime namespace.');
|
|
145
|
+
const metadata = record(manifest) && record(manifest.metaData) ? manifest.metaData.ultramodernRenderer : void 0;
|
|
146
|
+
const contract = readRendererFederationContract(metadata);
|
|
147
|
+
const buildIds = new Set(Object.values(contract.identities).map((identity)=>identity.buildId));
|
|
148
|
+
if (1 !== buildIds.size) throw new Error('Native browser graph has conflicting renderer build identities.');
|
|
149
|
+
return {
|
|
150
|
+
revision,
|
|
151
|
+
runtimeHash: namespace[1],
|
|
152
|
+
buildId: [
|
|
153
|
+
...buildIds
|
|
154
|
+
][0],
|
|
155
|
+
remote: {
|
|
156
|
+
...remote,
|
|
157
|
+
entry: entry.href
|
|
158
|
+
}
|
|
159
|
+
};
|
|
160
|
+
})();
|
|
161
|
+
let removeAbort;
|
|
162
|
+
const aborted = new Promise((_resolve, reject)=>{
|
|
163
|
+
const abort = ()=>reject(options.signal.reason);
|
|
164
|
+
removeAbort = ()=>options.signal.removeEventListener('abort', abort);
|
|
165
|
+
if (options.signal.aborted) abort();
|
|
166
|
+
else options.signal.addEventListener('abort', abort, {
|
|
167
|
+
once: true
|
|
168
|
+
});
|
|
169
|
+
});
|
|
170
|
+
try {
|
|
171
|
+
return await Promise.race([
|
|
172
|
+
request,
|
|
173
|
+
aborted
|
|
174
|
+
]);
|
|
175
|
+
} finally{
|
|
176
|
+
removeAbort();
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
function hasNativeBrowserDevPublisher(remoteName) {
|
|
180
|
+
return [
|
|
181
|
+
...registry().bindings
|
|
182
|
+
].some((binding)=>binding.remoteName === remoteName);
|
|
183
|
+
}
|
|
184
|
+
export { NATIVE_BROWSER_DEV_BOUNDARY, createNativeBrowserDevRuntimePlugin, hasNativeBrowserDevPublisher, readNativeBrowserDevPublication, replaceNativeBrowserDevRemote };
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
import "node:module";
|
|
2
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
3
|
+
const LOAD_WITH_CAPTURE = Symbol.for('ultramodern.federation.load-with-capture');
|
|
4
|
+
function nativeDevRefreshRuntimePlugin(options) {
|
|
5
|
+
if (!options.revisionHeader || !Number.isSafeInteger(options.requestTimeout) || options.requestTimeout <= 0) throw new TypeError('Native federation dev refresh requires a bounded revision probe.');
|
|
6
|
+
const hosts = new WeakMap();
|
|
7
|
+
const calls = new AsyncLocalStorage();
|
|
8
|
+
const probe = (host, lane, expose)=>{
|
|
9
|
+
if (lane.probe) return lane.probe;
|
|
10
|
+
const info = lane.info;
|
|
11
|
+
if (!info) throw new Error('Native federation dev remote has no admitted entry.');
|
|
12
|
+
const pending = (async ()=>{
|
|
13
|
+
const init = {
|
|
14
|
+
method: 'HEAD',
|
|
15
|
+
cache: 'no-store',
|
|
16
|
+
signal: AbortSignal.timeout(options.requestTimeout)
|
|
17
|
+
};
|
|
18
|
+
let clearDeadline;
|
|
19
|
+
const signal = init.signal;
|
|
20
|
+
const deadline = new Promise((_resolve, reject)=>{
|
|
21
|
+
const abort = ()=>reject(signal.reason);
|
|
22
|
+
signal.addEventListener('abort', abort, {
|
|
23
|
+
once: true
|
|
24
|
+
});
|
|
25
|
+
clearDeadline = ()=>signal.removeEventListener('abort', abort);
|
|
26
|
+
});
|
|
27
|
+
const request = (async ()=>{
|
|
28
|
+
const hooked = await host.loaderHook.lifecycle.fetch.emit(info.entry, init, info, {
|
|
29
|
+
initiator: 'loadRemote',
|
|
30
|
+
id: expose && '.' !== expose ? `${info.name}/${expose.replace(/^\.\//u, '')}` : info.name,
|
|
31
|
+
resourceType: 'remoteEntry',
|
|
32
|
+
url: info.entry,
|
|
33
|
+
expose
|
|
34
|
+
});
|
|
35
|
+
const response = hooked instanceof Response ? hooked : await globalThis.fetch(info.entry, init);
|
|
36
|
+
if (signal.aborted) {
|
|
37
|
+
response.body?.cancel().catch(()=>{});
|
|
38
|
+
throw signal.reason;
|
|
39
|
+
}
|
|
40
|
+
return response;
|
|
41
|
+
})();
|
|
42
|
+
let response;
|
|
43
|
+
try {
|
|
44
|
+
response = await Promise.race([
|
|
45
|
+
request,
|
|
46
|
+
deadline
|
|
47
|
+
]);
|
|
48
|
+
} finally{
|
|
49
|
+
clearDeadline();
|
|
50
|
+
}
|
|
51
|
+
response.body?.cancel().catch(()=>{});
|
|
52
|
+
if (!response.ok) throw Object.assign(new Error(`Native federation dev revision request failed: ${response.status} ${info.entry}`), {
|
|
53
|
+
status: response.status
|
|
54
|
+
});
|
|
55
|
+
const revision = response.headers.get(options.revisionHeader);
|
|
56
|
+
if (null !== revision && !/^[a-f0-9]{64}$/u.test(revision)) throw new Error('Native federation dev remote returned an invalid graph revision.');
|
|
57
|
+
if (null === revision && 'string' == typeof lane.revision) throw new Error('Native federation dev remote lost its completed graph revision.');
|
|
58
|
+
return revision;
|
|
59
|
+
})();
|
|
60
|
+
lane.probe = pending;
|
|
61
|
+
const clear = ()=>{
|
|
62
|
+
if (lane.probe === pending) lane.probe = void 0;
|
|
63
|
+
};
|
|
64
|
+
pending.then(clear, clear);
|
|
65
|
+
return pending;
|
|
66
|
+
};
|
|
67
|
+
return {
|
|
68
|
+
name: 'ultramodern-native-federation-dev-refresh',
|
|
69
|
+
beforeInit (args) {
|
|
70
|
+
const host = args.origin;
|
|
71
|
+
if (hosts.has(host)) return args;
|
|
72
|
+
const lanes = new Map(options.remotes.map((remote)=>[
|
|
73
|
+
remote.name,
|
|
74
|
+
{
|
|
75
|
+
remote: {
|
|
76
|
+
...remote
|
|
77
|
+
},
|
|
78
|
+
active: new Set()
|
|
79
|
+
}
|
|
80
|
+
]));
|
|
81
|
+
hosts.set(host, lanes);
|
|
82
|
+
const load = host.loadRemote.bind(host);
|
|
83
|
+
const loadWithCapture = async (id, loadOptions, capture)=>{
|
|
84
|
+
const parent = calls.getStore();
|
|
85
|
+
const lease = {
|
|
86
|
+
host,
|
|
87
|
+
...parent?.host === host ? {
|
|
88
|
+
parent
|
|
89
|
+
} : {}
|
|
90
|
+
};
|
|
91
|
+
return calls.run(lease, async ()=>{
|
|
92
|
+
let success = false;
|
|
93
|
+
try {
|
|
94
|
+
const module = await load(id, loadOptions);
|
|
95
|
+
const captured = capture?.(module, lease.resolution);
|
|
96
|
+
success = null !== module && true === lease.admitted;
|
|
97
|
+
return {
|
|
98
|
+
module,
|
|
99
|
+
captured
|
|
100
|
+
};
|
|
101
|
+
} finally{
|
|
102
|
+
lease.active = false;
|
|
103
|
+
lease.release?.(success);
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
};
|
|
107
|
+
host.loadRemote = async (id, loadOptions)=>(await loadWithCapture(id, loadOptions)).module;
|
|
108
|
+
host[LOAD_WITH_CAPTURE] = (id, capture)=>loadWithCapture(id, void 0, capture);
|
|
109
|
+
return args;
|
|
110
|
+
},
|
|
111
|
+
async beforeRequest (args) {
|
|
112
|
+
const lease = calls.getStore();
|
|
113
|
+
if (!lease || lease.host !== args.origin) return args;
|
|
114
|
+
const lane = [
|
|
115
|
+
...hosts.get(args.origin)?.values() ?? []
|
|
116
|
+
].find(({ remote })=>[
|
|
117
|
+
remote.alias,
|
|
118
|
+
remote.name
|
|
119
|
+
].some((name)=>args.id === name || args.id.startsWith(`${name}/`)));
|
|
120
|
+
if (!lane || lease.lane) return args;
|
|
121
|
+
lease.lane = lane;
|
|
122
|
+
const prefix = [
|
|
123
|
+
lane.remote.alias,
|
|
124
|
+
lane.remote.name
|
|
125
|
+
].find((name)=>args.id === name || args.id.startsWith(`${name}/`));
|
|
126
|
+
const expose = args.id === prefix ? '.' : `./${args.id.slice(prefix.length + 1)}`;
|
|
127
|
+
for(let ancestor = lease.parent; ancestor; ancestor = ancestor.parent)if (ancestor.lane === lane && ancestor.active) {
|
|
128
|
+
let release;
|
|
129
|
+
const finished = new Promise((resolve)=>{
|
|
130
|
+
release = resolve;
|
|
131
|
+
});
|
|
132
|
+
lane.active.add(finished);
|
|
133
|
+
lease.active = true;
|
|
134
|
+
lease.snapshot = ancestor.snapshot;
|
|
135
|
+
lease.release = ()=>{
|
|
136
|
+
lane.active.delete(finished);
|
|
137
|
+
release();
|
|
138
|
+
};
|
|
139
|
+
return args;
|
|
140
|
+
}
|
|
141
|
+
for(;;){
|
|
142
|
+
if (lane.transition) {
|
|
143
|
+
await lane.transition;
|
|
144
|
+
continue;
|
|
145
|
+
}
|
|
146
|
+
const revision = void 0 === lane.revision ? void 0 : null === lane.revision ? null : await probe(args.origin, lane, expose);
|
|
147
|
+
if (lane.transition) {
|
|
148
|
+
await lane.transition;
|
|
149
|
+
continue;
|
|
150
|
+
}
|
|
151
|
+
let finish;
|
|
152
|
+
const finished = new Promise((resolve)=>{
|
|
153
|
+
finish = resolve;
|
|
154
|
+
});
|
|
155
|
+
if (void 0 === lane.revision || revision !== lane.revision) {
|
|
156
|
+
lane.transition = finished;
|
|
157
|
+
lease.release = (success)=>{
|
|
158
|
+
if (success) lane.revision = lane.candidate ?? null;
|
|
159
|
+
if (lane.transition === finished) lane.transition = void 0;
|
|
160
|
+
finish();
|
|
161
|
+
};
|
|
162
|
+
while(lane.active.size)await Promise.allSettled([
|
|
163
|
+
...lane.active
|
|
164
|
+
]);
|
|
165
|
+
lane.candidate = revision;
|
|
166
|
+
if (void 0 !== lane.revision) {
|
|
167
|
+
lease.active = true;
|
|
168
|
+
lease.snapshot = lane.snapshot;
|
|
169
|
+
const retained = new Set();
|
|
170
|
+
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);
|
|
171
|
+
for (const pkgName of retained){
|
|
172
|
+
if (!args.origin.loadShare) throw new TypeError('Native federation dev refresh requires the host share lifecycle.');
|
|
173
|
+
await args.origin.loadShare(pkgName);
|
|
174
|
+
}
|
|
175
|
+
while(lane.active.size)await Promise.allSettled([
|
|
176
|
+
...lane.active
|
|
177
|
+
]);
|
|
178
|
+
args.origin.registerRemotes([
|
|
179
|
+
{
|
|
180
|
+
...lane.remote
|
|
181
|
+
}
|
|
182
|
+
], {
|
|
183
|
+
force: true
|
|
184
|
+
});
|
|
185
|
+
lease.snapshot = void 0;
|
|
186
|
+
}
|
|
187
|
+
} else {
|
|
188
|
+
lane.active.add(finished);
|
|
189
|
+
lease.release = ()=>{
|
|
190
|
+
lane.active.delete(finished);
|
|
191
|
+
finish();
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
lease.active = true;
|
|
195
|
+
return args;
|
|
196
|
+
}
|
|
197
|
+
},
|
|
198
|
+
loadSnapshot (args) {
|
|
199
|
+
const lease = calls.getStore();
|
|
200
|
+
if (!lease || args.options !== lease.host.options || args.moduleInfo.name !== lease.lane?.remote.name) return args;
|
|
201
|
+
if (void 0 !== lease.snapshot) return {
|
|
202
|
+
...args,
|
|
203
|
+
remoteSnapshot: lease.snapshot
|
|
204
|
+
};
|
|
205
|
+
lease.snapshot = args.remoteSnapshot;
|
|
206
|
+
return args;
|
|
207
|
+
},
|
|
208
|
+
afterLoadSnapshot (args) {
|
|
209
|
+
const lease = calls.getStore();
|
|
210
|
+
if (lease?.host === args.host && args.moduleInfo.name === lease.lane?.remote.name) lease.snapshot = args.remoteSnapshot;
|
|
211
|
+
return args;
|
|
212
|
+
},
|
|
213
|
+
async afterResolve (args) {
|
|
214
|
+
const lane = hosts.get(args.origin)?.get(args.remote.name);
|
|
215
|
+
const lease = calls.getStore();
|
|
216
|
+
if (!lane || lease?.host !== args.origin || lease.lane !== lane || !lease.active) return args;
|
|
217
|
+
if (lane.info?.entry !== args.remoteInfo.entry) lane.candidate = void 0;
|
|
218
|
+
lane.info = args.remoteInfo;
|
|
219
|
+
if (void 0 === lane.candidate) lane.candidate = await probe(args.origin, lane, args.expose);
|
|
220
|
+
lease.admitted = true;
|
|
221
|
+
lane.snapshot = args.remoteSnapshot;
|
|
222
|
+
lease.resolution = {
|
|
223
|
+
remoteSnapshot: args.remoteSnapshot,
|
|
224
|
+
expose: args.expose
|
|
225
|
+
};
|
|
226
|
+
return args;
|
|
227
|
+
}
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
export default nativeDevRefreshRuntimePlugin;
|
|
@@ -110,6 +110,9 @@ function createRendererFederationRuntimePlugin(options) {
|
|
|
110
110
|
const observedEntries = new WeakMap();
|
|
111
111
|
const acceptedEntries = new Set();
|
|
112
112
|
const versions = rendererShareVersions(expected);
|
|
113
|
+
const sharedVersion = (name)=>versions.get(name) ?? ('react' === expected.profile.renderer ? void 0 : [
|
|
114
|
+
...versions
|
|
115
|
+
].find(([owner])=>name.startsWith(`${owner}/`))?.[1]);
|
|
113
116
|
return {
|
|
114
117
|
name: 'ultramodern-renderer-federation-contract',
|
|
115
118
|
beforeRegisterRemote (args) {
|
|
@@ -183,7 +186,7 @@ function createRendererFederationRuntimePlugin(options) {
|
|
|
183
186
|
return args;
|
|
184
187
|
},
|
|
185
188
|
resolveShare (args) {
|
|
186
|
-
const version =
|
|
189
|
+
const version = sharedVersion(args.pkgName);
|
|
187
190
|
if (void 0 === version) return args;
|
|
188
191
|
const resolve = args.resolver;
|
|
189
192
|
return {
|
|
@@ -208,7 +211,7 @@ function createRendererFederationRuntimePlugin(options) {
|
|
|
208
211
|
};
|
|
209
212
|
},
|
|
210
213
|
afterLoadShare (args) {
|
|
211
|
-
if (
|
|
214
|
+
if (void 0 !== sharedVersion(args.pkgName) && args.selectedShared) {
|
|
212
215
|
const shared = record(args.selectedShared.treeShaking) && args.selectedShared.treeShaking.loaded ? args.selectedShared.treeShaking : args.selectedShared;
|
|
213
216
|
selected.set(args.pkgName, Object.freeze({
|
|
214
217
|
lib: shared.lib,
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
interface HotRuntime {
|
|
2
|
+
status(): string;
|
|
3
|
+
check(apply: true): Promise<readonly unknown[] | null>;
|
|
4
|
+
}
|
|
5
|
+
interface Overlay {
|
|
6
|
+
create(title: string, body: string): void;
|
|
7
|
+
clear(): void;
|
|
8
|
+
}
|
|
9
|
+
/** Rearm only a loaded standby runtime before its exposed factory executes. */
|
|
10
|
+
export declare function prepareNativeBrowserDevClient(remoteName: string, manifest: string): () => void;
|
|
11
|
+
/** Release attempt-owned activity without evicting any working shared owner. */
|
|
12
|
+
export declare function releaseNativeBrowserDevClient(remoteName: string, manifest: string): void;
|
|
13
|
+
/** Rsbuild's ordinary socket/check protocol, with an explicit native handoff. */
|
|
14
|
+
export declare function startNativeBrowserDevClient(options: {
|
|
15
|
+
readonly remoteName: string;
|
|
16
|
+
readonly buildId: string;
|
|
17
|
+
readonly revisionHeader: string;
|
|
18
|
+
readonly token: string;
|
|
19
|
+
readonly config: {
|
|
20
|
+
readonly host?: string;
|
|
21
|
+
readonly port?: number | string;
|
|
22
|
+
readonly protocol?: string;
|
|
23
|
+
readonly path: string;
|
|
24
|
+
readonly reconnect: number;
|
|
25
|
+
readonly overlay?: boolean | {
|
|
26
|
+
readonly errors?: boolean;
|
|
27
|
+
};
|
|
28
|
+
};
|
|
29
|
+
readonly serverHost: string;
|
|
30
|
+
readonly serverPort: number;
|
|
31
|
+
readonly serverBase: string;
|
|
32
|
+
readonly liveReload: boolean;
|
|
33
|
+
readonly browserLogs: boolean;
|
|
34
|
+
readonly logLevel: string;
|
|
35
|
+
readonly resolveWebSocketUrl?: (url: string) => string;
|
|
36
|
+
readonly hot: HotRuntime | undefined;
|
|
37
|
+
readonly hash: () => string;
|
|
38
|
+
readonly overlay: () => Overlay | undefined;
|
|
39
|
+
readonly installCustomListeners: (listeners: Map<string, ((value: unknown) => void)[]>) => () => void;
|
|
40
|
+
}): () => void;
|
|
41
|
+
export {};
|