@crazx/dsh-client-modules 0.1.6-alpha.1.zw.3 → 0.1.6-alpha.2.zw.2
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 +16 -5
- package/README.zh.md +16 -5
- package/lib/client.js +407 -26
- package/lib/index.js +150 -80
- 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 +26 -14
- package/lib/types/client/system.d.ts +13 -8
- package/lib/types/index.d.ts +12 -7
- package/package.json +5 -4
package/lib/index.js
CHANGED
|
@@ -181,6 +181,8 @@ const COMBO_REVISION_PLACEHOLDER = "0".repeat(HASH_REVISION_LENGTH);
|
|
|
181
181
|
const SOURCE_MAP_TRAILER = /(?:\r?\n)?\/\/# sourceMappingURL=[^\r\n]*(?:\r?\n)?$/;
|
|
182
182
|
/** Debugger source name appended to page bundles in the WebWorker image. */
|
|
183
183
|
const SOURCE_URL_TRAILER = /(?:\r?\n)?\/\/# sourceURL=([^\r\n]+)(?:\r?\n)?$/;
|
|
184
|
+
/** Published package-local client chunk names accepted by the on-demand route. */
|
|
185
|
+
const CLIENT_CHUNK = /^client\.[A-Za-z0-9][A-Za-z0-9._-]*\.js$/;
|
|
184
186
|
/** Resolve `exports["./client"]` to a relative path, accepting the string and one-level conditional forms. */
|
|
185
187
|
function clientExportOf(pkgName, exportsField) {
|
|
186
188
|
if (typeof exportsField !== "object" || exportsField === null) return void 0;
|
|
@@ -203,14 +205,18 @@ function framedHash(domain, parts) {
|
|
|
203
205
|
for (const part of parts) hash.update(`${String(part.byteLength)}:`).update(part);
|
|
204
206
|
return hash.digest("hex").slice(0, HASH_REVISION_LENGTH);
|
|
205
207
|
}
|
|
206
|
-
/** Hash
|
|
207
|
-
function artifactRevision(bundle,
|
|
208
|
-
return framedHash("plugin-artifact",
|
|
208
|
+
/** Hash one completed build generation observed through its entry artifact. */
|
|
209
|
+
function artifactRevision(bundle, baseline) {
|
|
210
|
+
return framedHash("plugin-artifact", [bundle, Buffer.from(String(baseline.mtimeMs))]);
|
|
209
211
|
}
|
|
210
212
|
/** Address one ordered plugin-file list through the shared combo route. */
|
|
211
213
|
function comboUrl(ids, rev, sourceMap = false) {
|
|
212
214
|
return `/plugins/??${ids.map((id) => `${id}/client.js${sourceMap ? ".map" : ""}`).join(",")}&rev=${rev}`;
|
|
213
215
|
}
|
|
216
|
+
/** Address one package-local chunk through the same revision as its entry. */
|
|
217
|
+
function chunkUrl(id, fileName, rev, sourceMap = false) {
|
|
218
|
+
return `/plugins/${id}/${fileName}${sourceMap ? ".map" : ""}?rev=${rev}`;
|
|
219
|
+
}
|
|
214
220
|
/** Measure the longer map-form URL used to partition a startup resource list. */
|
|
215
221
|
function projectedComboUrlBytes(records) {
|
|
216
222
|
return Buffer.byteLength(comboUrl(records.map((record) => record.entry.id), COMBO_REVISION_PLACEHOLDER, true));
|
|
@@ -234,12 +240,12 @@ function partitionComboRecords(records) {
|
|
|
234
240
|
return chunks;
|
|
235
241
|
}
|
|
236
242
|
/** Remove bundle-local debug directives and retain their stable generated-file name. */
|
|
237
|
-
function
|
|
238
|
-
let source =
|
|
243
|
+
function prepareSource(resource) {
|
|
244
|
+
let source = resource.bundle.toString("utf8");
|
|
239
245
|
const sourceUrl = SOURCE_URL_TRAILER.exec(source)?.[1];
|
|
240
246
|
source = source.replace(SOURCE_URL_TRAILER, "").replace(SOURCE_MAP_TRAILER, "");
|
|
241
247
|
if (!source.endsWith("\n")) source += "\n";
|
|
242
|
-
const fallbackSource = sourceUrl === void 0 ? `/plugins/${
|
|
248
|
+
const fallbackSource = sourceUrl === void 0 ? `/plugins/${resource.id}/${resource.fileName}` : /^(?:[A-Za-z][A-Za-z\d+.-]*:|\/)/.test(sourceUrl) ? sourceUrl : `/${sourceUrl}`;
|
|
243
249
|
return {
|
|
244
250
|
source,
|
|
245
251
|
fallbackSource
|
|
@@ -249,8 +255,8 @@ function comboSource(record) {
|
|
|
249
255
|
function comboScript(input, sourceMapUrl) {
|
|
250
256
|
return Buffer.from(sourceMapUrl === void 0 ? input : `${input}//# sourceMappingURL=${sourceMapUrl}\n`);
|
|
251
257
|
}
|
|
252
|
-
/**
|
|
253
|
-
function
|
|
258
|
+
/** Read and parse an optional source map when its combo-map endpoint is requested. */
|
|
259
|
+
function readSourceMap(clientPath) {
|
|
254
260
|
let body;
|
|
255
261
|
try {
|
|
256
262
|
body = readFileSync(`${clientPath}.map`);
|
|
@@ -261,10 +267,7 @@ function sourceMapSnapshot(clientPath) {
|
|
|
261
267
|
const value = JSON.parse(body.toString("utf8"));
|
|
262
268
|
const parsed = typeof value === "object" && value !== null ? value : void 0;
|
|
263
269
|
if (parsed === void 0 || parsed.version !== 3 || !Array.isArray(parsed.sources) || parsed.sources.some((source) => typeof source !== "string") || !Array.isArray(parsed.names) || parsed.names.some((name) => typeof name !== "string") || typeof parsed.mappings !== "string") throw new Error(`client-modules: ${clientPath}.map is not a regular Source Map v3 object`);
|
|
264
|
-
return
|
|
265
|
-
body,
|
|
266
|
-
parsed
|
|
267
|
-
};
|
|
270
|
+
return parsed;
|
|
268
271
|
}
|
|
269
272
|
/** Count generated lines while assembling indexed-map section offsets. */
|
|
270
273
|
function newlineCount(value) {
|
|
@@ -273,13 +276,10 @@ function newlineCount(value) {
|
|
|
273
276
|
return count;
|
|
274
277
|
}
|
|
275
278
|
/** Resolve section sources against their original per-plugin map URL before combo relocation. */
|
|
276
|
-
function comboSectionMap(
|
|
277
|
-
const original = record.sourceMap?.parsed;
|
|
278
|
-
/* v8 ignore next -- callers add sections only for records with a source map. */
|
|
279
|
-
if (original === void 0) throw new Error(`client-modules: source map missing for ${record.entry.id}`);
|
|
279
|
+
function comboSectionMap(resource, original) {
|
|
280
280
|
const sourcePaths = original.sources;
|
|
281
281
|
const sourceRoot = typeof original.sourceRoot === "string" ? original.sourceRoot : "";
|
|
282
|
-
const base = new URL(`/plugins/${
|
|
282
|
+
const base = new URL(`/plugins/${resource.id}/client.js.map`, "http://dsh.invalid");
|
|
283
283
|
const relocated = sourcePaths.map((source) => {
|
|
284
284
|
const separator = sourceRoot !== "" && !sourceRoot.endsWith("/") && !source.startsWith("/") ? "/" : "";
|
|
285
285
|
const resolved = new URL(`${sourceRoot}${separator}${source}`, base);
|
|
@@ -303,14 +303,35 @@ function identitySectionMap(source, sourceUrl) {
|
|
|
303
303
|
mappings
|
|
304
304
|
};
|
|
305
305
|
}
|
|
306
|
-
/**
|
|
307
|
-
function
|
|
306
|
+
/** Run one producer in the first requester's microtask, not off-thread, and share its settlement. */
|
|
307
|
+
function lazyBody(produce) {
|
|
308
|
+
let result;
|
|
309
|
+
return () => {
|
|
310
|
+
result ??= Promise.resolve().then(produce);
|
|
311
|
+
return result;
|
|
312
|
+
};
|
|
313
|
+
}
|
|
314
|
+
/** Derive one combo revision from the ordered immutable row revisions. */
|
|
315
|
+
function comboRevision(resources) {
|
|
316
|
+
return framedHash("combo", resources.flatMap((resource) => [Buffer.from(resource.id), Buffer.from(resource.rev)]));
|
|
317
|
+
}
|
|
318
|
+
/** Concatenate one or more factory registrations without reading or composing source maps. */
|
|
319
|
+
function buildComboScript(resources, sourceMapUrl) {
|
|
308
320
|
let source = "";
|
|
321
|
+
for (const resource of resources) source += `${prepareSource(resource).source};\n`;
|
|
322
|
+
return comboScript(source, sourceMapUrl);
|
|
323
|
+
}
|
|
324
|
+
/** Compose one indexed map from source-map files read only for this request. */
|
|
325
|
+
function buildComboSourceMap(resources, sourceMapOf, fileName = "client.js") {
|
|
309
326
|
const sections = [];
|
|
310
327
|
let line = 0;
|
|
311
|
-
for (const
|
|
312
|
-
const prepared =
|
|
313
|
-
const
|
|
328
|
+
for (const resource of resources) {
|
|
329
|
+
const prepared = prepareSource(resource);
|
|
330
|
+
const sourceMap = sourceMapOf(resource.clientPath);
|
|
331
|
+
let section = identitySectionMap(prepared.source, prepared.fallbackSource);
|
|
332
|
+
if (sourceMap !== void 0) try {
|
|
333
|
+
section = comboSectionMap(resource, sourceMap);
|
|
334
|
+
} catch {}
|
|
314
335
|
sections.push({
|
|
315
336
|
offset: {
|
|
316
337
|
line,
|
|
@@ -318,32 +339,39 @@ function buildCombo(records, revision) {
|
|
|
318
339
|
},
|
|
319
340
|
map: section
|
|
320
341
|
});
|
|
321
|
-
|
|
322
|
-
source += bundle;
|
|
323
|
-
line += newlineCount(bundle);
|
|
342
|
+
line += newlineCount(`${prepared.source};\n`);
|
|
324
343
|
}
|
|
325
|
-
|
|
344
|
+
return Buffer.from(`${JSON.stringify({
|
|
326
345
|
version: 3,
|
|
327
|
-
file:
|
|
346
|
+
file: fileName,
|
|
328
347
|
sections
|
|
329
348
|
})}\n`);
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
349
|
+
}
|
|
350
|
+
/** Describe one combo and defer its executable and debug payloads independently. */
|
|
351
|
+
function buildCombo(records, sourceMapOf, revision) {
|
|
352
|
+
const resources = records.map((record) => ({
|
|
353
|
+
id: record.entry.id,
|
|
354
|
+
rev: record.entry.rev,
|
|
355
|
+
clientPath: record.meta.clientPath,
|
|
356
|
+
fileName: "client.js",
|
|
357
|
+
bundle: record.bundle
|
|
358
|
+
}));
|
|
359
|
+
const rev = revision ?? comboRevision(resources);
|
|
360
|
+
const entries = resources.map((resource) => resource.id);
|
|
333
361
|
const url = comboUrl(entries, rev);
|
|
334
362
|
const sourceMapUrl = comboUrl(entries, rev, true);
|
|
335
363
|
return {
|
|
336
364
|
url,
|
|
337
365
|
rev,
|
|
338
366
|
entries,
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
367
|
+
sourceMapUrl,
|
|
368
|
+
scriptBody: lazyBody(() => buildComboScript(resources, sourceMapUrl)),
|
|
369
|
+
sourceMapBody: lazyBody(() => buildComboSourceMap(resources, sourceMapOf))
|
|
342
370
|
};
|
|
343
371
|
}
|
|
344
372
|
/** Add initial-load scheduling metadata to a combo artifact. */
|
|
345
|
-
function buildBatch(phase, records) {
|
|
346
|
-
const artifact = buildCombo(records);
|
|
373
|
+
function buildBatch(phase, records, sourceMapOf) {
|
|
374
|
+
const artifact = buildCombo(records, sourceMapOf);
|
|
347
375
|
return {
|
|
348
376
|
...artifact,
|
|
349
377
|
descriptor: {
|
|
@@ -537,12 +565,13 @@ var ClientModuleRegistry = class extends Service {
|
|
|
537
565
|
/**
|
|
538
566
|
* Serve an advertised revisioned bundle or source map without a Web server.
|
|
539
567
|
* Unknown URLs return 404, unsupported methods return 405, and `HEAD`
|
|
540
|
-
* returns the same immutable headers without a body.
|
|
568
|
+
* returns the same immutable headers without materializing a body. Each body
|
|
569
|
+
* is built once on its first `GET`; script construction never reads maps.
|
|
541
570
|
* @param request - shell-carrier request for a `/plugins` resource.
|
|
542
571
|
* @returns the exact response also exposed by the optional Web route.
|
|
543
572
|
*/
|
|
544
|
-
fetchBundle(request) {
|
|
545
|
-
const resource = this.bundleResource(request.method, request.url);
|
|
573
|
+
async fetchBundle(request) {
|
|
574
|
+
const resource = await this.bundleResource(request.method, request.url);
|
|
546
575
|
const body = resource.body === void 0 ? null : Uint8Array.from(resource.body);
|
|
547
576
|
return new Response(body, {
|
|
548
577
|
status: resource.status,
|
|
@@ -562,8 +591,8 @@ var ClientModuleRegistry = class extends Service {
|
|
|
562
591
|
return baseline === void 0 ? void 0 : { ...baseline };
|
|
563
592
|
}
|
|
564
593
|
/**
|
|
565
|
-
*
|
|
566
|
-
* point through which
|
|
594
|
+
* Publish one completed bundle generation (the HMR watch's registration
|
|
595
|
+
* hook — the only entry point through which build changes reach the graph).
|
|
567
596
|
* @param id - entry id (package name).
|
|
568
597
|
* @returns the new rev, or undefined for an unknown id.
|
|
569
598
|
*/
|
|
@@ -572,14 +601,11 @@ var ClientModuleRegistry = class extends Service {
|
|
|
572
601
|
if (record === void 0) return void 0;
|
|
573
602
|
const baseline = this.captureArtifactBaseline(record.meta.clientPath);
|
|
574
603
|
const bundle = readFileSync(record.meta.clientPath);
|
|
575
|
-
const
|
|
576
|
-
const rev = artifactRevision(bundle, sourceMap);
|
|
604
|
+
const rev = artifactRevision(bundle, baseline);
|
|
577
605
|
record.baseline = baseline;
|
|
578
606
|
if (rev === record.entry.rev) return rev;
|
|
579
607
|
record.entry = graphRow(id, rev, record.meta);
|
|
580
608
|
record.bundle = bundle;
|
|
581
|
-
if (sourceMap === void 0) delete record.sourceMap;
|
|
582
|
-
else record.sourceMap = sourceMap;
|
|
583
609
|
this.composed = this.compose();
|
|
584
610
|
for (const notify of this.rebuildListeners) try {
|
|
585
611
|
notify(id, rev);
|
|
@@ -618,31 +644,32 @@ var ClientModuleRegistry = class extends Service {
|
|
|
618
644
|
const bootstrapIds = new Set(bootstrap.map((record) => record.entry.id));
|
|
619
645
|
const application = entries.filter((entry) => !bootstrapIds.has(entry.id)).map((entry) => this.table.get(entry.id)).filter((record) => record !== void 0);
|
|
620
646
|
const artifacts = [];
|
|
621
|
-
for (const records of partitionComboRecords(bootstrap)) artifacts.push(buildBatch("bootstrap", records));
|
|
622
|
-
for (const records of partitionComboRecords(application)) artifacts.push(buildBatch("application", records));
|
|
647
|
+
for (const records of partitionComboRecords(bootstrap)) artifacts.push(buildBatch("bootstrap", records, this.readSourceMap));
|
|
648
|
+
for (const records of partitionComboRecords(application)) artifacts.push(buildBatch("application", records, this.readSourceMap));
|
|
623
649
|
const batchResponses = /* @__PURE__ */ new Map();
|
|
624
650
|
for (const artifact of artifacts) {
|
|
625
|
-
batchResponses.set(artifact.descriptor.url, {
|
|
626
|
-
body: artifact.
|
|
651
|
+
batchResponses.set(artifact.descriptor.url, this.responses.get(artifact.descriptor.url) ?? {
|
|
652
|
+
body: artifact.scriptBody,
|
|
627
653
|
contentType: "text/javascript; charset=utf-8"
|
|
628
654
|
});
|
|
629
|
-
batchResponses.set(artifact.sourceMapUrl, {
|
|
630
|
-
body: artifact.
|
|
655
|
+
batchResponses.set(artifact.sourceMapUrl, this.responses.get(artifact.sourceMapUrl) ?? {
|
|
656
|
+
body: artifact.sourceMapBody,
|
|
631
657
|
contentType: "application/json; charset=utf-8"
|
|
632
658
|
});
|
|
633
659
|
}
|
|
634
660
|
const responses = new Map(batchResponses);
|
|
635
661
|
for (const record of this.table.values()) {
|
|
636
|
-
const artifact = buildCombo([record], record.entry.rev);
|
|
637
|
-
responses.set(artifact.url, {
|
|
638
|
-
body: artifact.
|
|
662
|
+
const artifact = buildCombo([record], this.readSourceMap, record.entry.rev);
|
|
663
|
+
responses.set(artifact.url, responses.get(artifact.url) ?? this.responses.get(artifact.url) ?? {
|
|
664
|
+
body: artifact.scriptBody,
|
|
639
665
|
contentType: "text/javascript; charset=utf-8"
|
|
640
666
|
});
|
|
641
|
-
responses.set(artifact.sourceMapUrl, {
|
|
642
|
-
body: artifact.
|
|
667
|
+
responses.set(artifact.sourceMapUrl, responses.get(artifact.sourceMapUrl) ?? this.responses.get(artifact.sourceMapUrl) ?? {
|
|
668
|
+
body: artifact.sourceMapBody,
|
|
643
669
|
contentType: "application/json; charset=utf-8"
|
|
644
670
|
});
|
|
645
671
|
}
|
|
672
|
+
for (const [resourceUrl, response] of this.responses) if (this.chunkRequest(new URL(resourceUrl, "http://x")) !== void 0) responses.set(resourceUrl, response);
|
|
646
673
|
this.previousBatchResponses = this.batchResponses;
|
|
647
674
|
this.batchResponses = batchResponses;
|
|
648
675
|
this.responses = responses;
|
|
@@ -772,7 +799,7 @@ var ClientModuleRegistry = class extends Service {
|
|
|
772
799
|
return `${this.initialRevisionNonce}-${String(this.nextInitialRevision++)}`;
|
|
773
800
|
}
|
|
774
801
|
/**
|
|
775
|
-
* Read the activation-time bundle
|
|
802
|
+
* Read the activation-time bundle snapshot.
|
|
776
803
|
* @param pkgName - package that declares the client bundle.
|
|
777
804
|
* @param clientPath - absolute path of the built client artifact.
|
|
778
805
|
* @returns the immutable bytes plus the pre-read filesystem baseline.
|
|
@@ -781,27 +808,24 @@ var ClientModuleRegistry = class extends Service {
|
|
|
781
808
|
initialBundleSnapshot(pkgName, clientPath) {
|
|
782
809
|
try {
|
|
783
810
|
const baseline = this.captureArtifactBaseline(clientPath);
|
|
784
|
-
const bundle = readFileSync(clientPath);
|
|
785
|
-
const sourceMap = this.readSourceMapSnapshot(clientPath);
|
|
786
811
|
return {
|
|
787
|
-
bundle,
|
|
788
|
-
baseline
|
|
789
|
-
...sourceMap === void 0 ? {} : { sourceMap }
|
|
812
|
+
bundle: readFileSync(clientPath),
|
|
813
|
+
baseline
|
|
790
814
|
};
|
|
791
815
|
} catch (error) {
|
|
792
816
|
if (error.code !== "ENOENT") throw error;
|
|
793
817
|
throw new MissingClientBundleError(pkgName, clientPath, error);
|
|
794
818
|
}
|
|
795
819
|
}
|
|
796
|
-
/** Treat a missing, torn, or malformed development map as an identity
|
|
797
|
-
|
|
820
|
+
/** Treat a missing, torn, or malformed development map as an identity section. */
|
|
821
|
+
readSourceMap = (clientPath) => {
|
|
798
822
|
try {
|
|
799
|
-
return
|
|
823
|
+
return readSourceMap(clientPath);
|
|
800
824
|
} catch (error) {
|
|
801
825
|
this.ctx.logger.warn(error);
|
|
802
826
|
return;
|
|
803
827
|
}
|
|
804
|
-
}
|
|
828
|
+
};
|
|
805
829
|
/** Reconcile one entry name against the live Loader sources. @returns whether the table changed. */
|
|
806
830
|
processOne(entryName, onError) {
|
|
807
831
|
const nextSources = /* @__PURE__ */ new Map();
|
|
@@ -859,8 +883,7 @@ var ClientModuleRegistry = class extends Service {
|
|
|
859
883
|
sourceKey: source.sourceKey,
|
|
860
884
|
meta: source.meta,
|
|
861
885
|
bundle: snapshot.bundle,
|
|
862
|
-
baseline: snapshot.baseline
|
|
863
|
-
...snapshot.sourceMap === void 0 ? {} : { sourceMap: snapshot.sourceMap }
|
|
886
|
+
baseline: snapshot.baseline
|
|
864
887
|
});
|
|
865
888
|
return true;
|
|
866
889
|
}
|
|
@@ -885,25 +908,72 @@ var ClientModuleRegistry = class extends Service {
|
|
|
885
908
|
this.composed = composed;
|
|
886
909
|
this.notifyGraphChanged();
|
|
887
910
|
}
|
|
888
|
-
|
|
911
|
+
/** Match an exact current-revision package-local chunk URL without reading its file. */
|
|
912
|
+
chunkRequest(requestUrl) {
|
|
913
|
+
const resourceUrl = `${requestUrl.pathname}${requestUrl.search}`;
|
|
914
|
+
for (const record of this.table.values()) {
|
|
915
|
+
const prefix = `/plugins/${record.entry.id}/`;
|
|
916
|
+
if (!requestUrl.pathname.startsWith(prefix)) continue;
|
|
917
|
+
const requested = requestUrl.pathname.slice(prefix.length);
|
|
918
|
+
const sourceMap = requested.endsWith(".map");
|
|
919
|
+
const fileName = sourceMap ? requested.slice(0, -4) : requested;
|
|
920
|
+
if (!CLIENT_CHUNK.test(fileName)) return void 0;
|
|
921
|
+
if (resourceUrl !== chunkUrl(record.entry.id, fileName, record.entry.rev, sourceMap)) return void 0;
|
|
922
|
+
return {
|
|
923
|
+
record,
|
|
924
|
+
fileName,
|
|
925
|
+
sourceMap,
|
|
926
|
+
resourceUrl
|
|
927
|
+
};
|
|
928
|
+
}
|
|
929
|
+
}
|
|
930
|
+
/** Build a package-local chunk response only when its URL is requested. */
|
|
931
|
+
chunkResponse(requestUrl) {
|
|
932
|
+
const request = this.chunkRequest(requestUrl);
|
|
933
|
+
if (request === void 0) return void 0;
|
|
934
|
+
const { record, fileName, sourceMap, resourceUrl } = request;
|
|
935
|
+
const clientPath = join(dirname(record.meta.clientPath), fileName);
|
|
936
|
+
if (!existsSync(clientPath)) return void 0;
|
|
937
|
+
const sourceMapUrl = chunkUrl(record.entry.id, fileName, record.entry.rev, true);
|
|
938
|
+
const resource = () => ({
|
|
939
|
+
id: record.entry.id,
|
|
940
|
+
rev: record.entry.rev,
|
|
941
|
+
clientPath,
|
|
942
|
+
fileName,
|
|
943
|
+
bundle: readFileSync(clientPath)
|
|
944
|
+
});
|
|
945
|
+
const response = sourceMap ? {
|
|
946
|
+
body: lazyBody(() => buildComboSourceMap([resource()], this.readSourceMap, fileName)),
|
|
947
|
+
contentType: "application/json; charset=utf-8"
|
|
948
|
+
} : {
|
|
949
|
+
body: lazyBody(() => buildComboScript([resource()], sourceMapUrl)),
|
|
950
|
+
contentType: "text/javascript; charset=utf-8"
|
|
951
|
+
};
|
|
952
|
+
this.responses.set(resourceUrl, response);
|
|
953
|
+
return response;
|
|
954
|
+
}
|
|
955
|
+
async bundleResource(method, url) {
|
|
889
956
|
if (method !== "GET" && method !== "HEAD") return { status: 405 };
|
|
890
957
|
const requestUrl = new URL(url, "http://x");
|
|
891
958
|
const resourceUrl = `${requestUrl.pathname}${requestUrl.search}`;
|
|
892
|
-
const response = this.responses.get(resourceUrl) ?? this.previousBatchResponses.get(resourceUrl);
|
|
893
|
-
if (response !== void 0)
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
959
|
+
const response = this.responses.get(resourceUrl) ?? this.previousBatchResponses.get(resourceUrl) ?? this.chunkResponse(requestUrl);
|
|
960
|
+
if (response !== void 0) {
|
|
961
|
+
const body = await response.body();
|
|
962
|
+
return {
|
|
963
|
+
status: 200,
|
|
964
|
+
headers: {
|
|
965
|
+
"content-type": response.contentType,
|
|
966
|
+
"cache-control": IMMUTABLE_CACHE,
|
|
967
|
+
"content-length": String(body.length)
|
|
968
|
+
},
|
|
969
|
+
...method === "HEAD" ? {} : { body }
|
|
970
|
+
};
|
|
971
|
+
}
|
|
902
972
|
return { status: 404 };
|
|
903
973
|
}
|
|
904
|
-
serveBundle = (req, res) => {
|
|
974
|
+
serveBundle = async (req, res) => {
|
|
905
975
|
/* v8 ignore next -- `?? '/'` arm: node:http always sets url on server requests. */
|
|
906
|
-
const response = this.bundleResource(req.method, req.url ?? "/");
|
|
976
|
+
const response = await this.bundleResource(req.method, req.url ?? "/");
|
|
907
977
|
res.writeHead(response.status, response.headers);
|
|
908
978
|
res.end(response.body);
|
|
909
979
|
};
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import type { Loader } from '@deepseek-ai/cordis-plugin-loader';
|
|
2
|
+
import type { ObservableSnapshot } from '@deepseek-ai/dsh-client-store';
|
|
3
|
+
import type { BootManifest, ClientModuleLoader } from './manifest.ts';
|
|
4
|
+
/** Page-local failures do not change the Host's bundle enablement. */
|
|
5
|
+
export interface ClientEntryState {
|
|
6
|
+
/** True while a snapshot, retry or code replacement is being applied. */
|
|
7
|
+
readonly syncing: boolean;
|
|
8
|
+
/** Package ids and errors from the latest reconciliation. */
|
|
9
|
+
readonly failures: readonly {
|
|
10
|
+
readonly id: string;
|
|
11
|
+
readonly message: string;
|
|
12
|
+
}[];
|
|
13
|
+
}
|
|
14
|
+
/** Module-table capabilities used within serialized entry operations. */
|
|
15
|
+
interface ModuleIndex {
|
|
16
|
+
update(manifest: BootManifest, managed: Iterable<string>): void;
|
|
17
|
+
invalidateForReplacement(id: string, rev: string): void;
|
|
18
|
+
prune(roots: Iterable<string>): void;
|
|
19
|
+
}
|
|
20
|
+
/** Manages only entries created from the Host manifest; other Loader contributors retain ownership. */
|
|
21
|
+
export declare class ClientEntries {
|
|
22
|
+
private readonly modules;
|
|
23
|
+
private readonly index;
|
|
24
|
+
/** Stable observable consumed by page diagnostics through the renderer's injected hook. */
|
|
25
|
+
readonly state: ObservableSnapshot<ClientEntryState>;
|
|
26
|
+
private snapshot;
|
|
27
|
+
private readonly listeners;
|
|
28
|
+
private readonly managed;
|
|
29
|
+
private readonly revisions;
|
|
30
|
+
private loader;
|
|
31
|
+
private queue;
|
|
32
|
+
private desired;
|
|
33
|
+
private generation;
|
|
34
|
+
private stopped;
|
|
35
|
+
/**
|
|
36
|
+
* Construct the page controller before Cordis boot.
|
|
37
|
+
* @param modules - Module arrival and materialization owner.
|
|
38
|
+
* @param index - Private descriptor replacement and unused-module cleanup.
|
|
39
|
+
*/
|
|
40
|
+
constructor(modules: ClientModuleLoader, index: ModuleIndex);
|
|
41
|
+
/**
|
|
42
|
+
* Create the initial roster and retain its entry identities for subsequent reconciliation.
|
|
43
|
+
* @param loader - Page Loader, already configured with the module system.
|
|
44
|
+
* @param manifest - Initial roster audited by the boot caller.
|
|
45
|
+
* @returns after initial entries and their activation settle; boot owns its activation audit.
|
|
46
|
+
*/
|
|
47
|
+
start(loader: Loader, manifest: BootManifest): Promise<void>;
|
|
48
|
+
/**
|
|
49
|
+
* Validate and apply the latest full Host graph. Changed targets cancel obsolete mounts; identical targets share pending loads.
|
|
50
|
+
* @param graph - JSON-decoded graph received from the Host.
|
|
51
|
+
* @returns after the queued reconciliation; per-package failures remain available in {@link state}.
|
|
52
|
+
*/
|
|
53
|
+
sync(graph: unknown): Promise<void>;
|
|
54
|
+
/**
|
|
55
|
+
* Retry failed entries against the latest graph, including an unchanged revision.
|
|
56
|
+
* @returns after retry settlement, with remaining errors in {@link state}.
|
|
57
|
+
*/
|
|
58
|
+
retry(): Promise<void>;
|
|
59
|
+
/**
|
|
60
|
+
* Replace one entry's code in the same queue as graph updates; duplicate revisions are ignored.
|
|
61
|
+
* Entries missing after a failed import are reconciled; bootstrap replacement fails before teardown.
|
|
62
|
+
* @param id - Package id from a rebuilt frame.
|
|
63
|
+
* @param rev - Opaque revision selecting the rebuilt artifact.
|
|
64
|
+
* @returns after queued work; replacement errors reject, while per-package reconciliation errors remain in {@link state}.
|
|
65
|
+
*/
|
|
66
|
+
reload(id: string, rev: string): Promise<void>;
|
|
67
|
+
private publish;
|
|
68
|
+
private enqueue;
|
|
69
|
+
private current;
|
|
70
|
+
/** Keep ownership even when Loader rejects a module's plugin exports after inserting its entry. */
|
|
71
|
+
private create;
|
|
72
|
+
private replace;
|
|
73
|
+
private reconcile;
|
|
74
|
+
}
|
|
75
|
+
export {};
|
|
76
|
+
//# sourceMappingURL=entries.d.ts.map
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/** Loader lifecycle operations shared by live graph reconciliation and code replacement. */
|
|
2
|
+
import type { Entry } from '@deepseek-ai/cordis-plugin-loader';
|
|
3
|
+
/**
|
|
4
|
+
* Release a runtime before clearing its entry fiber so Loader refresh can import new code.
|
|
5
|
+
* Registry deletion prevents Loader from treating replacement as a user disable.
|
|
6
|
+
* @param entry - Entry retained for code replacement.
|
|
7
|
+
*/
|
|
8
|
+
export declare function tearDownEntryFiber(entry: Entry): Promise<void>;
|
|
9
|
+
/**
|
|
10
|
+
* Remove styles after their plugin's effect cleanup has settled.
|
|
11
|
+
* @param id - Package whose factory owns the style tags.
|
|
12
|
+
*/
|
|
13
|
+
export declare function removeOwnedStyles(id: string): void;
|
|
14
|
+
//# sourceMappingURL=entry-lifecycle.d.ts.map
|
|
@@ -14,6 +14,8 @@ import type { Context } from '@deepseek-ai/cordis';
|
|
|
14
14
|
import { ClientModuleSystem } from './system.ts';
|
|
15
15
|
import type { ClientBootstrapModule, ClientModuleCreateOptions, ClientModuleLoaderTarget } from './manifest.ts';
|
|
16
16
|
export { ClientModuleSystem };
|
|
17
|
+
export { tearDownEntryFiber } from './entry-lifecycle.ts';
|
|
18
|
+
export type { ClientEntries, ClientEntryState } from './entries.ts';
|
|
17
19
|
export { exactPackageSpecifier, parseBootManifest, parseDshClient, stripClientSuffix } from './manifest.ts';
|
|
18
20
|
export type { BootManifest, BootModuleRow, BootPluginRow, ClientBootstrapModule, ClientBundleRegistration, ClientModuleCreateOptions, ClientModuleLoader, ClientModuleLoaderTarget, ClientModuleRecord, ClientModuleSystemOptions, DshWindow, WebBootEntry, WebBootGraph, } from './manifest.ts';
|
|
19
21
|
/**
|
|
@@ -29,6 +29,7 @@
|
|
|
29
29
|
* composes the wire.
|
|
30
30
|
*/
|
|
31
31
|
import type { DshClientManifest } from '@deepseek-ai/dsh-package-manifest';
|
|
32
|
+
import type { ClientEntries } from './entries.ts';
|
|
32
33
|
import type { ClientModuleSystem } from './system.ts';
|
|
33
34
|
declare module '@deepseek-ai/cordis' {
|
|
34
35
|
interface Context {
|
|
@@ -58,22 +59,22 @@ export interface WebBootEntry {
|
|
|
58
59
|
/** Non-baseline module specifiers this row requests; omitted when it requests none. */
|
|
59
60
|
external?: string[];
|
|
60
61
|
}
|
|
61
|
-
/** Initial scheduling phase for one
|
|
62
|
+
/** Initial scheduling phase for one revisioned combo script. */
|
|
62
63
|
export type WebBootBatchPhase = 'bootstrap' | 'application';
|
|
63
64
|
/** One initial combo script; a scheduling phase may span several descriptors. */
|
|
64
65
|
export interface WebBootBatch {
|
|
65
66
|
/** Parser-blocking bootstrap or preloaded application scheduling. */
|
|
66
67
|
phase: WebBootBatchPhase;
|
|
67
|
-
/**
|
|
68
|
+
/** Revisioned combo script endpoint. */
|
|
68
69
|
url: string;
|
|
69
|
-
/** Revision
|
|
70
|
+
/** Revision derived from the ordered entry revisions. */
|
|
70
71
|
rev: string;
|
|
71
72
|
/** Graph entry ids whose factories the script registers, in execution order. */
|
|
72
73
|
entries: string[];
|
|
73
74
|
}
|
|
74
75
|
/** The composed client entry graph the host injects as `window.__DSH_BOOT__`. */
|
|
75
76
|
export interface WebBootGraph {
|
|
76
|
-
/** Consistency anchor over the
|
|
77
|
+
/** Consistency anchor over the current entry and batch descriptors. */
|
|
77
78
|
rev: string;
|
|
78
79
|
/**
|
|
79
80
|
* Composed entries in module-graph order — a dynamic package row precedes
|
|
@@ -90,7 +91,7 @@ export interface BootModuleRow {
|
|
|
90
91
|
id: string;
|
|
91
92
|
/** Revisioned single-resource combo endpoint used after HMR invalidation. */
|
|
92
93
|
url: string;
|
|
93
|
-
/**
|
|
94
|
+
/** Revisioned combo endpoint used before the first HMR invalidation. */
|
|
94
95
|
initialUrl: string;
|
|
95
96
|
/** Opaque plugin-artifact revision used after HMR invalidation. */
|
|
96
97
|
rev: string;
|
|
@@ -162,16 +163,25 @@ export declare function stripClientSuffix(spec: string): string;
|
|
|
162
163
|
* @returns the manifest with optional plugin-view fields normalized.
|
|
163
164
|
*/
|
|
164
165
|
export declare function parseBootManifest(wire: unknown): BootManifest;
|
|
166
|
+
/** Module resolver passed into a registered Client bundle factory. */
|
|
167
|
+
export interface ClientBundleRequire {
|
|
168
|
+
/** Resolve a module-table dependency synchronously. */
|
|
169
|
+
(specifier: string): unknown;
|
|
170
|
+
/** Load and resolve a package-local dynamic chunk asynchronously. */
|
|
171
|
+
async(specifier: string): Promise<unknown>;
|
|
172
|
+
}
|
|
165
173
|
/** One client bundle's factory registration submitted through `window.__ModuleLoader__.load`. */
|
|
166
174
|
export interface ClientBundleRegistration {
|
|
167
175
|
/** Plugin id (package name) — the registration key; must match the graph row being executed. */
|
|
168
176
|
id: string;
|
|
177
|
+
/** Package-local chunk filename; absent for the package's `client.js` entry. */
|
|
178
|
+
chunk?: string;
|
|
169
179
|
/**
|
|
170
|
-
* Closure factory holding the whole bundle body: receives the
|
|
171
|
-
* require
|
|
172
|
-
* once, at materialization.
|
|
180
|
+
* Closure factory holding the whole bundle body: receives the module-table
|
|
181
|
+
* require whose `async` operation loads generated chunks, and returns the
|
|
182
|
+
* bundle's exports. The factory runs once, at materialization.
|
|
173
183
|
*/
|
|
174
|
-
factory: (require:
|
|
184
|
+
factory: (require: ClientBundleRequire) => Record<string, unknown>;
|
|
175
185
|
}
|
|
176
186
|
/** Inputs passed by the web entry when it creates the client module system. */
|
|
177
187
|
export interface ClientModuleCreateOptions {
|
|
@@ -226,9 +236,11 @@ export interface ClientModuleRecord {
|
|
|
226
236
|
export interface ClientModuleLoader {
|
|
227
237
|
/** Discriminant against Node's internal loader shapes ('v1'/'v2'). */
|
|
228
238
|
version: 'client';
|
|
229
|
-
/**
|
|
239
|
+
/** Latest parsed Host graph, updated by live entry reconciliation. */
|
|
230
240
|
manifest: BootManifest;
|
|
231
|
-
/**
|
|
241
|
+
/** Page-owned entry reconciliation, shared by boot, graph updates and HMR. */
|
|
242
|
+
entries: ClientEntries;
|
|
243
|
+
/** Materialized-module registry: entry or package-local chunk id → record. */
|
|
232
244
|
loadCache: Map<string, ClientModuleRecord>;
|
|
233
245
|
/**
|
|
234
246
|
* Internal contract consumed by the vendored Loader's `tree.import`. Resolves
|
|
@@ -252,8 +264,8 @@ export interface ClientModuleLoader {
|
|
|
252
264
|
*/
|
|
253
265
|
prefetch(id: string): Promise<void>;
|
|
254
266
|
/**
|
|
255
|
-
* Full reset of one non-bootstrap
|
|
256
|
-
* materialized
|
|
267
|
+
* Full reset of one non-bootstrap package: drop its entry and chunk factories
|
|
268
|
+
* and materialized records so the next prefetch/import loads its one-resource
|
|
257
269
|
* combo script rather than the initial multi-resource request. The bootstrap
|
|
258
270
|
* module remains materialized.
|
|
259
271
|
* @param id - entry name to invalidate.
|
|
@@ -264,7 +276,7 @@ export interface ClientModuleLoader {
|
|
|
264
276
|
}
|
|
265
277
|
/** Internal construction inputs assembled by the modules bundle's bootstrap export. */
|
|
266
278
|
export interface ClientModuleSystemOptions {
|
|
267
|
-
/**
|
|
279
|
+
/** Boot graph validated by {@link parseBootManifest}, owned by the resulting module system. */
|
|
268
280
|
manifest: BootManifest;
|
|
269
281
|
/** Module-table seed: platform-singleton specifier → shell instance. */
|
|
270
282
|
staticModules: Record<string, unknown>;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { ClientEntries } from './entries.ts';
|
|
1
2
|
import type { BootManifest, ClientModuleLoader, ClientModuleRecord, ClientModuleSystemOptions } from './manifest.ts';
|
|
2
3
|
/**
|
|
3
4
|
* The client module system: state tables plus the arrival/materialization
|
|
@@ -8,15 +9,18 @@ import type { BootManifest, ClientModuleLoader, ClientModuleRecord, ClientModule
|
|
|
8
9
|
*/
|
|
9
10
|
export declare class ClientModuleSystem implements ClientModuleLoader {
|
|
10
11
|
readonly version = "client";
|
|
11
|
-
|
|
12
|
+
manifest: BootManifest;
|
|
13
|
+
readonly entries: ClientEntries;
|
|
12
14
|
readonly loadCache: Map<string, ClientModuleRecord>;
|
|
13
15
|
private readonly seed;
|
|
14
16
|
private readonly factories;
|
|
15
17
|
private readonly bootstrapIds;
|
|
16
18
|
/** In-flight script transport per URL; every row in one batch shares it. */
|
|
17
19
|
private readonly pendingArrival;
|
|
20
|
+
/** Owner generation captured by in-flight chunk requests and advanced on invalidation. */
|
|
21
|
+
private readonly generations;
|
|
18
22
|
/** Single-resource combo URL selected by HMR after invalidating one row. */
|
|
19
|
-
private readonly
|
|
23
|
+
private readonly reloadTargets;
|
|
20
24
|
/** Materialization re-entrancy guard: factory-form CJS cannot deliver partial exports, so a cycle is fatal. */
|
|
21
25
|
private readonly materializing;
|
|
22
26
|
private readonly graphRows;
|
|
@@ -34,15 +38,16 @@ export declare class ClientModuleSystem implements ClientModuleLoader {
|
|
|
34
38
|
private arriveGraphRow;
|
|
35
39
|
/** Materialize a registered factory (synchronous; memoized in loadCache). */
|
|
36
40
|
private materialize;
|
|
37
|
-
/**
|
|
38
|
-
* The synchronous require answered to factories: seed → memoized record →
|
|
39
|
-
* registered factory. Fetching is async and therefore unreachable
|
|
40
|
-
* from here; an external dynamic package must have arrived before its
|
|
41
|
-
* consumer materializes.
|
|
42
|
-
*/
|
|
41
|
+
/** Build the synchronous module-table require and its asynchronous chunk operation. */
|
|
43
42
|
private makeRequire;
|
|
43
|
+
/** Load, register, and materialize one package-local dynamic chunk. */
|
|
44
|
+
private importChunk;
|
|
44
45
|
import(specifier: string): Promise<unknown>;
|
|
45
46
|
prefetch(id: string): Promise<void>;
|
|
47
|
+
/** Refresh descriptors and unowned factory revisions before any entry imports its dependencies. */
|
|
48
|
+
private updateManifest;
|
|
49
|
+
/** Retain live Loader modules and their transitive requests before evicting unreferenced graph records. */
|
|
50
|
+
private prune;
|
|
46
51
|
invalidate(id: string, rev?: string): void;
|
|
47
52
|
}
|
|
48
53
|
//# sourceMappingURL=system.d.ts.map
|