@fusion.dev/helix-diagnostics 0.138.0 → 0.140.0
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.md +210 -34
- package/dist/analyze.d.ts +14 -3
- package/dist/analyze.js +76 -86
- package/dist/ci.js +14 -0
- package/dist/collect.d.ts +2 -1
- package/dist/collect.js +3 -15
- package/dist/config.d.ts +24 -0
- package/dist/config.js +42 -12
- package/dist/console.d.ts +11 -1
- package/dist/console.js +46 -5
- package/dist/diagnose.d.ts +75 -0
- package/dist/diagnose.js +93 -0
- package/dist/identity.d.ts +6 -0
- package/dist/identity.js +1 -0
- package/dist/index.d.ts +5 -77
- package/dist/index.js +4 -74
- package/dist/init.d.ts +87 -0
- package/dist/init.js +337 -0
- package/dist/manifest.d.ts +41 -3
- package/dist/manifest.js +79 -40
- package/dist/overlay.d.ts +12 -0
- package/dist/overlay.js +300 -0
- package/dist/page.d.ts +25 -0
- package/dist/page.js +21 -0
- package/dist/performance.d.ts +34 -0
- package/dist/performance.js +121 -0
- package/dist/report.d.ts +86 -0
- package/dist/runtime.d.ts +31 -0
- package/dist/runtime.js +39 -0
- package/dist/schedule.d.ts +57 -0
- package/dist/schedule.js +84 -0
- package/dist/usage.d.ts +34 -4
- package/dist/usage.js +38 -16
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +2 -2
package/dist/report.d.ts
CHANGED
|
@@ -109,6 +109,63 @@ export type Identified = Omit<Finding, 'decidedBy' | 'severity'>;
|
|
|
109
109
|
export declare const CATEGORIES: readonly ['assist', 'configuration', 'coverage', 'findings', 'performance', 'runtime', 'usage'];
|
|
110
110
|
/** A kind of telemetry. */
|
|
111
111
|
export type Category = (typeof CATEGORIES)[number];
|
|
112
|
+
/**
|
|
113
|
+
* What the components cost on a page, by tag, where a project asked for it.
|
|
114
|
+
*/
|
|
115
|
+
export interface PerformanceReport {
|
|
116
|
+
/**
|
|
117
|
+
* What this browser could measure. Layout shift and long tasks are
|
|
118
|
+
* Chromium's, so elsewhere their numbers are absent, never zero.
|
|
119
|
+
*/
|
|
120
|
+
measured: {
|
|
121
|
+
layoutShift: boolean;
|
|
122
|
+
longTasks: boolean;
|
|
123
|
+
};
|
|
124
|
+
/** Each tag rendered on the page. */
|
|
125
|
+
tags: Record<string, {
|
|
126
|
+
/** How many long tasks, of 50 ms or more, one of its elements' first updates ran in. */
|
|
127
|
+
longTasks: number;
|
|
128
|
+
/**
|
|
129
|
+
* Its elements' first updates, in milliseconds: how many, the median,
|
|
130
|
+
* the 95th percentile, and the slowest. Each is the element's own
|
|
131
|
+
* update, from `willUpdate` to `updated`, so time spent waiting behind
|
|
132
|
+
* other elements is not counted.
|
|
133
|
+
*/
|
|
134
|
+
render: {
|
|
135
|
+
count: number;
|
|
136
|
+
max: number;
|
|
137
|
+
p50: number;
|
|
138
|
+
p95: number;
|
|
139
|
+
};
|
|
140
|
+
/** The layout shift its elements moved, summed as the browser scores it. */
|
|
141
|
+
shift: number;
|
|
142
|
+
}>;
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* A runtime event as a run holds it: what `@fusion.dev/helix-core` reported
|
|
146
|
+
* as the library ran, such as a selectable table with no `row-key`, one entry
|
|
147
|
+
* per distinct event on the page.
|
|
148
|
+
*/
|
|
149
|
+
export interface RuntimeEntry {
|
|
150
|
+
/** How many times it was reported on this page. */
|
|
151
|
+
count: number;
|
|
152
|
+
/**
|
|
153
|
+
* Its identity across runs: the kind, the tag, and the sentence with its
|
|
154
|
+
* numbers left out, so a table drawing 3,000 rows and one drawing 3,200 are
|
|
155
|
+
* one event.
|
|
156
|
+
*/
|
|
157
|
+
fingerprint: string;
|
|
158
|
+
/** What went wrong, as core names it, stable across versions. */
|
|
159
|
+
kind: string;
|
|
160
|
+
/** `error` for a failure, `warn` for a mistake in how the library is used. */
|
|
161
|
+
level: 'error' | 'warn';
|
|
162
|
+
/** The sentence core prints, which says what to do. */
|
|
163
|
+
message: string;
|
|
164
|
+
/** Whether core printed it as it happened; where it did not, the console sink does. */
|
|
165
|
+
printed: boolean;
|
|
166
|
+
/** The tag of the element it happened on, where there is one. */
|
|
167
|
+
tag?: string;
|
|
168
|
+
}
|
|
112
169
|
/** Where a run was declared to happen. Undeclared is never sent. */
|
|
113
170
|
export type Environment = 'ci' | 'development' | 'production';
|
|
114
171
|
/** A sheet the collector could not read, and the reason. */
|
|
@@ -188,8 +245,20 @@ export interface Configuration {
|
|
|
188
245
|
console: {
|
|
189
246
|
coverage: 'info' | 'off';
|
|
190
247
|
findings: Severity | 'off';
|
|
248
|
+
/** What the components cost, by tag, which is measured where this is on. */
|
|
249
|
+
performance: 'info' | 'off';
|
|
250
|
+
/** The runtime events core left to diagnostics to print. */
|
|
251
|
+
runtime: 'warn' | 'off';
|
|
191
252
|
usage: 'debug' | 'off';
|
|
192
253
|
};
|
|
254
|
+
/** What `@fusion.dev/helix-core` does itself. */
|
|
255
|
+
core: {
|
|
256
|
+
/**
|
|
257
|
+
* Whether core prints each runtime warning as it happens. Off, diagnostics
|
|
258
|
+
* prints them in its own group, at the `console.runtime` level.
|
|
259
|
+
*/
|
|
260
|
+
warnings: boolean;
|
|
261
|
+
};
|
|
193
262
|
/** How much detail goes with what is sent. */
|
|
194
263
|
detail: {
|
|
195
264
|
evidence: 'full' | 'none' | 'selector';
|
|
@@ -204,6 +273,13 @@ export interface Configuration {
|
|
|
204
273
|
sample: number;
|
|
205
274
|
};
|
|
206
275
|
};
|
|
276
|
+
/** Where `initDiagnostics()` hands each run, beside the console. */
|
|
277
|
+
sinks: {
|
|
278
|
+
/** The overlay on the page. */
|
|
279
|
+
overlay: boolean;
|
|
280
|
+
/** The relay, `helix diagnostics`: off, on at its own address, or at this one. */
|
|
281
|
+
relay: boolean | string;
|
|
282
|
+
};
|
|
207
283
|
/** Each override in effect, from the file or the call. */
|
|
208
284
|
overrides: Array<{
|
|
209
285
|
kind: Kind;
|
|
@@ -235,6 +311,16 @@ export interface Report {
|
|
|
235
311
|
configuration: Configuration;
|
|
236
312
|
coverage: Coverage;
|
|
237
313
|
findings: Finding[];
|
|
314
|
+
/**
|
|
315
|
+
* What the components cost, by tag. Present only where the project asked,
|
|
316
|
+
* with `console.performance` or `send.performance`.
|
|
317
|
+
*/
|
|
318
|
+
performance?: PerformanceReport;
|
|
319
|
+
/**
|
|
320
|
+
* What the library reported going wrong as it ran on this page, from its
|
|
321
|
+
* first load: one entry per distinct event, counted.
|
|
322
|
+
*/
|
|
323
|
+
runtime: RuntimeEntry[];
|
|
238
324
|
/**
|
|
239
325
|
* The project's own name and labels, as its configuration gives them. The
|
|
240
326
|
* key is never written into a report.
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { RuntimeEntry } from './report.js';
|
|
2
|
+
/** The key core reports each event to. */
|
|
3
|
+
export declare const RUNTIME_SLOT: unique symbol;
|
|
4
|
+
/** The key core keeps the events it reported under, the first two hundred. */
|
|
5
|
+
export declare const RUNTIME_LOG: unique symbol;
|
|
6
|
+
/**
|
|
7
|
+
* The kinds a finding already reports, which the console and the overlay leave
|
|
8
|
+
* out: two copies of Helix on a page is the `duplicate-registration` finding.
|
|
9
|
+
*/
|
|
10
|
+
export declare const AS_FINDINGS: ReadonlySet<string>;
|
|
11
|
+
/** An event as core reports it, read without importing core. */
|
|
12
|
+
export interface Heard {
|
|
13
|
+
detail?: unknown;
|
|
14
|
+
kind: string;
|
|
15
|
+
level?: 'error' | 'warn';
|
|
16
|
+
message: string;
|
|
17
|
+
tag?: string | undefined;
|
|
18
|
+
}
|
|
19
|
+
/** The events core has kept so far. */
|
|
20
|
+
export declare const logged: () => Heard[];
|
|
21
|
+
/** One entry per distinct event, counted as each is heard. */
|
|
22
|
+
export interface Tally {
|
|
23
|
+
/** Counts one event; `printed` says whether core printed it. */
|
|
24
|
+
add(event: unknown, printed: boolean): void;
|
|
25
|
+
/** The entries so far. Copies: counting goes on after they are read. */
|
|
26
|
+
entries(): RuntimeEntry[];
|
|
27
|
+
}
|
|
28
|
+
/** Starts a count. */
|
|
29
|
+
export declare const tally: () => Tally;
|
|
30
|
+
/** The entries a reader is shown: those no finding reports already. */
|
|
31
|
+
export declare const shownRuntime: (entries: readonly RuntimeEntry[]) => RuntimeEntry[];
|
package/dist/runtime.js
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { runtimeFingerprint } from './identity.js';
|
|
2
|
+
export const RUNTIME_SLOT = Symbol.for('fusion.dev/helix/runtime');
|
|
3
|
+
export const RUNTIME_LOG = Symbol.for('fusion.dev/helix/runtime-log');
|
|
4
|
+
export const AS_FINDINGS = new Set(['duplicate-registration']);
|
|
5
|
+
const isHeard = (value) => typeof value === 'object' &&
|
|
6
|
+
value !== null &&
|
|
7
|
+
typeof value.kind === 'string' &&
|
|
8
|
+
typeof value.message === 'string';
|
|
9
|
+
export const logged = () => {
|
|
10
|
+
const log = globalThis[RUNTIME_LOG];
|
|
11
|
+
return Array.isArray(log) ? log.filter(isHeard) : [];
|
|
12
|
+
};
|
|
13
|
+
export const tally = () => {
|
|
14
|
+
const entries = new Map();
|
|
15
|
+
return {
|
|
16
|
+
add(event, printed) {
|
|
17
|
+
if (!isHeard(event)) {
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
const fingerprint = runtimeFingerprint(event.kind, event.tag, event.message);
|
|
21
|
+
const known = entries.get(fingerprint);
|
|
22
|
+
if (known) {
|
|
23
|
+
known.count += 1;
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
entries.set(fingerprint, {
|
|
27
|
+
count: 1,
|
|
28
|
+
fingerprint,
|
|
29
|
+
kind: event.kind,
|
|
30
|
+
level: event.level === 'error' ? 'error' : 'warn',
|
|
31
|
+
message: event.message,
|
|
32
|
+
printed,
|
|
33
|
+
...(event.tag === undefined ? {} : { tag: event.tag }),
|
|
34
|
+
});
|
|
35
|
+
},
|
|
36
|
+
entries: () => [...entries.values()].map((one) => ({ ...one })),
|
|
37
|
+
};
|
|
38
|
+
};
|
|
39
|
+
export const shownRuntime = (entries) => entries.filter((one) => !AS_FINDINGS.has(one.kind));
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* When `initDiagnostics()` runs: debounced, idle, and bounded by its own cost.
|
|
3
|
+
*
|
|
4
|
+
* - **After a change**, once changes stop for `quiet` milliseconds, or after
|
|
5
|
+
* `longest` milliseconds whatever keeps changing: a page that never stops,
|
|
6
|
+
* such as a carousel adding slides, would otherwise never be judged.
|
|
7
|
+
* - **In an idle moment**, at most `idle` milliseconds later.
|
|
8
|
+
* - **Never sooner than `gap` after the last run, nor sooner than `factor`
|
|
9
|
+
* times its duration**, so a slow page is judged less often.
|
|
10
|
+
* - **Never in a hidden tab**: a change there waits for the tab to be shown.
|
|
11
|
+
* - **Never when nothing changed** since the last run.
|
|
12
|
+
*
|
|
13
|
+
* The browser's hooks are passed in, so a spec drives it with small numbers
|
|
14
|
+
* and a pretend tab.
|
|
15
|
+
*/
|
|
16
|
+
/** How long each wait is, in milliseconds. */
|
|
17
|
+
export interface Timing {
|
|
18
|
+
/** How long changes must stop for before a run. */
|
|
19
|
+
quiet: number;
|
|
20
|
+
/** The longest an idle callback may wait. */
|
|
21
|
+
idle: number;
|
|
22
|
+
/** The least time between the end of one run and the start of the next. */
|
|
23
|
+
gap: number;
|
|
24
|
+
/** The least time between runs, as a multiple of the last run's duration. */
|
|
25
|
+
factor: number;
|
|
26
|
+
/** The longest a change waits for the page to go quiet. */
|
|
27
|
+
longest: number;
|
|
28
|
+
}
|
|
29
|
+
/** The shipped timing. */
|
|
30
|
+
export declare const TIMING: Timing;
|
|
31
|
+
/** What the schedule asks of the browser. */
|
|
32
|
+
export interface Browser {
|
|
33
|
+
/** Whether the tab is hidden now. */
|
|
34
|
+
hidden(): boolean;
|
|
35
|
+
/** Calls `then` once in an idle moment, at most `timeout` later; returns a cancel. */
|
|
36
|
+
idle(then: () => void, timeout: number): () => void;
|
|
37
|
+
/** The time now, in milliseconds. */
|
|
38
|
+
now(): number;
|
|
39
|
+
/** Calls `then` when the tab is next shown; returns a removal. */
|
|
40
|
+
shown(then: () => void): () => void;
|
|
41
|
+
}
|
|
42
|
+
/** The browser's own hooks, with a timer where a browser has no idle callback. */
|
|
43
|
+
export declare const browser: () => Browser;
|
|
44
|
+
/** A schedule: told of changes, it decides when `run` runs. */
|
|
45
|
+
export interface Schedule {
|
|
46
|
+
/** Something changed: a run follows, as the schedule allows. */
|
|
47
|
+
poke(): void;
|
|
48
|
+
/** Ends it: nothing more runs. */
|
|
49
|
+
stop(): void;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* A schedule for `run`, which returns how long it took in milliseconds.
|
|
53
|
+
*
|
|
54
|
+
* One run at a time, and a change during a run is a change after it: the run
|
|
55
|
+
* that follows sees it.
|
|
56
|
+
*/
|
|
57
|
+
export declare const schedule: (run: () => number, timing?: Timing, on?: Browser) => Schedule;
|
package/dist/schedule.js
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
export const TIMING = { factor: 20, gap: 2000, idle: 3000, longest: 10000, quiet: 1000 };
|
|
2
|
+
export const browser = () => ({
|
|
3
|
+
hidden: () => globalThis.document?.visibilityState === 'hidden',
|
|
4
|
+
idle: (then, timeout) => {
|
|
5
|
+
if (typeof globalThis.requestIdleCallback === 'function') {
|
|
6
|
+
const id = globalThis.requestIdleCallback(then, { timeout });
|
|
7
|
+
return () => globalThis.cancelIdleCallback(id);
|
|
8
|
+
}
|
|
9
|
+
const id = setTimeout(then, 1);
|
|
10
|
+
return () => clearTimeout(id);
|
|
11
|
+
},
|
|
12
|
+
now: () => globalThis.performance?.now() ?? Date.now(),
|
|
13
|
+
shown: (then) => {
|
|
14
|
+
const listener = () => {
|
|
15
|
+
if (globalThis.document?.visibilityState !== 'hidden') {
|
|
16
|
+
globalThis.document?.removeEventListener('visibilitychange', listener);
|
|
17
|
+
then();
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
globalThis.document?.addEventListener('visibilitychange', listener);
|
|
21
|
+
return () => globalThis.document?.removeEventListener('visibilitychange', listener);
|
|
22
|
+
},
|
|
23
|
+
});
|
|
24
|
+
export const schedule = (run, timing = TIMING, on = browser()) => {
|
|
25
|
+
let dirty = false;
|
|
26
|
+
let since = Number.POSITIVE_INFINITY;
|
|
27
|
+
let live = true;
|
|
28
|
+
let ended = Number.NEGATIVE_INFINITY;
|
|
29
|
+
let last = 0;
|
|
30
|
+
let cancel;
|
|
31
|
+
let quiet;
|
|
32
|
+
let wait;
|
|
33
|
+
const go = () => {
|
|
34
|
+
cancel = undefined;
|
|
35
|
+
if (!live || !dirty) {
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
if (on.hidden()) {
|
|
39
|
+
cancel = on.shown(settle);
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
const rest = Math.max(timing.gap, timing.factor * last) - (on.now() - ended);
|
|
43
|
+
if (rest > 0) {
|
|
44
|
+
wait = setTimeout(go, rest);
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
cancel = on.idle(() => {
|
|
48
|
+
cancel = undefined;
|
|
49
|
+
if (!live || !dirty) {
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
dirty = false;
|
|
53
|
+
since = Number.POSITIVE_INFINITY;
|
|
54
|
+
last = run();
|
|
55
|
+
ended = on.now();
|
|
56
|
+
if (dirty) {
|
|
57
|
+
settle();
|
|
58
|
+
}
|
|
59
|
+
}, timing.idle);
|
|
60
|
+
};
|
|
61
|
+
const settle = () => {
|
|
62
|
+
if (!live) {
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
clearTimeout(quiet);
|
|
66
|
+
clearTimeout(wait);
|
|
67
|
+
cancel?.();
|
|
68
|
+
cancel = undefined;
|
|
69
|
+
quiet = setTimeout(go, Math.min(timing.quiet, Math.max(0, since + timing.longest - on.now())));
|
|
70
|
+
};
|
|
71
|
+
return {
|
|
72
|
+
poke: () => {
|
|
73
|
+
dirty = true;
|
|
74
|
+
since = Math.min(since, on.now());
|
|
75
|
+
settle();
|
|
76
|
+
},
|
|
77
|
+
stop: () => {
|
|
78
|
+
live = false;
|
|
79
|
+
clearTimeout(quiet);
|
|
80
|
+
clearTimeout(wait);
|
|
81
|
+
cancel?.();
|
|
82
|
+
},
|
|
83
|
+
};
|
|
84
|
+
};
|
package/dist/usage.d.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import type { Answered, Located } from './collect.js';
|
|
2
2
|
import type { Published } from './manifest.js';
|
|
3
|
-
import type
|
|
3
|
+
import { type Page } from './page.js';
|
|
4
|
+
import type { PerformanceReport, RuntimeEntry, Usage } from './report.js';
|
|
5
|
+
import { type Heard } from './runtime.js';
|
|
4
6
|
/** The element counts, before the stylesheets add theirs. */
|
|
5
7
|
type Counted = Pick<Usage, 'axes' | 'composed' | 'slots' | 'tags'>;
|
|
6
8
|
/**
|
|
@@ -10,7 +12,7 @@ type Counted = Pick<Usage, 'axes' | 'composed' | 'slots' | 'tags'>;
|
|
|
10
12
|
*/
|
|
11
13
|
export declare const loadedVersions: () => string[];
|
|
12
14
|
/** The elements in the page now, counted once each. */
|
|
13
|
-
export declare const walk: (from: Document) => Counted;
|
|
15
|
+
export declare const walk: (from: Document, page?: Page) => Counted;
|
|
14
16
|
/**
|
|
15
17
|
* The counts for the tags a manifest lists, and the rest apart as `unlisted`.
|
|
16
18
|
*
|
|
@@ -22,7 +24,15 @@ export declare const listed: (counted: Counted, tags: Map<string, Published>) =>
|
|
|
22
24
|
export interface Watcher {
|
|
23
25
|
/** The counts so far. A copy: counting goes on after it is read. */
|
|
24
26
|
counts(): Counted;
|
|
25
|
-
/**
|
|
27
|
+
/** What the components cost so far, where the watcher was asked to measure. */
|
|
28
|
+
performance(): PerformanceReport | undefined;
|
|
29
|
+
/**
|
|
30
|
+
* The runtime events reported on this page from its first load, one entry
|
|
31
|
+
* per distinct event: the ones core kept from before the watcher started,
|
|
32
|
+
* and the ones heard since.
|
|
33
|
+
*/
|
|
34
|
+
runtime(): RuntimeEntry[];
|
|
35
|
+
/** Stops counting, and gives the slots back to whatever held them before. */
|
|
26
36
|
stop(): void;
|
|
27
37
|
}
|
|
28
38
|
/**
|
|
@@ -40,7 +50,27 @@ export interface Watcher {
|
|
|
40
50
|
* Two watchers on one page both count: a second one keeps the first's
|
|
41
51
|
* listener and calls it too.
|
|
42
52
|
*/
|
|
43
|
-
export declare const watch: (from?: Document
|
|
53
|
+
export declare const watch: (from?: Document, { onRender, onRuntime, performance, quiet, }?: {
|
|
54
|
+
/**
|
|
55
|
+
* Called as each Helix element renders for the first time, after it is
|
|
56
|
+
* counted: how `initDiagnostics()` hears that the page changed when an
|
|
57
|
+
* element upgrades without adding anything to the DOM.
|
|
58
|
+
*/
|
|
59
|
+
onRender?: ((element: Element) => void) | undefined;
|
|
60
|
+
/** Called with each runtime event core reports, after it is counted. */
|
|
61
|
+
onRuntime?: ((event: Heard) => void) | undefined;
|
|
62
|
+
/**
|
|
63
|
+
* Whether to measure what the components cost, from now on: each
|
|
64
|
+
* element's first update, and the layout shift and long tasks the browser
|
|
65
|
+
* reports.
|
|
66
|
+
*/
|
|
67
|
+
performance?: boolean | undefined;
|
|
68
|
+
/**
|
|
69
|
+
* Whether core should leave each runtime event for diagnostics to print,
|
|
70
|
+
* asked as each arrives: `core.warnings: false`.
|
|
71
|
+
*/
|
|
72
|
+
quiet?: (() => boolean) | undefined;
|
|
73
|
+
}) => Watcher;
|
|
44
74
|
/**
|
|
45
75
|
* The parts and hooks a page's stylesheets reach for, where the manifest or
|
|
46
76
|
* the element's own styles say they exist. A dead one is a finding and is not
|
package/dist/usage.js
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
import { parts, targets } from './analyze.js';
|
|
2
|
+
import { OVERLAY, pageOf } from './page.js';
|
|
3
|
+
import { measure } from './performance.js';
|
|
4
|
+
import { logged, RUNTIME_SLOT, tally as tallyRuntime } from './runtime.js';
|
|
2
5
|
const SLOT = Symbol.for('fusion.dev/helix/usage');
|
|
3
6
|
const AXES = ['emphasis', 'material', 'separation', 'tone'];
|
|
4
7
|
const bump = (into, key, by = 1) => {
|
|
@@ -19,7 +22,7 @@ export const loadedVersions = () => {
|
|
|
19
22
|
};
|
|
20
23
|
const tally = (counted, element) => {
|
|
21
24
|
const tag = element.localName;
|
|
22
|
-
if (!tag.startsWith('helix-')) {
|
|
25
|
+
if (!tag.startsWith('helix-') || tag === OVERLAY) {
|
|
23
26
|
return;
|
|
24
27
|
}
|
|
25
28
|
const root = element.getRootNode();
|
|
@@ -42,20 +45,11 @@ const tally = (counted, element) => {
|
|
|
42
45
|
}
|
|
43
46
|
}
|
|
44
47
|
};
|
|
45
|
-
const
|
|
46
|
-
if (depth > 32) {
|
|
47
|
-
return;
|
|
48
|
-
}
|
|
49
|
-
for (const element of from.querySelectorAll('*')) {
|
|
50
|
-
visit(element);
|
|
51
|
-
if (element.shadowRoot) {
|
|
52
|
-
everyElement(element.shadowRoot, visit, depth + 1);
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
};
|
|
56
|
-
export const walk = (from) => {
|
|
48
|
+
export const walk = (from, page = pageOf(from)) => {
|
|
57
49
|
const counted = empty();
|
|
58
|
-
|
|
50
|
+
for (const element of page.elements) {
|
|
51
|
+
tally(counted, element);
|
|
52
|
+
}
|
|
59
53
|
return counted;
|
|
60
54
|
};
|
|
61
55
|
export const listed = (counted, tags) => {
|
|
@@ -76,7 +70,7 @@ export const listed = (counted, tags) => {
|
|
|
76
70
|
unlisted,
|
|
77
71
|
};
|
|
78
72
|
};
|
|
79
|
-
export const watch = (from = document) => {
|
|
73
|
+
export const watch = (from = document, { onRender, onRuntime, performance, quiet, } = {}) => {
|
|
80
74
|
const counted = empty();
|
|
81
75
|
const seen = new WeakSet();
|
|
82
76
|
const see = (element) => {
|
|
@@ -91,20 +85,48 @@ export const watch = (from = document) => {
|
|
|
91
85
|
const listener = (element) => {
|
|
92
86
|
if (live) {
|
|
93
87
|
see(element);
|
|
88
|
+
onRender?.(element);
|
|
94
89
|
}
|
|
95
90
|
if (typeof before === 'function') {
|
|
96
91
|
before(element);
|
|
97
92
|
}
|
|
98
93
|
};
|
|
99
94
|
store[SLOT] = listener;
|
|
100
|
-
|
|
95
|
+
for (const element of pageOf(from).elements) {
|
|
96
|
+
see(element);
|
|
97
|
+
}
|
|
98
|
+
const heard = tallyRuntime();
|
|
99
|
+
for (const event of logged()) {
|
|
100
|
+
heard.add(event, true);
|
|
101
|
+
}
|
|
102
|
+
const beforeRuntime = store[RUNTIME_SLOT];
|
|
103
|
+
const hear = (event) => {
|
|
104
|
+
let silent = false;
|
|
105
|
+
if (live) {
|
|
106
|
+
silent = quiet?.() === true;
|
|
107
|
+
heard.add(event, !silent);
|
|
108
|
+
onRuntime?.(event);
|
|
109
|
+
}
|
|
110
|
+
if (typeof beforeRuntime === 'function' && beforeRuntime(event) === true) {
|
|
111
|
+
silent = true;
|
|
112
|
+
}
|
|
113
|
+
return silent;
|
|
114
|
+
};
|
|
115
|
+
store[RUNTIME_SLOT] = hear;
|
|
116
|
+
const measured = performance ? measure() : undefined;
|
|
101
117
|
return {
|
|
102
118
|
counts: () => structuredClone(counted),
|
|
119
|
+
performance: () => measured?.results(),
|
|
120
|
+
runtime: () => heard.entries(),
|
|
103
121
|
stop: () => {
|
|
104
122
|
live = false;
|
|
123
|
+
measured?.stop();
|
|
105
124
|
if (store[SLOT] === listener) {
|
|
106
125
|
store[SLOT] = before;
|
|
107
126
|
}
|
|
127
|
+
if (store[RUNTIME_SLOT] === hear) {
|
|
128
|
+
store[RUNTIME_SLOT] = beforeRuntime;
|
|
129
|
+
}
|
|
108
130
|
},
|
|
109
131
|
};
|
|
110
132
|
};
|
package/dist/version.d.ts
CHANGED
package/dist/version.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const VERSION = '0.
|
|
1
|
+
export const VERSION = '0.140.0';
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fusion.dev/helix-diagnostics",
|
|
3
3
|
"description": "Finds the Helix mistakes that are valid CSS: dead parts, misnamed hooks, two copies of the library.",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.140.0",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Ross Sheppard <ross.sheppard@fusion.dev>",
|
|
7
7
|
"repository": {
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
},
|
|
34
34
|
"bugs": "https://github.com/project-fusion/fusion/issues",
|
|
35
35
|
"devDependencies": {
|
|
36
|
-
"@fusion.dev/helix-testing": "0.
|
|
36
|
+
"@fusion.dev/helix-testing": "0.140.0",
|
|
37
37
|
"@types/bun": "^1.3.0"
|
|
38
38
|
},
|
|
39
39
|
"homepage": "https://fusion.dev",
|