@spicetify/kit 0.4.2 → 0.4.4
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/create.js +3 -3
- package/dist/from-theme.js +1 -1
- package/dist/push.js +14 -6
- package/dist/stdlib-boundary.js +2 -1
- package/dist/vault-metadata.js +8 -0
- package/dist/version.js +1 -1
- package/package.json +2 -2
- package/vendor/classmaps/{1030000/classmap-19f856aefd5.json → 1030003/classmap.json} +56 -61
- package/vendor/stdlib/metadata.json +1 -1
- package/vendor/stdlib/src/util.ts +20 -5
- package/vendor/stdlib/src/webpack/ComponentLibrary.xpui.ts +12 -2
- package/vendor/stdlib/src/webpack/index.ts +37 -39
- package/vendor/stdlib/src/webpack/registry-quiet.ts +30 -0
package/dist/create.js
CHANGED
|
@@ -206,7 +206,7 @@ test("nowPlaying returns the track name, or a fallback when idle", () => {
|
|
|
206
206
|
function writeThemeModule(dir, name, description, author, bare, cwd) {
|
|
207
207
|
writeFileSync(path.join(dir, "metadata.json"), `${JSON.stringify({
|
|
208
208
|
name,
|
|
209
|
-
|
|
209
|
+
kind: "theme",
|
|
210
210
|
version: "0.1.0",
|
|
211
211
|
authors: [author],
|
|
212
212
|
description,
|
|
@@ -315,13 +315,13 @@ export async function runCreate(argv, cwd = process.cwd()) {
|
|
|
315
315
|
writeThemeModule(dir, name, description, author, bare, cwd);
|
|
316
316
|
return;
|
|
317
317
|
}
|
|
318
|
-
const
|
|
318
|
+
const kind = template === "app" ? "app" : "extension";
|
|
319
319
|
// Extensions are behavior-only; templates that render a page ship css.
|
|
320
320
|
const hasCss = template !== "extension";
|
|
321
321
|
const entries = hasCss ? { js: "index.js", css: "index.css" } : { js: "index.js" };
|
|
322
322
|
writeFileSync(path.join(dir, "metadata.json"), `${JSON.stringify({
|
|
323
323
|
name,
|
|
324
|
-
|
|
324
|
+
kind,
|
|
325
325
|
version: "0.1.0",
|
|
326
326
|
authors: [author],
|
|
327
327
|
description,
|
package/dist/from-theme.js
CHANGED
|
@@ -58,7 +58,7 @@ export async function runFromTheme(argv, cwd = process.cwd()) {
|
|
|
58
58
|
}
|
|
59
59
|
writeFileSync(path.join(dir, "metadata.json"), `${JSON.stringify({
|
|
60
60
|
name,
|
|
61
|
-
|
|
61
|
+
kind: "theme",
|
|
62
62
|
version: "0.1.0",
|
|
63
63
|
authors: [author],
|
|
64
64
|
description: `${path.basename(themeDir)} theme, migrated from the classic format`,
|
package/dist/push.js
CHANGED
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
*/
|
|
13
13
|
import { readdirSync, readFileSync, statSync } from "node:fs";
|
|
14
14
|
import path from "node:path";
|
|
15
|
+
import { KIND_OF_META_SOURCE } from "./vault-metadata.js";
|
|
15
16
|
// localStorage quota is ~5 MB for the xpui origin, shared with Spotify's own
|
|
16
17
|
// keys and other local modules, so thresholds are conservative. Counted in
|
|
17
18
|
// UTF-16 code units (JSON.stringify(...).length), the unit the quota counts.
|
|
@@ -234,9 +235,15 @@ export function push(rec, id, port) {
|
|
|
234
235
|
// Refuse an oversized install before opening the socket (U7), so the failure
|
|
235
236
|
// is a named cause here rather than an opaque client-side quota error.
|
|
236
237
|
checkQuota(rec);
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
238
|
+
return evaluate(port, pushExpression(rec, id, nonce, stamped));
|
|
239
|
+
}
|
|
240
|
+
/**
|
|
241
|
+
* The expression push evaluates in the client: it installs the record, then
|
|
242
|
+
* re-enables anything the unload cascade took down, except other themes when
|
|
243
|
+
* the pushed module is one.
|
|
244
|
+
*/
|
|
245
|
+
export function pushExpression(rec, id, nonce, stamped) {
|
|
246
|
+
return `(async () => {
|
|
240
247
|
${AWAIT_LOADER}
|
|
241
248
|
if (!M) return JSON.stringify({ error: "loader not ready" });
|
|
242
249
|
const rec = ${JSON.stringify(rec)};
|
|
@@ -251,8 +258,10 @@ export function push(rec, id, port) {
|
|
|
251
258
|
if (reenabled) await (M.reload ?? M.enable)(id);
|
|
252
259
|
// Re-enabling a theme the loader just unloaded would fight the
|
|
253
260
|
// single-active-theme invariant and knock the pushed theme back off.
|
|
254
|
-
const
|
|
255
|
-
const
|
|
261
|
+
const kindOf = ${KIND_OF_META_SOURCE};
|
|
262
|
+
const themed = (meta) => kindOf(meta) === "theme";
|
|
263
|
+
const pushedIsTheme = themed(rec.metadata);
|
|
264
|
+
const isTheme = (mid) => themed(M.manifest?.modules?.find((m) => m.identifier === mid));
|
|
256
265
|
for (const other of before) {
|
|
257
266
|
if (pushedIsTheme && isTheme(other)) continue;
|
|
258
267
|
const s = M.list().find((m) => m.identifier === other);
|
|
@@ -268,7 +277,6 @@ export function push(rec, id, port) {
|
|
|
268
277
|
reenabled,
|
|
269
278
|
});
|
|
270
279
|
})()`;
|
|
271
|
-
return evaluate(port, expr);
|
|
272
280
|
}
|
|
273
281
|
// removeLocal drops a hot-pushed override so the client falls back to the
|
|
274
282
|
// staged or store-installed copy.
|
package/dist/stdlib-boundary.js
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
import { readdirSync, readFileSync, statSync } from "node:fs";
|
|
6
6
|
import path from "node:path";
|
|
7
|
+
import { kindOfMeta } from "./vault-metadata.js";
|
|
7
8
|
export const STDLIB_BOUNDARY_WARNING_RULES = ["ambient-client", "client-dom", "direct-map"];
|
|
8
9
|
const SOURCE_EXTENSIONS = new Set([".js", ".jsx", ".mjs", ".ts", ".tsx"]);
|
|
9
10
|
const STYLE_EXTENSIONS = new Set([".css", ".scss"]);
|
|
@@ -202,7 +203,7 @@ export function checkExternalStdlibBoundary(root, metadata) {
|
|
|
202
203
|
const entries = meta.entries;
|
|
203
204
|
if (typeof entries?.js !== "string" || meta.name === "stdlib")
|
|
204
205
|
return out;
|
|
205
|
-
const isTheme =
|
|
206
|
+
const isTheme = kindOfMeta(meta) === "theme";
|
|
206
207
|
const dependencies = meta.dependencies;
|
|
207
208
|
const missingDependency = !isTheme &&
|
|
208
209
|
typeof dependencies === "object" &&
|
package/dist/vault-metadata.js
CHANGED
|
@@ -12,6 +12,14 @@ export const kindOfMeta = (meta) => {
|
|
|
12
12
|
const tags = Array.isArray(meta.tags) ? meta.tags : [];
|
|
13
13
|
return KINDS.find((kind) => tags.includes(kind));
|
|
14
14
|
};
|
|
15
|
+
// kindOfMeta as JavaScript source, for expressions evaluated inside the
|
|
16
|
+
// client where kit code cannot be imported. It also accepts a missing meta.
|
|
17
|
+
export const KIND_OF_META_SOURCE = `((meta) => {
|
|
18
|
+
const kinds = ${JSON.stringify(KINDS)};
|
|
19
|
+
if (typeof meta?.kind === "string" && kinds.includes(meta.kind)) return meta.kind;
|
|
20
|
+
const tags = Array.isArray(meta?.tags) ? meta.tags : [];
|
|
21
|
+
return kinds.find((kind) => tags.includes(kind));
|
|
22
|
+
})`;
|
|
15
23
|
// metadata.json authors are plain names; author objects (with a github)
|
|
16
24
|
// pass through, so an artifact may declare either.
|
|
17
25
|
const normalizeAuthors = (authors) => authors.flatMap((a) => {
|
package/dist/version.js
CHANGED
|
@@ -4,5 +4,5 @@
|
|
|
4
4
|
*/
|
|
5
5
|
// Kept in lockstep with package.json by release-please. Scaffolds must depend
|
|
6
6
|
// on the kit version that generated them, especially across pre-1.0 minors.
|
|
7
|
-
export const KIT_VERSION = "0.4.
|
|
7
|
+
export const KIT_VERSION = "0.4.4"; // x-release-please-version
|
|
8
8
|
export const KIT_DEPENDENCY_RANGE = `^${KIT_VERSION}`;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@spicetify/kit",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.4",
|
|
4
4
|
"description": "Developer kit for spicetify v3 modules: scaffold, build, hot-push, and pack",
|
|
5
5
|
"license": "GPL-3.0-or-later",
|
|
6
6
|
"homepage": "https://github.com/spicetify/modules#readme",
|
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
"access": "public"
|
|
41
41
|
},
|
|
42
42
|
"spicetify": {
|
|
43
|
-
"stdlibVersion": "1.13.
|
|
43
|
+
"stdlibVersion": "1.13.2"
|
|
44
44
|
},
|
|
45
45
|
"scripts": {
|
|
46
46
|
"build": "tsc -p tsconfig.build.json",
|
|
@@ -1,13 +1,31 @@
|
|
|
1
1
|
{
|
|
2
|
+
"context_menu": {
|
|
3
|
+
"menu_item": "fmmygDM45MTS7I35OyLh"
|
|
4
|
+
},
|
|
2
5
|
"main": {
|
|
6
|
+
"navbar": {
|
|
7
|
+
"link": {
|
|
8
|
+
"container": "xlH6FJnBzFT4l3TgUBre",
|
|
9
|
+
"container__active": "ajKYUZPcNmFVzcAzzlwe",
|
|
10
|
+
"wrapper": "xlH6FJnBzFT4l3TgUBre"
|
|
11
|
+
}
|
|
12
|
+
},
|
|
13
|
+
"playbar": {
|
|
14
|
+
"buttons": {
|
|
15
|
+
"button": {
|
|
16
|
+
"wrapper": "e-10860-legacy-button e-10860-button-tertiary--icon-only-small",
|
|
17
|
+
"wrapper__active": "e-10860-legacy-button e-10860-legacy-button-primary",
|
|
18
|
+
"wrapper__indicator": "e-10860-button-primary__inner"
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
},
|
|
3
22
|
"topbar": {
|
|
4
|
-
"wrapper": "bQetA_KiP9n0DQVcGa7m",
|
|
5
23
|
"left": {
|
|
6
24
|
"button": {
|
|
7
|
-
"wrapper": "e-10860-legacy-button e-10860-button-tertiary--icon-only-medium",
|
|
8
25
|
"icon": {
|
|
9
26
|
"wrapper": "e-10860-icon e-10860-icon--auto-mirror"
|
|
10
|
-
}
|
|
27
|
+
},
|
|
28
|
+
"wrapper": "e-10860-legacy-button e-10860-button-tertiary--icon-only-medium"
|
|
11
29
|
},
|
|
12
30
|
"button_t": {
|
|
13
31
|
"wrapper": "e-10860-legacy-button e-10860-button-tertiary--icon-only-medium"
|
|
@@ -23,87 +41,64 @@
|
|
|
23
41
|
"upgrade_button": {
|
|
24
42
|
"wrapper": "e-10860-legacy-button e-10860-legacy-button-primary"
|
|
25
43
|
}
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
"widget": {},
|
|
30
|
-
"controls": {},
|
|
31
|
-
"buttons": {
|
|
32
|
-
"button": {
|
|
33
|
-
"wrapper": "e-10860-legacy-button e-10860-button-tertiary--icon-only-small",
|
|
34
|
-
"wrapper__active": "e-10860-legacy-button e-10860-legacy-button-primary",
|
|
35
|
-
"wrapper__indicator": "e-10860-button-primary__inner"
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
},
|
|
39
|
-
"navbar": {
|
|
40
|
-
"link": {
|
|
41
|
-
"wrapper": "xlH6FJnBzFT4l3TgUBre",
|
|
42
|
-
"container": "xlH6FJnBzFT4l3TgUBre",
|
|
43
|
-
"container__active": "ajKYUZPcNmFVzcAzzlwe"
|
|
44
|
-
}
|
|
45
|
-
},
|
|
46
|
-
"global_navbar": {},
|
|
47
|
-
"sidebar": {},
|
|
48
|
-
"panel": {}
|
|
49
|
-
},
|
|
50
|
-
"tracklist": {
|
|
51
|
-
"column_header": "e-10860-legacy-list-row__header"
|
|
52
|
-
},
|
|
53
|
-
"search_chips": {
|
|
54
|
-
"wrapper_wrapper": "e-10860-chip-group",
|
|
55
|
-
"wrapper": "e-10860-chip-group__component",
|
|
56
|
-
"container": "e-10860-chip-group__component",
|
|
57
|
-
"chip": "e-10860-legacy-chip"
|
|
44
|
+
},
|
|
45
|
+
"wrapper": "bQetA_KiP9n0DQVcGa7m"
|
|
46
|
+
}
|
|
58
47
|
},
|
|
59
48
|
"modal": {
|
|
49
|
+
"track_credits": {
|
|
50
|
+
"container": "gtXQasDkhtg93wGk6yNG",
|
|
51
|
+
"content": {
|
|
52
|
+
"container": "wYQNBdH2rNMGe_B4oumf"
|
|
53
|
+
},
|
|
54
|
+
"header": {
|
|
55
|
+
"close": "CaHvWv36mAu4x5QTKlb1",
|
|
56
|
+
"container": "nQ_c7yoZ2XOD6fWtmv18"
|
|
57
|
+
}
|
|
58
|
+
},
|
|
60
59
|
"widget_generator": {
|
|
61
60
|
"container": "_XeAt3IgtZQepOYqU2g4",
|
|
62
|
-
"header": {
|
|
63
|
-
"container": "mnU1hqlaF5JIcrTxLnZF",
|
|
64
|
-
"close": "AslPcD0TRZBBGZofe58g"
|
|
65
|
-
},
|
|
66
61
|
"content": {
|
|
67
62
|
"container": "Xu_Z4l6TIOF1f5kDB9dt"
|
|
68
|
-
}
|
|
69
|
-
},
|
|
70
|
-
"track_credits": {
|
|
71
|
-
"container": "gtXQasDkhtg93wGk6yNG",
|
|
72
|
-
"header": {
|
|
73
|
-
"container": "nQ_c7yoZ2XOD6fWtmv18",
|
|
74
|
-
"close": "CaHvWv36mAu4x5QTKlb1"
|
|
75
63
|
},
|
|
76
|
-
"
|
|
77
|
-
"
|
|
64
|
+
"header": {
|
|
65
|
+
"close": "AslPcD0TRZBBGZofe58g",
|
|
66
|
+
"container": "mnU1hqlaF5JIcrTxLnZF"
|
|
78
67
|
}
|
|
79
68
|
}
|
|
80
69
|
},
|
|
70
|
+
"scrollable_text": {
|
|
71
|
+
"container": "e-10860-legacy-box e-10860-legacy-box--naked",
|
|
72
|
+
"wrapper": "e-10860-legacy-box"
|
|
73
|
+
},
|
|
74
|
+
"search_box": {
|
|
75
|
+
"container": "mlx0CgXAnGz0dADVsCdS",
|
|
76
|
+
"expand_button": "e-10860-legacy-button e-10860-button-tertiary--icon-only-medium aslJk0YypwKvyKXaBJcG"
|
|
77
|
+
},
|
|
78
|
+
"search_chips": {
|
|
79
|
+
"chip": "e-10860-legacy-chip",
|
|
80
|
+
"container": "e-10860-chip-group__component",
|
|
81
|
+
"wrapper": "e-10860-chip-group__component",
|
|
82
|
+
"wrapper_wrapper": "e-10860-chip-group"
|
|
83
|
+
},
|
|
81
84
|
"settings": {
|
|
85
|
+
"button": {
|
|
86
|
+
"wrapper": "e-10860-legacy-button e-10860-legacy-button--small e-10860-legacy-button-secondary"
|
|
87
|
+
},
|
|
82
88
|
"header": {
|
|
83
89
|
"container": "Hwax_pfCzrntLhnYXiqL"
|
|
84
90
|
},
|
|
85
91
|
"section": {
|
|
86
92
|
"container": "HdwEUcX8xijMdU7doYPt"
|
|
87
93
|
},
|
|
88
|
-
"button": {
|
|
89
|
-
"wrapper": "e-10860-legacy-button e-10860-legacy-button--small e-10860-legacy-button-secondary"
|
|
90
|
-
},
|
|
91
94
|
"text_input": "e-10860-form-input e-10860-form-control encore-text-body-medium"
|
|
92
95
|
},
|
|
93
|
-
"scrollable_text": {
|
|
94
|
-
"container": "e-10860-legacy-box e-10860-legacy-box--naked",
|
|
95
|
-
"wrapper": "e-10860-legacy-box"
|
|
96
|
-
},
|
|
97
96
|
"sort_box": {
|
|
98
97
|
"list": {
|
|
99
98
|
"button": "MYWr4UY4pKAAscUXvqrB"
|
|
100
99
|
}
|
|
101
100
|
},
|
|
102
|
-
"
|
|
103
|
-
"
|
|
104
|
-
"expand_button": "e-10860-legacy-button e-10860-button-tertiary--icon-only-medium aslJk0YypwKvyKXaBJcG"
|
|
105
|
-
},
|
|
106
|
-
"context_menu": {
|
|
107
|
-
"menu_item": "fmmygDM45MTS7I35OyLh"
|
|
101
|
+
"tracklist": {
|
|
102
|
+
"column_header": "e-10860-legacy-list-row__header"
|
|
108
103
|
}
|
|
109
104
|
}
|
|
@@ -14,9 +14,10 @@ type Predicate<A> = (value: A) => boolean;
|
|
|
14
14
|
|
|
15
15
|
// Some client exports are functions whose own toString is not callable;
|
|
16
16
|
// they can never match a needle, so they stringify to "".
|
|
17
|
-
const
|
|
17
|
+
const stringify = (x: any): string => {
|
|
18
|
+
if (x == null) return "";
|
|
18
19
|
try {
|
|
19
|
-
return x
|
|
20
|
+
return String(x);
|
|
20
21
|
} catch {
|
|
21
22
|
try {
|
|
22
23
|
return Function.prototype.toString.call(x);
|
|
@@ -26,18 +27,32 @@ const safeString = (x: any): string => {
|
|
|
26
27
|
}
|
|
27
28
|
};
|
|
28
29
|
|
|
30
|
+
const sources = new WeakMap<object, string>();
|
|
31
|
+
|
|
32
|
+
// sourceOf is the source text needles match against, computed once per
|
|
33
|
+
// function or object for the whole session.
|
|
34
|
+
export const sourceOf = (x: unknown): string => {
|
|
35
|
+
if ((typeof x !== "object" && typeof x !== "function") || x === null) return stringify(x);
|
|
36
|
+
let source = sources.get(x);
|
|
37
|
+
if (source === undefined) {
|
|
38
|
+
source = stringify(x);
|
|
39
|
+
sources.set(x, source);
|
|
40
|
+
}
|
|
41
|
+
return source;
|
|
42
|
+
};
|
|
43
|
+
|
|
29
44
|
export function findBy(...tests: Array<string | RegExp | Predicate<any>>) {
|
|
30
45
|
const testFns = tests.map((test): Predicate<any> => {
|
|
31
46
|
switch (typeof test) {
|
|
32
47
|
case "string":
|
|
33
|
-
return (x) =>
|
|
48
|
+
return (x) => sourceOf(x).includes(test);
|
|
34
49
|
case "function":
|
|
35
50
|
return (x) => test(x);
|
|
36
51
|
default: // assume regex
|
|
37
|
-
return (x) => test.test(
|
|
52
|
+
return (x) => test.test(sourceOf(x));
|
|
38
53
|
}
|
|
39
54
|
});
|
|
40
|
-
const testFn = (x: any) => testFns.
|
|
55
|
+
const testFn = (x: any) => testFns.every((t) => t(x));
|
|
41
56
|
return <A>(xs: A[]) => xs.find(testFn)!;
|
|
42
57
|
}
|
|
43
58
|
|
|
@@ -10,9 +10,19 @@ await CHUNKS.xpui.promise;
|
|
|
10
10
|
const componentNames = Object.keys(
|
|
11
11
|
exported.find((e) => e.BrowserDefaultFocusStyleProvider && Object.hasOwn(e, "BrowserDefaultFocusStyleProvider")),
|
|
12
12
|
);
|
|
13
|
-
const componentRegexes = componentNames.map(
|
|
13
|
+
const componentRegexes = componentNames.map(
|
|
14
|
+
(n) => new RegExp(String.raw`"data-encore-id":(?:[a-zA-Z_$][\w$]*\.){2}${n}\b`),
|
|
15
|
+
);
|
|
14
16
|
const componentPairs = [exportedFunctions.map((f) => [f, f]), exportedForwardRefs.map((f) => [(f as any).render, f])]
|
|
15
17
|
.flat()
|
|
16
|
-
.map(([s, f]) =>
|
|
18
|
+
.map(([s, f]) => {
|
|
19
|
+
const code = src(s);
|
|
20
|
+
return [
|
|
21
|
+
code.includes('"data-encore-id"')
|
|
22
|
+
? componentNames.find((_, i) => componentRegexes[i].test(code))
|
|
23
|
+
: undefined,
|
|
24
|
+
f,
|
|
25
|
+
];
|
|
26
|
+
});
|
|
17
27
|
|
|
18
28
|
export const UI: any = Object.fromEntries(componentPairs);
|
|
@@ -4,8 +4,10 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import { warn } from "../logger.ts";
|
|
7
|
+
import { sourceOf } from "../util.ts";
|
|
7
8
|
import { postWebpackRequireHooks, WebpackModule, WebpackRequire, webpackRequire } from "../wpunpk.mix.ts";
|
|
8
9
|
import { createCaptureReadiness } from "./capture-readiness.ts";
|
|
10
|
+
import { watchRegistryQuiet } from "./registry-quiet.ts";
|
|
9
11
|
|
|
10
12
|
export let modules: Array<[PropertyKey, WebpackModule]>;
|
|
11
13
|
export let exports: Array<Record<string, any>>;
|
|
@@ -18,19 +20,7 @@ export let exportedContexts: Array<React.Context<any>>;
|
|
|
18
20
|
export let exportedForwardRefs: Array<any>;
|
|
19
21
|
export let exportedMemos: React.NamedExoticComponent[];
|
|
20
22
|
|
|
21
|
-
|
|
22
|
-
// can never match a needle, so they stringify to "".
|
|
23
|
-
export const src = (f: unknown): string => {
|
|
24
|
-
try {
|
|
25
|
-
return String(f);
|
|
26
|
-
} catch {
|
|
27
|
-
try {
|
|
28
|
-
return Function.prototype.toString.call(f);
|
|
29
|
-
} catch {
|
|
30
|
-
return "";
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
};
|
|
23
|
+
export const src = sourceOf;
|
|
34
24
|
|
|
35
25
|
export const analyzeWebpackRequire = (webpackRequire: WebpackRequire) => {
|
|
36
26
|
const modules = Object.entries(webpackRequire.m) as Array<[keyof any, WebpackModule]>;
|
|
@@ -84,18 +74,31 @@ Object.assign(CHUNKS, {
|
|
|
84
74
|
// executed by capture time. Lazy route chunks go through the runtime's own
|
|
85
75
|
// script loader (wpr.l) from then on.
|
|
86
76
|
postWebpackRequireHooks.push((wpr: any) => {
|
|
77
|
+
let loading = 0;
|
|
87
78
|
if (typeof wpr?.l === "function") {
|
|
88
79
|
const load = wpr.l.bind(wpr);
|
|
89
|
-
wpr.l = (url: string, done: (event: unknown) => unknown, key?: string, chunkId?: unknown) =>
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
(
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
80
|
+
wpr.l = (url: string, done: (event: unknown) => unknown, key?: string, chunkId?: unknown) => {
|
|
81
|
+
loading++;
|
|
82
|
+
try {
|
|
83
|
+
return load(
|
|
84
|
+
url,
|
|
85
|
+
(event: unknown) => {
|
|
86
|
+
loading--;
|
|
87
|
+
try {
|
|
88
|
+
resolveChunk(new URL(url, location.href).pathname);
|
|
89
|
+
} catch (error) {
|
|
90
|
+
console.error(`[stdlib] could not resolve chunk ${url}:`, error);
|
|
91
|
+
}
|
|
92
|
+
return done(event);
|
|
93
|
+
},
|
|
94
|
+
key,
|
|
95
|
+
chunkId,
|
|
96
|
+
);
|
|
97
|
+
} catch (error) {
|
|
98
|
+
loading--;
|
|
99
|
+
throw error;
|
|
100
|
+
}
|
|
101
|
+
};
|
|
99
102
|
}
|
|
100
103
|
// Chunk scripts that finished loading before capture. The xpui entry
|
|
101
104
|
// chunks are deliberately excluded: they are gated below on registry
|
|
@@ -108,23 +111,18 @@ postWebpackRequireHooks.push((wpr: any) => {
|
|
|
108
111
|
}
|
|
109
112
|
}
|
|
110
113
|
// Capture can fire while boot is still registering modules (xpui-modules
|
|
111
|
-
// and
|
|
112
|
-
// there). The analysis below
|
|
113
|
-
//
|
|
114
|
-
//
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
const count = Object.keys(wpr?.m ?? {}).length;
|
|
120
|
-
stable = count === last ? stable + 1 : 0;
|
|
121
|
-
last = count;
|
|
122
|
-
if (stable >= 3 || ++ticks > 100) {
|
|
123
|
-
clearInterval(settle);
|
|
114
|
+
// and the lazy chunks the client requests right after it land later, and
|
|
115
|
+
// analysis needles such as GenericModal live there). The analysis below
|
|
116
|
+
// snapshots wpr.m once, so hold the xpui promises until the registry has
|
|
117
|
+
// been quiet, with no chunk in flight, for 300ms.
|
|
118
|
+
watchRegistryQuiet(
|
|
119
|
+
() => Object.keys(wpr?.m ?? {}).length,
|
|
120
|
+
() => loading,
|
|
121
|
+
() => {
|
|
124
122
|
resolveChunk("/vendor~xpui.js");
|
|
125
123
|
resolveChunk("/xpui.js");
|
|
126
|
-
}
|
|
127
|
-
|
|
124
|
+
},
|
|
125
|
+
);
|
|
128
126
|
});
|
|
129
127
|
|
|
130
128
|
// Capture subscribers run synchronously right after the analysis lands, so
|
|
@@ -135,7 +133,7 @@ postWebpackRequireHooks.push((wpr: any) => {
|
|
|
135
133
|
let captured = false;
|
|
136
134
|
const captureSubscribers: Array<() => void> = [];
|
|
137
135
|
const webpackCaptureReadiness = createCaptureReadiness({
|
|
138
|
-
// Registry quiescence caps at about
|
|
136
|
+
// Registry quiescence caps at about 10s. Leave room for its last tick,
|
|
139
137
|
// then release degraded so a changed runtime cannot deadlock all modules.
|
|
140
138
|
timeoutMs: 12000,
|
|
141
139
|
onTimeout: () => warn("[stdlib] capture health: webpack capture timed out; module surfaces will be degraded"),
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (C) 2026 Afonso Jorge Ramos
|
|
3
|
+
* SPDX-License-Identifier: GPL-3.0-or-later
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const QUIET_MS = 300;
|
|
7
|
+
const TICK_MS = 25;
|
|
8
|
+
const CAP_MS = 10_000;
|
|
9
|
+
|
|
10
|
+
// watchRegistryQuiet calls onQuiet once the module registry has not changed,
|
|
11
|
+
// and no chunk load has been in flight, for 300ms. A 10s cap bounds the wait
|
|
12
|
+
// so a chunk that never finishes, or a registry that never settles, still
|
|
13
|
+
// resolves.
|
|
14
|
+
export function watchRegistryQuiet(count: () => number, pending: () => number, onQuiet: () => void): void {
|
|
15
|
+
const started = Date.now();
|
|
16
|
+
let last = -1;
|
|
17
|
+
let quietSince = started;
|
|
18
|
+
const timer = setInterval(() => {
|
|
19
|
+
const current = count();
|
|
20
|
+
const at = Date.now();
|
|
21
|
+
if (current !== last || pending() > 0) {
|
|
22
|
+
last = current;
|
|
23
|
+
quietSince = at;
|
|
24
|
+
}
|
|
25
|
+
if (at - quietSince >= QUIET_MS || at - started >= CAP_MS) {
|
|
26
|
+
clearInterval(timer);
|
|
27
|
+
onQuiet();
|
|
28
|
+
}
|
|
29
|
+
}, TICK_MS);
|
|
30
|
+
}
|