@deepseek-ai/dsh-client-modules 0.1.6-alpha.1 → 0.1.7-alpha.1
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.i18n.yaml +2 -2
- package/README.md +17 -5
- package/README.zh.md +17 -5
- package/lib/client.js +407 -26
- package/lib/index.js +185 -97
- package/lib/invariant.js +2 -2
- package/lib/types/client/entries.d.ts +76 -0
- package/lib/types/client/entry-lifecycle.d.ts +14 -0
- package/lib/types/client/index.d.ts +2 -0
- package/lib/types/client/manifest.d.ts +31 -16
- package/lib/types/client/system.d.ts +13 -8
- package/lib/types/index.d.ts +17 -13
- package/package.json +8 -7
package/lib/client.js
CHANGED
|
@@ -172,6 +172,274 @@ window.__ModuleLoader__.load({
|
|
|
172
172
|
};
|
|
173
173
|
}
|
|
174
174
|
//#endregion
|
|
175
|
+
//#region lib/types/client/entry-lifecycle.js
|
|
176
|
+
/**
|
|
177
|
+
* Release a runtime before clearing its entry fiber so Loader refresh can import new code.
|
|
178
|
+
* Registry deletion prevents Loader from treating replacement as a user disable.
|
|
179
|
+
* @param entry - Entry retained for code replacement.
|
|
180
|
+
*/
|
|
181
|
+
async function tearDownEntryFiber(entry) {
|
|
182
|
+
const fiber = entry.fiber;
|
|
183
|
+
if (fiber === void 0) return;
|
|
184
|
+
const runtime = fiber.runtime;
|
|
185
|
+
/* v8 ignore next -- Loader entries own plugin fibers; only the root context has a null runtime. */
|
|
186
|
+
if (runtime !== null) entry.ctx.registry.delete(runtime.callback);
|
|
187
|
+
while (fiber.inertia !== void 0) await fiber.inertia;
|
|
188
|
+
delete entry.fiber;
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* Remove styles after their plugin's effect cleanup has settled.
|
|
192
|
+
* @param id - Package whose factory owns the style tags.
|
|
193
|
+
*/
|
|
194
|
+
function removeOwnedStyles(id) {
|
|
195
|
+
if (typeof document === "undefined") return;
|
|
196
|
+
for (const el of document.querySelectorAll("style[data-plugin]")) if (el.getAttribute("data-plugin") === id) el.remove();
|
|
197
|
+
}
|
|
198
|
+
//#endregion
|
|
199
|
+
//#region lib/types/client/entries.js
|
|
200
|
+
/** Numeric values mirror Cordis's const enum, which bundle loaders cannot import as a runtime object. */
|
|
201
|
+
const ACTIVE = 2;
|
|
202
|
+
const FAILED = 3;
|
|
203
|
+
/** Revisions and requests identify desired code; URLs only select its immutable delivery resource. */
|
|
204
|
+
function entryTargets(manifest) {
|
|
205
|
+
return JSON.stringify(manifest.modules.map((row) => [
|
|
206
|
+
row.id,
|
|
207
|
+
row.rev,
|
|
208
|
+
row.inject,
|
|
209
|
+
row.external
|
|
210
|
+
]));
|
|
211
|
+
}
|
|
212
|
+
/** Manages only entries created from the Host manifest; other Loader contributors retain ownership. */
|
|
213
|
+
var ClientEntries = class {
|
|
214
|
+
modules;
|
|
215
|
+
index;
|
|
216
|
+
/** Stable observable consumed by page diagnostics through the renderer's injected hook. */
|
|
217
|
+
state = {
|
|
218
|
+
getSnapshot: () => this.snapshot,
|
|
219
|
+
subscribe: (listener) => {
|
|
220
|
+
this.listeners.add(listener);
|
|
221
|
+
return () => {
|
|
222
|
+
this.listeners.delete(listener);
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
};
|
|
226
|
+
snapshot = {
|
|
227
|
+
syncing: false,
|
|
228
|
+
failures: []
|
|
229
|
+
};
|
|
230
|
+
listeners = /* @__PURE__ */ new Set();
|
|
231
|
+
managed = /* @__PURE__ */ new Map();
|
|
232
|
+
revisions = /* @__PURE__ */ new Map();
|
|
233
|
+
loader;
|
|
234
|
+
queue = Promise.resolve();
|
|
235
|
+
desired;
|
|
236
|
+
generation = 0;
|
|
237
|
+
stopped = false;
|
|
238
|
+
/**
|
|
239
|
+
* Construct the page controller before Cordis boot.
|
|
240
|
+
* @param modules - Module arrival and materialization owner.
|
|
241
|
+
* @param index - Private descriptor replacement and unused-module cleanup.
|
|
242
|
+
*/
|
|
243
|
+
constructor(modules, index) {
|
|
244
|
+
this.modules = modules;
|
|
245
|
+
this.index = index;
|
|
246
|
+
this.desired = modules.manifest;
|
|
247
|
+
}
|
|
248
|
+
/**
|
|
249
|
+
* Create the initial roster and retain its entry identities for subsequent reconciliation.
|
|
250
|
+
* @param loader - Page Loader, already configured with the module system.
|
|
251
|
+
* @param manifest - Initial roster audited by the boot caller.
|
|
252
|
+
* @returns after initial entries and their activation settle; boot owns its activation audit.
|
|
253
|
+
*/
|
|
254
|
+
start(loader, manifest) {
|
|
255
|
+
if (this.loader !== void 0) throw new Error("client-modules: entries already started");
|
|
256
|
+
this.loader = loader;
|
|
257
|
+
this.desired = manifest;
|
|
258
|
+
loader.ctx.effect(() => () => {
|
|
259
|
+
this.stopped = true;
|
|
260
|
+
this.generation++;
|
|
261
|
+
return this.queue;
|
|
262
|
+
}, "client-modules: entry reconciliation");
|
|
263
|
+
return this.enqueue(async () => {
|
|
264
|
+
await Promise.all(this.desired.plugins.map(async ({ id }) => {
|
|
265
|
+
await this.create(loader, id);
|
|
266
|
+
}));
|
|
267
|
+
await loader.await();
|
|
268
|
+
for (const row of this.modules.manifest.modules) this.revisions.set(row.id, row.rev);
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
/**
|
|
272
|
+
* Validate and apply the latest full Host graph. Changed targets cancel obsolete mounts; identical targets share pending loads.
|
|
273
|
+
* @param graph - JSON-decoded graph received from the Host.
|
|
274
|
+
* @returns after the queued reconciliation; per-package failures remain available in {@link state}.
|
|
275
|
+
*/
|
|
276
|
+
sync(graph) {
|
|
277
|
+
const manifest = parseBootManifest(graph);
|
|
278
|
+
if (entryTargets(manifest) !== entryTargets(this.desired)) this.generation++;
|
|
279
|
+
this.desired = manifest;
|
|
280
|
+
const generation = this.generation;
|
|
281
|
+
return this.enqueue(() => this.reconcile(generation));
|
|
282
|
+
}
|
|
283
|
+
/**
|
|
284
|
+
* Retry failed entries against the latest graph, including an unchanged revision.
|
|
285
|
+
* @returns after retry settlement, with remaining errors in {@link state}.
|
|
286
|
+
*/
|
|
287
|
+
retry() {
|
|
288
|
+
const generation = ++this.generation;
|
|
289
|
+
return this.enqueue(() => this.reconcile(generation));
|
|
290
|
+
}
|
|
291
|
+
/**
|
|
292
|
+
* Replace one entry's code in the same queue as graph updates; duplicate revisions are ignored.
|
|
293
|
+
* Entries missing after a failed import are reconciled; bootstrap replacement fails before teardown.
|
|
294
|
+
* @param id - Package id from a rebuilt frame.
|
|
295
|
+
* @param rev - Opaque revision selecting the rebuilt artifact.
|
|
296
|
+
* @returns after queued work; replacement errors reject, while per-package reconciliation errors remain in {@link state}.
|
|
297
|
+
*/
|
|
298
|
+
reload(id, rev) {
|
|
299
|
+
this.desired = {
|
|
300
|
+
...this.desired,
|
|
301
|
+
modules: this.desired.modules.map((row) => row.id === id ? {
|
|
302
|
+
...row,
|
|
303
|
+
rev
|
|
304
|
+
} : row)
|
|
305
|
+
};
|
|
306
|
+
return this.enqueue(async () => {
|
|
307
|
+
const desired = this.desired.modules.find((row) => row.id === id);
|
|
308
|
+
if (this.stopped || desired === void 0) return;
|
|
309
|
+
const entry = this.managed.get(id);
|
|
310
|
+
if (entry === void 0) {
|
|
311
|
+
this.modules.invalidate(id, desired.rev);
|
|
312
|
+
removeOwnedStyles(id);
|
|
313
|
+
await this.reconcile(this.generation);
|
|
314
|
+
return;
|
|
315
|
+
}
|
|
316
|
+
if (this.revisions.get(id) === rev) return;
|
|
317
|
+
this.publish({
|
|
318
|
+
syncing: true,
|
|
319
|
+
failures: this.snapshot.failures.filter((failure) => failure.id !== id)
|
|
320
|
+
});
|
|
321
|
+
await this.replace(entry, id, rev, this.generation);
|
|
322
|
+
this.publish({
|
|
323
|
+
syncing: false,
|
|
324
|
+
failures: this.snapshot.failures
|
|
325
|
+
});
|
|
326
|
+
}, id);
|
|
327
|
+
}
|
|
328
|
+
publish(snapshot) {
|
|
329
|
+
this.snapshot = snapshot;
|
|
330
|
+
for (const listener of [...this.listeners]) try {
|
|
331
|
+
listener();
|
|
332
|
+
} catch (error) {
|
|
333
|
+
console.error("client-modules: synchronization subscriber failed", error);
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
enqueue(task, subject = "graph") {
|
|
337
|
+
const run = this.queue.then(task);
|
|
338
|
+
this.queue = run.then(() => void 0, (error) => {
|
|
339
|
+
this.publish({
|
|
340
|
+
syncing: false,
|
|
341
|
+
failures: [...this.snapshot.failures.filter((failure) => failure.id !== subject), {
|
|
342
|
+
id: subject,
|
|
343
|
+
message: String(error)
|
|
344
|
+
}]
|
|
345
|
+
});
|
|
346
|
+
});
|
|
347
|
+
return run;
|
|
348
|
+
}
|
|
349
|
+
current(generation) {
|
|
350
|
+
return !this.stopped && generation === this.generation;
|
|
351
|
+
}
|
|
352
|
+
/** Keep ownership even when Loader rejects a module's plugin exports after inserting its entry. */
|
|
353
|
+
async create(loader, id) {
|
|
354
|
+
const options = { name: id };
|
|
355
|
+
const entryId = loader.ensureId(options);
|
|
356
|
+
try {
|
|
357
|
+
await loader.create(options);
|
|
358
|
+
} finally {
|
|
359
|
+
this.managed.set(id, loader.resolve(entryId));
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
async replace(entry, id, rev, generation) {
|
|
363
|
+
this.index.invalidateForReplacement(id, rev);
|
|
364
|
+
await this.modules.prefetch(id);
|
|
365
|
+
if (!this.current(generation)) return;
|
|
366
|
+
await tearDownEntryFiber(entry);
|
|
367
|
+
removeOwnedStyles(id);
|
|
368
|
+
if (!this.current(generation)) return;
|
|
369
|
+
await this.modules.import(id, "", {});
|
|
370
|
+
if (!this.current(generation)) return;
|
|
371
|
+
await entry.refresh();
|
|
372
|
+
await entry.fiber?.await();
|
|
373
|
+
if (entry.fiber === void 0) throw new Error(`client-modules: ${id} import failed (see console)`);
|
|
374
|
+
this.revisions.set(id, rev);
|
|
375
|
+
}
|
|
376
|
+
async reconcile(generation) {
|
|
377
|
+
if (!this.current(generation)) return;
|
|
378
|
+
const loader = this.loader;
|
|
379
|
+
if (loader === void 0) throw new Error("client-modules: entries have not started");
|
|
380
|
+
const manifest = this.desired;
|
|
381
|
+
this.publish({
|
|
382
|
+
syncing: true,
|
|
383
|
+
failures: []
|
|
384
|
+
});
|
|
385
|
+
const failures = [];
|
|
386
|
+
this.index.update(manifest, this.managed.keys());
|
|
387
|
+
const wanted = new Set(manifest.plugins.map((row) => row.id));
|
|
388
|
+
for (const [id, entry] of this.managed) {
|
|
389
|
+
if (wanted.has(id)) continue;
|
|
390
|
+
const fiber = entry.fiber;
|
|
391
|
+
loader.remove(entry.id);
|
|
392
|
+
this.managed.delete(id);
|
|
393
|
+
this.revisions.delete(id);
|
|
394
|
+
while (fiber?.inertia !== void 0) await fiber.inertia;
|
|
395
|
+
}
|
|
396
|
+
for (const row of manifest.modules) {
|
|
397
|
+
if (!this.current(generation)) break;
|
|
398
|
+
try {
|
|
399
|
+
const entry = this.managed.get(row.id);
|
|
400
|
+
if (entry === void 0) {
|
|
401
|
+
await this.modules.prefetch(row.id);
|
|
402
|
+
if (!this.current(generation)) break;
|
|
403
|
+
await this.modules.import(row.id, "", {});
|
|
404
|
+
if (!this.current(generation)) break;
|
|
405
|
+
await this.create(loader, row.id);
|
|
406
|
+
this.revisions.set(row.id, row.rev);
|
|
407
|
+
} else if (this.revisions.get(row.id) !== row.rev) await this.replace(entry, row.id, row.rev, generation);
|
|
408
|
+
else if (entry.fiber === void 0) await this.replace(entry, row.id, row.rev, generation);
|
|
409
|
+
else if (entry.fiber.state === FAILED) entry.fiber.update(entry.options.config);
|
|
410
|
+
} catch (error) {
|
|
411
|
+
failures.push({
|
|
412
|
+
id: row.id,
|
|
413
|
+
message: String(error)
|
|
414
|
+
});
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
await loader.await();
|
|
418
|
+
for (const [id, entry] of this.managed) {
|
|
419
|
+
if (failures.some((failure) => failure.id === id)) continue;
|
|
420
|
+
if (entry.fiber?.state === ACTIVE) continue;
|
|
421
|
+
try {
|
|
422
|
+
if (entry.fiber === void 0) throw new Error(`client-modules: ${id} import failed (see console)`);
|
|
423
|
+
await entry.fiber.await();
|
|
424
|
+
failures.push({
|
|
425
|
+
id,
|
|
426
|
+
message: `client-modules: ${id} is waiting for activation`
|
|
427
|
+
});
|
|
428
|
+
} catch (error) {
|
|
429
|
+
failures.push({
|
|
430
|
+
id,
|
|
431
|
+
message: String(error)
|
|
432
|
+
});
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
this.index.prune([...loader.entries()].map((entry) => entry.options.name));
|
|
436
|
+
if (this.current(generation)) this.publish({
|
|
437
|
+
syncing: false,
|
|
438
|
+
failures
|
|
439
|
+
});
|
|
440
|
+
}
|
|
441
|
+
};
|
|
442
|
+
//#endregion
|
|
175
443
|
//#region lib/types/client/system.js
|
|
176
444
|
/**
|
|
177
445
|
* ClientModuleSystem — the implementation behind the {@link ClientModuleLoader}
|
|
@@ -199,6 +467,19 @@ window.__ModuleLoader__.load({
|
|
|
199
467
|
if (!/[?&]rev=[^&#]*/.test(url)) throw new Error(`client-modules: bundle URL ${url} has no revision`);
|
|
200
468
|
return url.replace(/([?&]rev=)[^&#]*/, `$1${encodeURIComponent(rev)}`);
|
|
201
469
|
}
|
|
470
|
+
const CLIENT_CHUNK = /^client\.[A-Za-z0-9][A-Za-z0-9._-]*\.js$/;
|
|
471
|
+
/** Internal module-table key for one package-local chunk. */
|
|
472
|
+
function chunkId(ownerId, fileName) {
|
|
473
|
+
return `${ownerId}/${fileName}`;
|
|
474
|
+
}
|
|
475
|
+
/** Resolve a sibling chunk against the package's one-resource URL and current revision. */
|
|
476
|
+
function chunkUrl(row, fileName, rev) {
|
|
477
|
+
const url = atRevision(row.url, rev);
|
|
478
|
+
const resourceStart = url.indexOf("/??");
|
|
479
|
+
const revisionStart = url.indexOf("&rev=", resourceStart + 3);
|
|
480
|
+
if ((resourceStart < 0 || revisionStart < 0 ? void 0 : url.slice(resourceStart + 3, revisionStart)) !== `${row.id}/client.js`) throw new Error(`client-modules: cannot resolve chunk ${JSON.stringify(fileName)} from bundle URL ${url}`);
|
|
481
|
+
return `${url.slice(0, resourceStart)}/${row.id}/${fileName}?${url.slice(revisionStart + 1)}`;
|
|
482
|
+
}
|
|
202
483
|
/**
|
|
203
484
|
* Claim and inventory the <style> tags a factory injected during
|
|
204
485
|
* materialization: preset-emitted tags arrive pre-tagged with data-plugin;
|
|
@@ -221,14 +502,17 @@ window.__ModuleLoader__.load({
|
|
|
221
502
|
var ClientModuleSystem = class {
|
|
222
503
|
version = "client";
|
|
223
504
|
manifest;
|
|
505
|
+
entries;
|
|
224
506
|
loadCache = /* @__PURE__ */ new Map();
|
|
225
507
|
seed;
|
|
226
508
|
factories = /* @__PURE__ */ new Map();
|
|
227
509
|
bootstrapIds = /* @__PURE__ */ new Set();
|
|
228
510
|
/** In-flight script transport per URL; every row in one batch shares it. */
|
|
229
511
|
pendingArrival = /* @__PURE__ */ new Map();
|
|
512
|
+
/** Owner generation captured by in-flight chunk requests and advanced on invalidation. */
|
|
513
|
+
generations = /* @__PURE__ */ new Map();
|
|
230
514
|
/** Single-resource combo URL selected by HMR after invalidating one row. */
|
|
231
|
-
|
|
515
|
+
reloadTargets = /* @__PURE__ */ new Map();
|
|
232
516
|
/** Materialization re-entrancy guard: factory-form CJS cannot deliver partial exports, so a cycle is fatal. */
|
|
233
517
|
materializing = /* @__PURE__ */ new Set();
|
|
234
518
|
graphRows = /* @__PURE__ */ new Map();
|
|
@@ -239,12 +523,21 @@ window.__ModuleLoader__.load({
|
|
|
239
523
|
*/
|
|
240
524
|
constructor(options) {
|
|
241
525
|
this.manifest = options.manifest;
|
|
526
|
+
this.entries = new ClientEntries(this, {
|
|
527
|
+
update: (manifest, managed) => {
|
|
528
|
+
this.updateManifest(manifest, managed);
|
|
529
|
+
},
|
|
530
|
+
invalidateForReplacement: (id, rev) => {
|
|
531
|
+
if (this.bootstrapIds.has(id)) throw new Error(`client-modules: replacing bootstrap module ${id} requires a page reload`);
|
|
532
|
+
this.invalidate(id, rev);
|
|
533
|
+
},
|
|
534
|
+
prune: (roots) => {
|
|
535
|
+
this.prune(roots);
|
|
536
|
+
}
|
|
537
|
+
});
|
|
242
538
|
this.seed = new Map(Object.entries(options.staticModules));
|
|
243
539
|
this.loadBundle = options.loadBundle ?? defaultLoadBundle;
|
|
244
|
-
for (const row of options.manifest.modules)
|
|
245
|
-
if (this.graphRows.has(row.id)) throw new Error(`client-modules: duplicate graph entry "${row.id}"`);
|
|
246
|
-
this.graphRows.set(row.id, row);
|
|
247
|
-
}
|
|
540
|
+
for (const row of options.manifest.modules) this.graphRows.set(row.id, row);
|
|
248
541
|
const bootstrapId = stripClientSuffix(options.bootstrapModule.id);
|
|
249
542
|
this.bootstrapIds.add(bootstrapId);
|
|
250
543
|
this.loadCache.set(bootstrapId, {
|
|
@@ -264,16 +557,24 @@ window.__ModuleLoader__.load({
|
|
|
264
557
|
}
|
|
265
558
|
/** Register one bundle factory, rejecting a script that executes twice without invalidation. */
|
|
266
559
|
register(registration) {
|
|
267
|
-
const
|
|
268
|
-
if (
|
|
269
|
-
|
|
560
|
+
const ownerId = stripClientSuffix(registration.id);
|
|
561
|
+
if (registration.chunk !== void 0 && !CLIENT_CHUNK.test(registration.chunk)) throw new Error(`client-modules: invalid package-local chunk ${JSON.stringify(registration.chunk)}`);
|
|
562
|
+
const id = registration.chunk === void 0 ? ownerId : chunkId(ownerId, registration.chunk);
|
|
563
|
+
if (this.bootstrapIds.has(id) || this.factories.has(id)) {
|
|
564
|
+
const registrationName = registration.chunk === void 0 ? registration.id : id;
|
|
565
|
+
throw new Error(`client-modules: duplicate factory registration for "${registrationName}" (bundle executed twice without invalidate?)`);
|
|
566
|
+
}
|
|
567
|
+
this.factories.set(id, {
|
|
568
|
+
factory: registration.factory,
|
|
569
|
+
rev: this.reloadTargets.get(ownerId)?.rev ?? this.graphRows.get(ownerId)?.rev
|
|
570
|
+
});
|
|
270
571
|
}
|
|
271
572
|
/** Load one graph row so its factory is registered (idempotent per in-flight arrival). */
|
|
272
573
|
arrive(row) {
|
|
273
574
|
const { id } = row;
|
|
274
575
|
if (this.loadCache.has(id) || this.factories.has(id)) return Promise.resolve();
|
|
275
|
-
const
|
|
276
|
-
const url =
|
|
576
|
+
const reload = this.reloadTargets.get(id);
|
|
577
|
+
const url = reload?.url ?? row.initialUrl;
|
|
277
578
|
let transport = this.pendingArrival.get(url);
|
|
278
579
|
if (transport === void 0) {
|
|
279
580
|
transport = this.loadBundle(url).finally(() => {
|
|
@@ -283,7 +584,7 @@ window.__ModuleLoader__.load({
|
|
|
283
584
|
}
|
|
284
585
|
return transport.then(() => {
|
|
285
586
|
if (!this.factories.has(id)) throw new Error(`client-modules: bundle ${url} loaded without registering "${id}" via __ModuleLoader__.load`);
|
|
286
|
-
if (
|
|
587
|
+
if (reload !== void 0 && this.reloadTargets.get(id) === reload) this.reloadTargets.delete(id);
|
|
287
588
|
});
|
|
288
589
|
}
|
|
289
590
|
/** Register each injected package and unresolved dynamic request before its consumer. */
|
|
@@ -306,7 +607,7 @@ window.__ModuleLoader__.load({
|
|
|
306
607
|
await this.arrive(row);
|
|
307
608
|
}
|
|
308
609
|
/** Materialize a registered factory (synchronous; memoized in loadCache). */
|
|
309
|
-
materialize(id) {
|
|
610
|
+
materialize(id, ownerId = id) {
|
|
310
611
|
const existing = this.loadCache.get(id);
|
|
311
612
|
if (existing !== void 0) return existing;
|
|
312
613
|
const registered = this.factories.get(id);
|
|
@@ -318,24 +619,22 @@ window.__ModuleLoader__.load({
|
|
|
318
619
|
const edges = /* @__PURE__ */ new Set();
|
|
319
620
|
const record = {
|
|
320
621
|
id,
|
|
321
|
-
exports: registered(this.makeRequire(edges)),
|
|
322
|
-
styles: claimStyles(
|
|
622
|
+
exports: registered.factory(this.makeRequire(ownerId, edges)),
|
|
623
|
+
styles: claimStyles(ownerId),
|
|
323
624
|
edges
|
|
324
625
|
};
|
|
325
626
|
this.loadCache.set(id, record);
|
|
326
627
|
return record;
|
|
628
|
+
} catch (error) {
|
|
629
|
+
removeOwnedStyles(ownerId);
|
|
630
|
+
throw error;
|
|
327
631
|
} finally {
|
|
328
632
|
this.materializing.delete(id);
|
|
329
633
|
}
|
|
330
634
|
}
|
|
331
|
-
/**
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
* from here; an external dynamic package must have arrived before its
|
|
335
|
-
* consumer materializes.
|
|
336
|
-
*/
|
|
337
|
-
makeRequire(edges) {
|
|
338
|
-
return (spec) => {
|
|
635
|
+
/** Build the synchronous module-table require and its asynchronous chunk operation. */
|
|
636
|
+
makeRequire(ownerId, edges) {
|
|
637
|
+
const require = (spec) => {
|
|
339
638
|
edges.add(spec);
|
|
340
639
|
if (this.seed.has(spec)) return this.seed.get(spec);
|
|
341
640
|
const id = stripClientSuffix(spec);
|
|
@@ -344,6 +643,41 @@ window.__ModuleLoader__.load({
|
|
|
344
643
|
if (this.factories.has(id)) return this.materialize(id).exports;
|
|
345
644
|
throw new Error(`client-modules: require("${spec}") missed the module table — not a platform seed word, not a materialized module, and no registered package factory (a build-time externals drift, or a dynamic dependency that did not arrive)`);
|
|
346
645
|
};
|
|
646
|
+
require.async = async (spec) => {
|
|
647
|
+
edges.add(spec);
|
|
648
|
+
if (!spec.startsWith("./")) return await this.import(spec);
|
|
649
|
+
const fileName = spec.slice(2);
|
|
650
|
+
if (!CLIENT_CHUNK.test(fileName)) throw new Error(`client-modules: invalid relative chunk request ${JSON.stringify(spec)}`);
|
|
651
|
+
return await this.importChunk(ownerId, fileName);
|
|
652
|
+
};
|
|
653
|
+
return require;
|
|
654
|
+
}
|
|
655
|
+
/** Load, register, and materialize one package-local dynamic chunk. */
|
|
656
|
+
async importChunk(ownerId, fileName) {
|
|
657
|
+
const id = chunkId(ownerId, fileName);
|
|
658
|
+
const existing = this.loadCache.get(id);
|
|
659
|
+
if (existing !== void 0) return existing.exports;
|
|
660
|
+
if (!this.factories.has(id)) {
|
|
661
|
+
const generation = this.generations.get(ownerId) ?? 0;
|
|
662
|
+
const row = this.graphRows.get(ownerId);
|
|
663
|
+
if (row === void 0) throw new Error(`client-modules: chunk owner "${ownerId}" is not a boot graph entry`);
|
|
664
|
+
const url = chunkUrl(row, fileName, this.factories.get(ownerId)?.rev ?? this.reloadTargets.get(ownerId)?.rev ?? row.rev);
|
|
665
|
+
let transport = this.pendingArrival.get(url);
|
|
666
|
+
if (transport === void 0) {
|
|
667
|
+
transport = this.loadBundle(url).finally(() => {
|
|
668
|
+
this.pendingArrival.delete(url);
|
|
669
|
+
});
|
|
670
|
+
this.pendingArrival.set(url, transport);
|
|
671
|
+
}
|
|
672
|
+
await transport;
|
|
673
|
+
if ((this.generations.get(ownerId) ?? 0) !== generation) {
|
|
674
|
+
this.factories.delete(id);
|
|
675
|
+
this.loadCache.delete(id);
|
|
676
|
+
return await this.importChunk(ownerId, fileName);
|
|
677
|
+
}
|
|
678
|
+
if (!this.factories.has(id)) throw new Error(`client-modules: bundle ${url} loaded without registering "${id}" via __ModuleLoader__.load`);
|
|
679
|
+
}
|
|
680
|
+
return this.materialize(id, ownerId).exports;
|
|
347
681
|
}
|
|
348
682
|
async import(specifier) {
|
|
349
683
|
if (this.seed.has(specifier)) return this.seed.get(specifier);
|
|
@@ -362,14 +696,60 @@ window.__ModuleLoader__.load({
|
|
|
362
696
|
if (row === void 0) throw new Error(`client-modules: prefetch("${id}") — not a graph entry`);
|
|
363
697
|
await this.arriveGraphRow(row);
|
|
364
698
|
}
|
|
699
|
+
/** Refresh descriptors and unowned factory revisions before any entry imports its dependencies. */
|
|
700
|
+
updateManifest(manifest, managed) {
|
|
701
|
+
for (const id of this.bootstrapIds) if (this.manifest.modules.some((row) => row.id === id) && !manifest.modules.some((row) => row.id === id)) throw new Error(`client-modules: removing bootstrap module ${id} requires a page reload`);
|
|
702
|
+
const owned = new Set(managed);
|
|
703
|
+
for (const row of manifest.modules) {
|
|
704
|
+
this.graphRows.set(row.id, {
|
|
705
|
+
...row,
|
|
706
|
+
initialUrl: row.url
|
|
707
|
+
});
|
|
708
|
+
const cachedRevision = this.factories.get(row.id)?.rev ?? this.reloadTargets.get(row.id)?.rev;
|
|
709
|
+
if (!owned.has(row.id) && cachedRevision !== void 0 && cachedRevision !== row.rev) {
|
|
710
|
+
this.invalidate(row.id, row.rev);
|
|
711
|
+
removeOwnedStyles(row.id);
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
this.manifest = manifest;
|
|
715
|
+
}
|
|
716
|
+
/** Retain live Loader modules and their transitive requests before evicting unreferenced graph records. */
|
|
717
|
+
prune(roots) {
|
|
718
|
+
const retained = new Set(this.bootstrapIds);
|
|
719
|
+
const visit = (specifier) => {
|
|
720
|
+
const id = stripClientSuffix(specifier);
|
|
721
|
+
if (retained.has(id)) return;
|
|
722
|
+
retained.add(id);
|
|
723
|
+
const row = this.graphRows.get(id);
|
|
724
|
+
for (const request of [
|
|
725
|
+
...row?.external ?? [],
|
|
726
|
+
...row?.inject ?? [],
|
|
727
|
+
...this.loadCache.get(id)?.edges ?? []
|
|
728
|
+
]) visit(request);
|
|
729
|
+
};
|
|
730
|
+
for (const row of this.manifest.modules) visit(row.id);
|
|
731
|
+
for (const id of roots) visit(id);
|
|
732
|
+
for (const id of this.graphRows.keys()) {
|
|
733
|
+
if (retained.has(id)) continue;
|
|
734
|
+
this.graphRows.delete(id);
|
|
735
|
+
this.invalidate(id);
|
|
736
|
+
removeOwnedStyles(id);
|
|
737
|
+
}
|
|
738
|
+
}
|
|
365
739
|
invalidate(id, rev) {
|
|
366
740
|
const normalized = stripClientSuffix(id);
|
|
367
741
|
if (this.bootstrapIds.has(normalized)) return;
|
|
742
|
+
this.generations.set(normalized, (this.generations.get(normalized) ?? 0) + 1);
|
|
368
743
|
const row = this.graphRows.get(normalized);
|
|
369
|
-
if (row !== void 0)
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
744
|
+
if (row !== void 0) {
|
|
745
|
+
const revision = rev ?? row.rev;
|
|
746
|
+
this.reloadTargets.set(normalized, {
|
|
747
|
+
url: atRevision(row.url, revision),
|
|
748
|
+
rev: revision
|
|
749
|
+
});
|
|
750
|
+
} else this.reloadTargets.delete(normalized);
|
|
751
|
+
for (const key of this.factories.keys()) if (key === normalized || key.startsWith(`${normalized}/client.`)) this.factories.delete(key);
|
|
752
|
+
for (const key of this.loadCache.keys()) if (key === normalized || key.startsWith(`${normalized}/client.`)) this.loadCache.delete(key);
|
|
373
753
|
}
|
|
374
754
|
};
|
|
375
755
|
//#endregion
|
|
@@ -410,6 +790,7 @@ window.__ModuleLoader__.load({
|
|
|
410
790
|
exports.parseBootManifest = parseBootManifest;
|
|
411
791
|
exports.parseDshClient = parseDshClient;
|
|
412
792
|
exports.stripClientSuffix = stripClientSuffix;
|
|
793
|
+
exports.tearDownEntryFiber = tearDownEntryFiber;
|
|
413
794
|
return module.exports;
|
|
414
795
|
}
|
|
415
796
|
});
|