@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,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;
|
|
@@ -109,6 +109,9 @@ function createRendererFederationRuntimePlugin(options) {
|
|
|
109
109
|
const observedEntries = new WeakMap();
|
|
110
110
|
const acceptedEntries = new Set();
|
|
111
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]);
|
|
112
115
|
return {
|
|
113
116
|
name: 'ultramodern-renderer-federation-contract',
|
|
114
117
|
beforeRegisterRemote (args) {
|
|
@@ -182,7 +185,7 @@ function createRendererFederationRuntimePlugin(options) {
|
|
|
182
185
|
return args;
|
|
183
186
|
},
|
|
184
187
|
resolveShare (args) {
|
|
185
|
-
const version =
|
|
188
|
+
const version = sharedVersion(args.pkgName);
|
|
186
189
|
if (void 0 === version) return args;
|
|
187
190
|
const resolve = args.resolver;
|
|
188
191
|
return {
|
|
@@ -207,7 +210,7 @@ function createRendererFederationRuntimePlugin(options) {
|
|
|
207
210
|
};
|
|
208
211
|
},
|
|
209
212
|
afterLoadShare (args) {
|
|
210
|
-
if (
|
|
213
|
+
if (void 0 !== sharedVersion(args.pkgName) && args.selectedShared) {
|
|
211
214
|
const shared = record(args.selectedShared.treeShaking) && args.selectedShared.treeShaking.loaded ? args.selectedShared.treeShaking : args.selectedShared;
|
|
212
215
|
selected.set(args.pkgName, Object.freeze({
|
|
213
216
|
lib: shared.lib,
|
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
import "node:module";
|
|
2
|
+
import { isNativeBrowserDevCommitError } from "./native-browser-dev-refresh.mjs";
|
|
3
|
+
import { hasNativeBrowserDevPublisher, readNativeBrowserDevPublication, replaceNativeBrowserDevRemote } from "./native-browser-dev-runtime-plugin.mjs";
|
|
4
|
+
const CLIENTS = Symbol.for('ultramodern.federation.browser-dev-clients.v1');
|
|
5
|
+
const clients = ()=>{
|
|
6
|
+
const root = globalThis;
|
|
7
|
+
if (!root[CLIENTS]) Object.defineProperty(root, CLIENTS, {
|
|
8
|
+
value: new Map()
|
|
9
|
+
});
|
|
10
|
+
return root[CLIENTS];
|
|
11
|
+
};
|
|
12
|
+
function commitClient(remoteName, graph) {
|
|
13
|
+
const state = clients().get(remoteName);
|
|
14
|
+
const next = state?.candidates.get(graph.runtimeHash)?.deref();
|
|
15
|
+
if (!state || !next) throw new Error('Accepted native graph has no matching fresh HMR runtime.');
|
|
16
|
+
if (state.active === next) return;
|
|
17
|
+
state.active?.stop();
|
|
18
|
+
state.active = next;
|
|
19
|
+
next.start();
|
|
20
|
+
}
|
|
21
|
+
function prepareNativeBrowserDevClient(remoteName, manifest) {
|
|
22
|
+
const hash = /\/__ultramodern_mf__\/([a-f0-9]+)\//u.exec(new URL(manifest).pathname)?.[1];
|
|
23
|
+
const candidate = hash && clients().get(remoteName)?.candidates.get(hash)?.deref();
|
|
24
|
+
if (!candidate) throw new Error('Native graph has no loaded standby HMR runtime.');
|
|
25
|
+
return candidate.lease();
|
|
26
|
+
}
|
|
27
|
+
function releaseNativeBrowserDevClient(remoteName, manifest) {
|
|
28
|
+
const hash = /\/__ultramodern_mf__\/([a-f0-9]+)\//u.exec(new URL(manifest).pathname)?.[1];
|
|
29
|
+
if (!hash) return;
|
|
30
|
+
const state = clients().get(remoteName);
|
|
31
|
+
const candidate = state?.candidates.get(hash)?.deref();
|
|
32
|
+
if (!state || !candidate) return;
|
|
33
|
+
candidate.releaseStandby();
|
|
34
|
+
}
|
|
35
|
+
function startNativeBrowserDevClient(options) {
|
|
36
|
+
const state = clients().get(options.remoteName) ?? {
|
|
37
|
+
candidates: new Map()
|
|
38
|
+
};
|
|
39
|
+
clients().set(options.remoteName, state);
|
|
40
|
+
const initialHash = options.hash();
|
|
41
|
+
let socket;
|
|
42
|
+
let ping;
|
|
43
|
+
let reconnect;
|
|
44
|
+
let reconnectCount = 0;
|
|
45
|
+
let lastHash;
|
|
46
|
+
let running = false;
|
|
47
|
+
let applying;
|
|
48
|
+
let lifetime;
|
|
49
|
+
let removeCustom;
|
|
50
|
+
const custom = new Map();
|
|
51
|
+
const queued = [];
|
|
52
|
+
const leases = new Set();
|
|
53
|
+
const base = options.serverBase.endsWith('/') ? options.serverBase : `${options.serverBase}/`;
|
|
54
|
+
const report = (error)=>{
|
|
55
|
+
console.error('[ultramodern] Native federation development update failed:', error);
|
|
56
|
+
if (false !== options.config.overlay && ('object' != typeof options.config.overlay || false !== options.config.overlay.errors)) options.overlay()?.create('Native federation update failed', String(error));
|
|
57
|
+
};
|
|
58
|
+
const send = (message)=>{
|
|
59
|
+
if (socket?.readyState === WebSocket.OPEN) socket.send(JSON.stringify(message));
|
|
60
|
+
else queued.push(message);
|
|
61
|
+
};
|
|
62
|
+
const errorMessage = (error)=>error instanceof Error ? error.message : String(error);
|
|
63
|
+
const onError = (event)=>send({
|
|
64
|
+
type: 'client-error',
|
|
65
|
+
id: crypto.randomUUID(),
|
|
66
|
+
message: event.message,
|
|
67
|
+
name: event.error?.name,
|
|
68
|
+
stack: event.error?.stack
|
|
69
|
+
});
|
|
70
|
+
const onRejected = (event)=>send({
|
|
71
|
+
type: 'client-error',
|
|
72
|
+
id: crypto.randomUUID(),
|
|
73
|
+
message: `Uncaught (in promise) ${errorMessage(event.reason)}`,
|
|
74
|
+
name: event.reason?.name,
|
|
75
|
+
stack: event.reason?.stack
|
|
76
|
+
});
|
|
77
|
+
const fullReload = (data)=>{
|
|
78
|
+
if (!options.liveReload) return;
|
|
79
|
+
if (data?.path?.endsWith('.html')) {
|
|
80
|
+
const pathname = decodeURI(location.pathname);
|
|
81
|
+
const target = base + data.path.slice(1);
|
|
82
|
+
if (pathname !== target && pathname !== target.slice(0, -5) && !(pathname.endsWith('/') && `${pathname}index.html` === target)) return;
|
|
83
|
+
}
|
|
84
|
+
location.reload();
|
|
85
|
+
};
|
|
86
|
+
const apply = ()=>{
|
|
87
|
+
if (!running || applying || !lastHash || lastHash === options.hash()) return;
|
|
88
|
+
const signal = lifetime.signal;
|
|
89
|
+
const targetHash = lastHash;
|
|
90
|
+
applying = (async ()=>{
|
|
91
|
+
if (hasNativeBrowserDevPublisher(options.remoteName)) {
|
|
92
|
+
const graph = await readNativeBrowserDevPublication(options.remoteName, {
|
|
93
|
+
signal: AbortSignal.any([
|
|
94
|
+
signal,
|
|
95
|
+
AbortSignal.timeout(3000)
|
|
96
|
+
]),
|
|
97
|
+
revisionHeader: options.revisionHeader
|
|
98
|
+
});
|
|
99
|
+
if (signal.aborted) throw signal.reason;
|
|
100
|
+
if (graph.buildId !== options.buildId) {
|
|
101
|
+
try {
|
|
102
|
+
await replaceNativeBrowserDevRemote(graph);
|
|
103
|
+
} catch (error) {
|
|
104
|
+
if (running && !signal.aborted && hasNativeBrowserDevPublisher(options.remoteName) && isNativeBrowserDevCommitError(error, graph.revision)) {
|
|
105
|
+
state.failure = {
|
|
106
|
+
runtimeHash: graph.runtimeHash,
|
|
107
|
+
error
|
|
108
|
+
};
|
|
109
|
+
report(error);
|
|
110
|
+
commitClient(options.remoteName, graph);
|
|
111
|
+
}
|
|
112
|
+
throw error;
|
|
113
|
+
}
|
|
114
|
+
if (!running || signal.aborted || !hasNativeBrowserDevPublisher(options.remoteName)) return;
|
|
115
|
+
commitClient(options.remoteName, graph);
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
if (!options.hot) return fullReload();
|
|
120
|
+
if ('idle' !== options.hot.status()) return;
|
|
121
|
+
try {
|
|
122
|
+
const updated = await options.hot.check(true);
|
|
123
|
+
if (!updated) fullReload();
|
|
124
|
+
} catch (error) {
|
|
125
|
+
console.error('[rsbuild] HMR update failed, performing full reload:', error);
|
|
126
|
+
fullReload();
|
|
127
|
+
}
|
|
128
|
+
})().catch((error)=>{
|
|
129
|
+
if (!signal.aborted) report(error);
|
|
130
|
+
}).finally(()=>{
|
|
131
|
+
applying = void 0;
|
|
132
|
+
if (running && !signal.aborted && lastHash !== targetHash) apply();
|
|
133
|
+
});
|
|
134
|
+
};
|
|
135
|
+
const clearOverlayForBuild = ()=>{
|
|
136
|
+
if (state.failure) {
|
|
137
|
+
if (!lastHash || lastHash === state.failure.runtimeHash) return;
|
|
138
|
+
state.failure = void 0;
|
|
139
|
+
}
|
|
140
|
+
options.overlay()?.clear();
|
|
141
|
+
};
|
|
142
|
+
const message = (event)=>{
|
|
143
|
+
let value;
|
|
144
|
+
try {
|
|
145
|
+
value = JSON.parse(event.data);
|
|
146
|
+
} catch (error) {
|
|
147
|
+
report(error);
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
switch(value.type){
|
|
151
|
+
case 'hash':
|
|
152
|
+
lastHash = value.data;
|
|
153
|
+
break;
|
|
154
|
+
case 'ok':
|
|
155
|
+
clearOverlayForBuild();
|
|
156
|
+
apply();
|
|
157
|
+
break;
|
|
158
|
+
case 'warnings':
|
|
159
|
+
for (const warning of value.data?.text ?? [])console.warn(warning);
|
|
160
|
+
clearOverlayForBuild();
|
|
161
|
+
apply();
|
|
162
|
+
break;
|
|
163
|
+
case 'errors':
|
|
164
|
+
for (const error of value.data?.text ?? [])console.error(error);
|
|
165
|
+
if (value.data?.html) options.overlay()?.create('Build failed', value.data.html);
|
|
166
|
+
break;
|
|
167
|
+
case 'full-reload':
|
|
168
|
+
fullReload(value.data);
|
|
169
|
+
break;
|
|
170
|
+
case 'static-changed':
|
|
171
|
+
fullReload();
|
|
172
|
+
break;
|
|
173
|
+
case 'custom':
|
|
174
|
+
for (const listener of custom.get(value.data?.event) ?? [])listener(value.data.data);
|
|
175
|
+
break;
|
|
176
|
+
case 'resolved-client-error':
|
|
177
|
+
options.overlay()?.create('Runtime errors', String(value.data?.message ?? ''));
|
|
178
|
+
break;
|
|
179
|
+
}
|
|
180
|
+
};
|
|
181
|
+
const socketUrl = (fallback)=>{
|
|
182
|
+
const url = new URL('http://localhost');
|
|
183
|
+
url.hostname = (fallback ? options.serverHost : options.config.host) || location.hostname;
|
|
184
|
+
url.port = String((fallback ? options.serverPort : options.config.port) || location.port);
|
|
185
|
+
url.protocol = options.config.protocol || ('https:' === location.protocol ? 'wss:' : 'ws:');
|
|
186
|
+
url.pathname = options.config.path;
|
|
187
|
+
url.searchParams.set('token', options.token);
|
|
188
|
+
return options.resolveWebSocketUrl?.(url.href) ?? url.href;
|
|
189
|
+
};
|
|
190
|
+
const detachSocket = ()=>{
|
|
191
|
+
clearInterval(ping);
|
|
192
|
+
ping = void 0;
|
|
193
|
+
const previous = socket;
|
|
194
|
+
socket = void 0;
|
|
195
|
+
if (!previous) return;
|
|
196
|
+
previous.onopen = previous.onclose = previous.onmessage = previous.onerror = null;
|
|
197
|
+
previous.close();
|
|
198
|
+
};
|
|
199
|
+
const connect = (fallback = false)=>{
|
|
200
|
+
if (!running) return;
|
|
201
|
+
socket = new WebSocket(socketUrl(fallback));
|
|
202
|
+
socket.onopen = ()=>{
|
|
203
|
+
reconnectCount = 0;
|
|
204
|
+
ping = setInterval(()=>send({
|
|
205
|
+
type: 'ping'
|
|
206
|
+
}), 30000);
|
|
207
|
+
for (const item of queued.splice(0))send(item);
|
|
208
|
+
};
|
|
209
|
+
socket.onmessage = message;
|
|
210
|
+
socket.onclose = ()=>{
|
|
211
|
+
detachSocket();
|
|
212
|
+
if (!running || reconnectCount >= options.config.reconnect) return;
|
|
213
|
+
reconnectCount++;
|
|
214
|
+
reconnect = setTimeout(()=>connect(), 1000 * 1.5 ** reconnectCount);
|
|
215
|
+
};
|
|
216
|
+
socket.onerror = ()=>{
|
|
217
|
+
if (options.resolveWebSocketUrl || fallback || socketUrl(false) === socketUrl(true)) return;
|
|
218
|
+
detachSocket();
|
|
219
|
+
connect(true);
|
|
220
|
+
};
|
|
221
|
+
};
|
|
222
|
+
const client = {
|
|
223
|
+
arm () {
|
|
224
|
+
removeCustom ??= options.installCustomListeners(custom);
|
|
225
|
+
},
|
|
226
|
+
lease () {
|
|
227
|
+
const lease = {};
|
|
228
|
+
leases.add(lease);
|
|
229
|
+
client.arm();
|
|
230
|
+
return ()=>{
|
|
231
|
+
if (!leases.delete(lease)) return;
|
|
232
|
+
client.releaseStandby();
|
|
233
|
+
};
|
|
234
|
+
},
|
|
235
|
+
releaseStandby () {
|
|
236
|
+
if (state.active !== client && !leases.size) client.stop();
|
|
237
|
+
},
|
|
238
|
+
start () {
|
|
239
|
+
if (running) return;
|
|
240
|
+
running = true;
|
|
241
|
+
lifetime = new AbortController();
|
|
242
|
+
client.arm();
|
|
243
|
+
if (options.browserLogs) {
|
|
244
|
+
window.addEventListener('error', onError);
|
|
245
|
+
window.addEventListener('unhandledrejection', onRejected);
|
|
246
|
+
}
|
|
247
|
+
connect();
|
|
248
|
+
},
|
|
249
|
+
stop () {
|
|
250
|
+
if (!running && !removeCustom) return;
|
|
251
|
+
running = false;
|
|
252
|
+
lifetime?.abort(new Error('Native browser HMR runtime retired.'));
|
|
253
|
+
clearTimeout(reconnect);
|
|
254
|
+
reconnect = void 0;
|
|
255
|
+
detachSocket();
|
|
256
|
+
window.removeEventListener('error', onError);
|
|
257
|
+
window.removeEventListener('unhandledrejection', onRejected);
|
|
258
|
+
removeCustom?.();
|
|
259
|
+
removeCustom = void 0;
|
|
260
|
+
custom.clear();
|
|
261
|
+
queued.length = 0;
|
|
262
|
+
}
|
|
263
|
+
};
|
|
264
|
+
client.arm();
|
|
265
|
+
state.candidates.set(initialHash, new WeakRef(client));
|
|
266
|
+
if (!state.active) {
|
|
267
|
+
state.active = client;
|
|
268
|
+
client.start();
|
|
269
|
+
}
|
|
270
|
+
return ()=>{
|
|
271
|
+
client.stop();
|
|
272
|
+
if (state.active === client) state.active = void 0;
|
|
273
|
+
if (state.candidates.get(initialHash)?.deref() === client) state.candidates.delete(initialHash);
|
|
274
|
+
if (!state.active && !state.candidates.size) clients().delete(options.remoteName);
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
export { prepareNativeBrowserDevClient, releaseNativeBrowserDevClient, startNativeBrowserDevClient };
|