@ogygia/devtools 0.0.0 → 0.11.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/LICENSE +21 -0
- package/README.md +24 -1
- package/dist/client.d.ts +3 -0
- package/dist/client.js +24 -0
- package/dist/devtools/BoundaryOverlay.svelte +396 -0
- package/dist/devtools/BoundaryOverlay.svelte.d.ts +19 -0
- package/dist/devtools/Devtools.svelte +663 -0
- package/dist/devtools/Devtools.svelte.d.ts +15 -0
- package/dist/devtools/HubTab.svelte +206 -0
- package/dist/devtools/HubTab.svelte.d.ts +11 -0
- package/dist/devtools/HydrationTab.svelte +237 -0
- package/dist/devtools/HydrationTab.svelte.d.ts +13 -0
- package/dist/devtools/IslandDetail.svelte +615 -0
- package/dist/devtools/IslandDetail.svelte.d.ts +15 -0
- package/dist/devtools/LedgerTab.svelte +287 -0
- package/dist/devtools/LedgerTab.svelte.d.ts +11 -0
- package/dist/devtools/LensTab.svelte +290 -0
- package/dist/devtools/LensTab.svelte.d.ts +19 -0
- package/dist/devtools/NavTab.svelte +229 -0
- package/dist/devtools/NavTab.svelte.d.ts +11 -0
- package/dist/devtools/PageTab.svelte +1122 -0
- package/dist/devtools/PageTab.svelte.d.ts +15 -0
- package/dist/devtools/ProfilerTab.svelte +755 -0
- package/dist/devtools/ProfilerTab.svelte.d.ts +15 -0
- package/dist/devtools/PropsTree.svelte +155 -0
- package/dist/devtools/PropsTree.svelte.d.ts +15 -0
- package/dist/devtools/SessionTab.svelte +796 -0
- package/dist/devtools/SessionTab.svelte.d.ts +11 -0
- package/dist/devtools/TimelineTab.svelte +267 -0
- package/dist/devtools/TimelineTab.svelte.d.ts +11 -0
- package/dist/devtools/WireTab.svelte +149 -0
- package/dist/devtools/WireTab.svelte.d.ts +11 -0
- package/dist/devtools/bytes.d.ts +70 -0
- package/dist/devtools/bytes.js +82 -0
- package/dist/devtools/cache-headers.d.ts +20 -0
- package/dist/devtools/cache-headers.js +81 -0
- package/dist/devtools/highlight.d.ts +7 -0
- package/dist/devtools/highlight.js +83 -0
- package/dist/devtools/hydration.d.ts +48 -0
- package/dist/devtools/hydration.js +93 -0
- package/dist/devtools/index.d.ts +3 -0
- package/dist/devtools/index.js +3 -0
- package/dist/devtools/ledger-dom.d.ts +7 -0
- package/dist/devtools/ledger-dom.js +151 -0
- package/dist/devtools/leftovers.d.ts +77 -0
- package/dist/devtools/leftovers.js +319 -0
- package/dist/devtools/names.d.ts +1 -0
- package/dist/devtools/names.js +34 -0
- package/dist/devtools/page.d.ts +96 -0
- package/dist/devtools/page.js +957 -0
- package/dist/devtools/profile-anchors.d.ts +22 -0
- package/dist/devtools/profile-anchors.js +135 -0
- package/dist/devtools/profile-store.d.ts +175 -0
- package/dist/devtools/profile-store.js +239 -0
- package/dist/devtools/regions.d.ts +77 -0
- package/dist/devtools/regions.js +132 -0
- package/dist/devtools/session.d.ts +31 -0
- package/dist/devtools/session.js +455 -0
- package/dist/devtools/since-load.d.ts +50 -0
- package/dist/devtools/since-load.js +89 -0
- package/dist/devtools/source-lines.d.ts +24 -0
- package/dist/devtools/source-lines.js +106 -0
- package/dist/devtools/styles.d.ts +52 -0
- package/dist/devtools/styles.js +248 -0
- package/dist/devtools/testing-engine.d.ts +83 -0
- package/dist/devtools/testing-engine.js +220 -0
- package/dist/devtools/ui-app.d.ts +5 -0
- package/dist/devtools/ui-app.js +69 -0
- package/dist/devtools/ui.d.ts +50 -0
- package/dist/devtools/ui.js +175 -0
- package/dist/profiler/alloc.d.ts +75 -0
- package/dist/profiler/alloc.js +76 -0
- package/dist/profiler/analyze.d.ts +437 -0
- package/dist/profiler/analyze.js +1941 -0
- package/dist/profiler/app-path.d.ts +7 -0
- package/dist/profiler/app-path.js +26 -0
- package/dist/profiler/async-io.d.ts +58 -0
- package/dist/profiler/async-io.js +187 -0
- package/dist/profiler/beacon-standalone.d.ts +4 -0
- package/dist/profiler/beacon-standalone.js +409 -0
- package/dist/profiler/browser-findings.d.ts +25 -0
- package/dist/profiler/browser-findings.js +260 -0
- package/dist/profiler/byte-strip.d.ts +58 -0
- package/dist/profiler/byte-strip.js +228 -0
- package/dist/profiler/client-files.d.ts +16 -0
- package/dist/profiler/client-files.js +60 -0
- package/dist/profiler/client-windows.d.ts +49 -0
- package/dist/profiler/client-windows.js +95 -0
- package/dist/profiler/client.d.ts +40 -0
- package/dist/profiler/client.js +82 -0
- package/dist/profiler/compare.d.ts +308 -0
- package/dist/profiler/compare.js +733 -0
- package/dist/profiler/compress.d.ts +10 -0
- package/dist/profiler/compress.js +36 -0
- package/dist/profiler/contention.d.ts +60 -0
- package/dist/profiler/contention.js +86 -0
- package/dist/profiler/crypto.d.ts +19 -0
- package/dist/profiler/crypto.js +101 -0
- package/dist/profiler/doc-diff.d.ts +47 -0
- package/dist/profiler/doc-diff.js +214 -0
- package/dist/profiler/dom-diff.d.ts +43 -0
- package/dist/profiler/dom-diff.js +121 -0
- package/dist/profiler/drill.d.ts +89 -0
- package/dist/profiler/drill.js +553 -0
- package/dist/profiler/embedded-maps.d.ts +37 -0
- package/dist/profiler/embedded-maps.js +135 -0
- package/dist/profiler/env.d.ts +6 -0
- package/dist/profiler/env.js +10 -0
- package/dist/profiler/forecast.d.ts +76 -0
- package/dist/profiler/forecast.js +225 -0
- package/dist/profiler/frames.d.ts +11 -0
- package/dist/profiler/frames.js +42 -0
- package/dist/profiler/gc.d.ts +144 -0
- package/dist/profiler/gc.js +271 -0
- package/dist/profiler/hole-slots.d.ts +31 -0
- package/dist/profiler/hole-slots.js +90 -0
- package/dist/profiler/index.d.ts +288 -0
- package/dist/profiler/index.js +4921 -0
- package/dist/profiler/insights.d.ts +96 -0
- package/dist/profiler/insights.js +239 -0
- package/dist/profiler/ledger.d.ts +121 -0
- package/dist/profiler/ledger.js +440 -0
- package/dist/profiler/lineage.d.ts +81 -0
- package/dist/profiler/lineage.js +359 -0
- package/dist/profiler/module-map.d.ts +12 -0
- package/dist/profiler/module-map.js +94 -0
- package/dist/profiler/net.d.ts +169 -0
- package/dist/profiler/net.js +586 -0
- package/dist/profiler/on-demand.d.ts +11 -0
- package/dist/profiler/on-demand.js +15 -0
- package/dist/profiler/own-requests.d.ts +9 -0
- package/dist/profiler/own-requests.js +17 -0
- package/dist/profiler/patterns.d.ts +476 -0
- package/dist/profiler/patterns.js +2203 -0
- package/dist/profiler/profiler-router.d.ts +245 -0
- package/dist/profiler/profiler-router.js +113 -0
- package/dist/profiler/refresh-stub.d.ts +2 -0
- package/dist/profiler/refresh-stub.js +37 -0
- package/dist/profiler/report.d.ts +1404 -0
- package/dist/profiler/report.js +2638 -0
- package/dist/profiler/river.d.ts +65 -0
- package/dist/profiler/river.js +298 -0
- package/dist/profiler/route-files.d.ts +22 -0
- package/dist/profiler/route-files.js +55 -0
- package/dist/profiler/route-imports.d.ts +14 -0
- package/dist/profiler/route-imports.js +83 -0
- package/dist/profiler/saving.d.ts +22 -0
- package/dist/profiler/saving.js +42 -0
- package/dist/profiler/score.d.ts +109 -0
- package/dist/profiler/score.js +261 -0
- package/dist/profiler/session-cookie.d.ts +28 -0
- package/dist/profiler/session-cookie.js +45 -0
- package/dist/profiler/sink.d.ts +81 -0
- package/dist/profiler/sink.js +124 -0
- package/dist/profiler/site-fixes.d.ts +25 -0
- package/dist/profiler/site-fixes.js +62 -0
- package/dist/profiler/source-scan.d.ts +210 -0
- package/dist/profiler/source-scan.js +1724 -0
- package/dist/profiler/span.d.ts +90 -0
- package/dist/profiler/span.js +109 -0
- package/dist/profiler/stacks.d.ts +43 -0
- package/dist/profiler/stacks.js +107 -0
- package/dist/profiler/standalone.d.ts +27 -0
- package/dist/profiler/standalone.js +98 -0
- package/dist/profiler/steps.d.ts +39 -0
- package/dist/profiler/steps.js +74 -0
- package/dist/profiler/storage/index.d.ts +87 -0
- package/dist/profiler/storage/index.js +76 -0
- package/dist/profiler/storage/postgres.d.ts +12 -0
- package/dist/profiler/storage/postgres.js +94 -0
- package/dist/profiler/storage/redis.d.ts +19 -0
- package/dist/profiler/storage/redis.js +148 -0
- package/dist/profiler/storage/sqlite.d.ts +2 -0
- package/dist/profiler/storage/sqlite.js +85 -0
- package/dist/profiler/storage/visits.d.ts +4 -0
- package/dist/profiler/storage/visits.js +6 -0
- package/dist/profiler/stream-tail.d.ts +26 -0
- package/dist/profiler/stream-tail.js +74 -0
- package/dist/profiler/timeline.d.ts +279 -0
- package/dist/profiler/timeline.js +690 -0
- package/dist/profiler/ui/AllocStrip.svelte +146 -0
- package/dist/profiler/ui/AllocStrip.svelte.d.ts +17 -0
- package/dist/profiler/ui/Awaits.svelte +363 -0
- package/dist/profiler/ui/Awaits.svelte.d.ts +16 -0
- package/dist/profiler/ui/ByteStrip.svelte +182 -0
- package/dist/profiler/ui/ByteStrip.svelte.d.ts +18 -0
- package/dist/profiler/ui/Compare.svelte +24 -0
- package/dist/profiler/ui/Compare.svelte.d.ts +16 -0
- package/dist/profiler/ui/CompareBody.svelte +429 -0
- package/dist/profiler/ui/CompareBody.svelte.d.ts +8 -0
- package/dist/profiler/ui/ComponentsTable.svelte +265 -0
- package/dist/profiler/ui/ComponentsTable.svelte.d.ts +20 -0
- package/dist/profiler/ui/Dashboard.svelte +492 -0
- package/dist/profiler/ui/Dashboard.svelte.d.ts +31 -0
- package/dist/profiler/ui/DataRiver.svelte +206 -0
- package/dist/profiler/ui/DataRiver.svelte.d.ts +13 -0
- package/dist/profiler/ui/DomTravel.svelte +207 -0
- package/dist/profiler/ui/DomTravel.svelte.d.ts +9 -0
- package/dist/profiler/ui/DownloadHtml.svelte +52 -0
- package/dist/profiler/ui/DownloadHtml.svelte.d.ts +10 -0
- package/dist/profiler/ui/Drill.svelte +276 -0
- package/dist/profiler/ui/Drill.svelte.d.ts +12 -0
- package/dist/profiler/ui/ExportButton.svelte +59 -0
- package/dist/profiler/ui/ExportButton.svelte.d.ts +8 -0
- package/dist/profiler/ui/Flame.svelte +222 -0
- package/dist/profiler/ui/Flame.svelte.d.ts +12 -0
- package/dist/profiler/ui/FrameDetails.svelte +308 -0
- package/dist/profiler/ui/FrameDetails.svelte.d.ts +16 -0
- package/dist/profiler/ui/FunctionsTable.svelte +156 -0
- package/dist/profiler/ui/FunctionsTable.svelte.d.ts +17 -0
- package/dist/profiler/ui/IslandHeatmap.svelte +151 -0
- package/dist/profiler/ui/IslandHeatmap.svelte.d.ts +16 -0
- package/dist/profiler/ui/IslandsTable.svelte +448 -0
- package/dist/profiler/ui/IslandsTable.svelte.d.ts +16 -0
- package/dist/profiler/ui/KeepLocal.svelte +46 -0
- package/dist/profiler/ui/KeepLocal.svelte.d.ts +8 -0
- package/dist/profiler/ui/Ledger.svelte +196 -0
- package/dist/profiler/ui/Ledger.svelte.d.ts +18 -0
- package/dist/profiler/ui/LocalCompare.svelte +48 -0
- package/dist/profiler/ui/LocalCompare.svelte.d.ts +8 -0
- package/dist/profiler/ui/LocalReports.svelte +68 -0
- package/dist/profiler/ui/LocalReports.svelte.d.ts +7 -0
- package/dist/profiler/ui/Login.svelte +13 -0
- package/dist/profiler/ui/Login.svelte.d.ts +12 -0
- package/dist/profiler/ui/LoginForm.svelte +42 -0
- package/dist/profiler/ui/LoginForm.svelte.d.ts +7 -0
- package/dist/profiler/ui/Message.svelte +10 -0
- package/dist/profiler/ui/Message.svelte.d.ts +12 -0
- package/dist/profiler/ui/OnThisPage.svelte +137 -0
- package/dist/profiler/ui/OnThisPage.svelte.d.ts +3 -0
- package/dist/profiler/ui/OneClock.svelte +297 -0
- package/dist/profiler/ui/OneClock.svelte.d.ts +27 -0
- package/dist/profiler/ui/Panel.svelte +10 -0
- package/dist/profiler/ui/Panel.svelte.d.ts +16 -0
- package/dist/profiler/ui/Paths.svelte +269 -0
- package/dist/profiler/ui/Paths.svelte.d.ts +15 -0
- package/dist/profiler/ui/Patterns.svelte +364 -0
- package/dist/profiler/ui/Patterns.svelte.d.ts +15 -0
- package/dist/profiler/ui/PermalinkGate.svelte +143 -0
- package/dist/profiler/ui/PermalinkGate.svelte.d.ts +10 -0
- package/dist/profiler/ui/RenderSteps.svelte +218 -0
- package/dist/profiler/ui/RenderSteps.svelte.d.ts +13 -0
- package/dist/profiler/ui/Report.svelte +28 -0
- package/dist/profiler/ui/Report.svelte.d.ts +20 -0
- package/dist/profiler/ui/ReportBody.svelte +2592 -0
- package/dist/profiler/ui/ReportBody.svelte.d.ts +21 -0
- package/dist/profiler/ui/Run.svelte +17 -0
- package/dist/profiler/ui/Run.svelte.d.ts +14 -0
- package/dist/profiler/ui/RunView.svelte +146 -0
- package/dist/profiler/ui/RunView.svelte.d.ts +10 -0
- package/dist/profiler/ui/ScoreCard.svelte +197 -0
- package/dist/profiler/ui/ScoreCard.svelte.d.ts +7 -0
- package/dist/profiler/ui/Scrub.svelte +293 -0
- package/dist/profiler/ui/Scrub.svelte.d.ts +18 -0
- package/dist/profiler/ui/SeedExplainer.svelte +193 -0
- package/dist/profiler/ui/SeedExplainer.svelte.d.ts +16 -0
- package/dist/profiler/ui/ShareLink.svelte +148 -0
- package/dist/profiler/ui/ShareLink.svelte.d.ts +8 -0
- package/dist/profiler/ui/Shell.svelte +50 -0
- package/dist/profiler/ui/Shell.svelte.d.ts +27 -0
- package/dist/profiler/ui/Sidebar.svelte +312 -0
- package/dist/profiler/ui/Sidebar.svelte.d.ts +7 -0
- package/dist/profiler/ui/Site.svelte +20 -0
- package/dist/profiler/ui/Site.svelte.d.ts +23 -0
- package/dist/profiler/ui/SiteView.svelte +385 -0
- package/dist/profiler/ui/SiteView.svelte.d.ts +27 -0
- package/dist/profiler/ui/ThemeSwitch.svelte +78 -0
- package/dist/profiler/ui/ThemeSwitch.svelte.d.ts +3 -0
- package/dist/profiler/ui/Timeline.svelte +672 -0
- package/dist/profiler/ui/Timeline.svelte.d.ts +22 -0
- package/dist/profiler/ui/Treemap.svelte +278 -0
- package/dist/profiler/ui/Treemap.svelte.d.ts +20 -0
- package/dist/profiler/ui/Upload.svelte +19 -0
- package/dist/profiler/ui/Upload.svelte.d.ts +10 -0
- package/dist/profiler/ui/UploadForm.svelte +59 -0
- package/dist/profiler/ui/UploadForm.svelte.d.ts +6 -0
- package/dist/profiler/ui/WaitingTable.svelte +55 -0
- package/dist/profiler/ui/WaitingTable.svelte.d.ts +14 -0
- package/dist/profiler/ui/Waterfall.svelte +371 -0
- package/dist/profiler/ui/Waterfall.svelte.d.ts +7 -0
- package/dist/profiler/ui/WhatIf.svelte +87 -0
- package/dist/profiler/ui/WhatIf.svelte.d.ts +18 -0
- package/dist/profiler/ui/fake-progress.svelte.d.ts +15 -0
- package/dist/profiler/ui/fake-progress.svelte.js +38 -0
- package/dist/profiler/ui/format.d.ts +20 -0
- package/dist/profiler/ui/format.js +71 -0
- package/dist/profiler/ui/permalink.d.ts +9 -0
- package/dist/profiler/ui/permalink.js +108 -0
- package/dist/profiler/ui/press.d.ts +5 -0
- package/dist/profiler/ui/press.js +19 -0
- package/dist/profiler/ui/profiler.css +650 -0
- package/dist/profiler/ui/report-data.d.ts +189 -0
- package/dist/profiler/ui/report-data.js +393 -0
- package/dist/profiler/ui/report-request.d.ts +6 -0
- package/dist/profiler/ui/report-request.js +50 -0
- package/dist/profiler/ui/row-anchor.svelte.d.ts +12 -0
- package/dist/profiler/ui/row-anchor.svelte.js +44 -0
- package/dist/profiler/ui/sort.svelte.d.ts +14 -0
- package/dist/profiler/ui/sort.svelte.js +37 -0
- package/dist/profiler/ui/store.d.ts +45 -0
- package/dist/profiler/ui/store.js +92 -0
- package/dist/profiler/visit.d.ts +389 -0
- package/dist/profiler/visit.js +1002 -0
- package/dist/report.d.ts +5 -0
- package/dist/report.js +4 -0
- package/dist/server.d.ts +11 -0
- package/dist/server.js +38 -0
- package/dist/vite.d.ts +12 -0
- package/dist/vite.js +78 -0
- package/package.json +105 -6
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { SlimProfile } from "./profile-store.js";
|
|
2
|
+
export interface Anchor {
|
|
3
|
+
key: string;
|
|
4
|
+
title: string;
|
|
5
|
+
detail: string;
|
|
6
|
+
/** the elements, found now */
|
|
7
|
+
find: () => Element[];
|
|
8
|
+
/** how many there were when the list was built */
|
|
9
|
+
count: number;
|
|
10
|
+
}
|
|
11
|
+
/** a tag name we may put in a selector: letters, digits and dashes, starting with a letter */
|
|
12
|
+
export declare function safe_tag(t: string): boolean;
|
|
13
|
+
/** Every element on the page, INTO open shadow roots (a design system nests its parts in them), the
|
|
14
|
+
* dock's own host left out. */
|
|
15
|
+
export declare function all_elements(doc?: Document): Element[];
|
|
16
|
+
/** Every element that has a source location, by file (one pass over the page). */
|
|
17
|
+
export declare function elements_by_file(doc?: Document): Map<string, Element[]>;
|
|
18
|
+
/** Hosts of declarative shadow roots on the page (the dock's own host left out). */
|
|
19
|
+
export declare function shadow_hosts(doc?: Document): Element[];
|
|
20
|
+
/** Elements of a tag, into shadow roots. */
|
|
21
|
+
export declare function by_tag(tag: string, doc?: Document): Element[];
|
|
22
|
+
export declare function page_anchors(p: SlimProfile, doc?: Document): Anchor[];
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
// src/devtools/profile-anchors.ts
|
|
2
|
+
const ms = (n) => n >= 1e3 ? (n / 1e3).toFixed(2) + " s" : n >= 10 ? Math.round(n) + " ms" : n.toFixed(1) + " ms";
|
|
3
|
+
const kb = (n) => n < 1024 ? n + " B" : n < 1048576 ? (n / 1024).toFixed(1) + " KB" : (n / 1048576).toFixed(2) + " MB";
|
|
4
|
+
/** a tag name we may put in a selector: letters, digits and dashes, starting with a letter */
|
|
5
|
+
function safe_tag(t) {
|
|
6
|
+
if (!t || t.length > 60) return false;
|
|
7
|
+
const c0 = t.charCodeAt(0);
|
|
8
|
+
if (!(c0 >= 97 && c0 <= 122 || c0 >= 65 && c0 <= 90)) return false;
|
|
9
|
+
for (let i = 1; i < t.length; i++) {
|
|
10
|
+
const c = t.charCodeAt(i);
|
|
11
|
+
if (!(c >= 97 && c <= 122 || c >= 65 && c <= 90 || c >= 48 && c <= 57 || c === 45)) return false;
|
|
12
|
+
}
|
|
13
|
+
return true;
|
|
14
|
+
}
|
|
15
|
+
/** Every element on the page, INTO open shadow roots (a design system nests its parts in them), the
|
|
16
|
+
* dock's own host left out. */
|
|
17
|
+
function all_elements(doc = document) {
|
|
18
|
+
const out = [];
|
|
19
|
+
const walk = (root) => {
|
|
20
|
+
for (const el of root.querySelectorAll("*")) {
|
|
21
|
+
if (el.hasAttribute("data-ogygia-devtools-host")) continue;
|
|
22
|
+
out.push(el);
|
|
23
|
+
if (el.shadowRoot) walk(el.shadowRoot);
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
walk(doc);
|
|
27
|
+
return out;
|
|
28
|
+
}
|
|
29
|
+
/** Every element that has a source location, by file (one pass over the page). */
|
|
30
|
+
function elements_by_file(doc = document) {
|
|
31
|
+
const out = /* @__PURE__ */ new Map();
|
|
32
|
+
for (const el of all_elements(doc)) {
|
|
33
|
+
const file = el.__svelte_meta?.loc?.file;
|
|
34
|
+
if (!file) continue;
|
|
35
|
+
const list = out.get(file);
|
|
36
|
+
if (list) list.push(el);
|
|
37
|
+
else out.set(file, [el]);
|
|
38
|
+
}
|
|
39
|
+
return out;
|
|
40
|
+
}
|
|
41
|
+
/** Hosts of declarative shadow roots on the page (the dock's own host left out). */
|
|
42
|
+
function shadow_hosts(doc = document) {
|
|
43
|
+
return all_elements(doc).filter((el) => !!el.shadowRoot);
|
|
44
|
+
}
|
|
45
|
+
/** Elements of a tag, into shadow roots. */
|
|
46
|
+
function by_tag(tag, doc = document) {
|
|
47
|
+
const t = tag.toLowerCase();
|
|
48
|
+
return all_elements(doc).filter((el) => el.localName === t);
|
|
49
|
+
}
|
|
50
|
+
const same_file = (a, b) => a === b || a.endsWith("/" + b) || b.endsWith("/" + a);
|
|
51
|
+
function page_anchors(p, doc = document) {
|
|
52
|
+
const out = [];
|
|
53
|
+
const by_file = elements_by_file(doc);
|
|
54
|
+
const files = [...by_file.keys()];
|
|
55
|
+
const els_of = (file) => {
|
|
56
|
+
const hit = files.find((f) => same_file(f, file));
|
|
57
|
+
return hit ? by_file.get(hit) ?? [] : [];
|
|
58
|
+
};
|
|
59
|
+
const seen_files = /* @__PURE__ */ new Set();
|
|
60
|
+
for (const c of p.components) {
|
|
61
|
+
if (!c.file || !c.file.endsWith(".svelte") || seen_files.has(c.file)) continue;
|
|
62
|
+
seen_files.add(c.file);
|
|
63
|
+
const n = els_of(c.file).length;
|
|
64
|
+
if (!n) continue;
|
|
65
|
+
const file = c.file;
|
|
66
|
+
out.push({
|
|
67
|
+
key: "component:" + file,
|
|
68
|
+
title: c.name,
|
|
69
|
+
detail: `${ms(c.self_ms)} on the server${c.instances ? ` for ${c.instances} instance${c.instances === 1 ? "" : "s"}` : ""} · ${n} element${n === 1 ? "" : "s"} here`,
|
|
70
|
+
find: () => els_of(file),
|
|
71
|
+
count: n
|
|
72
|
+
});
|
|
73
|
+
if (out.length >= 6) break;
|
|
74
|
+
}
|
|
75
|
+
const islands = p.islands.filter((i) => i.fp && doc.querySelector(`ogygia-region[data-og-fp="${i.fp}"]`)).sort((a, b) => (b.ssr_ms ?? 0) - (a.ssr_ms ?? 0) || (b.props_bytes ?? 0) - (a.props_bytes ?? 0)).slice(0, 4);
|
|
76
|
+
for (const i of islands) {
|
|
77
|
+
const fp = i.fp;
|
|
78
|
+
out.push({
|
|
79
|
+
key: "island:" + fp,
|
|
80
|
+
title: `${i.name} (island)`,
|
|
81
|
+
detail: [
|
|
82
|
+
i.ssr_ms !== null ? `${ms(i.ssr_ms)} server render` : null,
|
|
83
|
+
i.props_bytes !== null ? `${kb(i.props_bytes)} props${i.culprit ? ` (not JSON: ${i.culprit})` : ""}` : null,
|
|
84
|
+
i.client_p50_ms !== null ? `${ms(i.client_p50_ms)} to hydrate` : null
|
|
85
|
+
].filter(Boolean).join(" · "),
|
|
86
|
+
find: () => [...doc.querySelectorAll(`ogygia-region[data-og-fp="${fp}"]`)],
|
|
87
|
+
count: 1
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
const runs = Math.max(1, p.runs.length);
|
|
91
|
+
for (const s of p.spans) for (const t of s.tags) {
|
|
92
|
+
if (!safe_tag(t.tag)) continue;
|
|
93
|
+
const n = by_tag(t.tag, doc).length;
|
|
94
|
+
if (!n) continue;
|
|
95
|
+
const tag = t.tag;
|
|
96
|
+
out.push({
|
|
97
|
+
key: `span:${s.name}:${tag}`,
|
|
98
|
+
title: `${s.name} · <${tag}>`,
|
|
99
|
+
detail: `${Math.round(t.count / runs)} per render, ${ms(t.ms / runs)} of the server's CPU each render · ${n} on the page`,
|
|
100
|
+
find: () => by_tag(tag, doc),
|
|
101
|
+
count: n
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
if (p.html && p.html.shadow_count) {
|
|
105
|
+
const n = shadow_hosts(doc).length;
|
|
106
|
+
if (n) out.push({
|
|
107
|
+
key: "html:shadow",
|
|
108
|
+
title: `${p.html.shadow_count} declarative shadow roots`,
|
|
109
|
+
detail: `${kb(p.html.shadow)} of the ${kb(p.html.total)} HTML · ${n} became shadow roots here` + (n < p.html.shadow_count ? ` — the browser refused the other ${p.html.shadow_count - n} (a second shadow root on one element is not allowed): their bytes are shipped for nothing` : ""),
|
|
110
|
+
find: () => shadow_hosts(doc),
|
|
111
|
+
count: n
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
if (p.lcp && (p.lcp.fp || p.lcp.tag)) {
|
|
115
|
+
const find = () => {
|
|
116
|
+
const island = p.lcp.fp ? doc.querySelector(`ogygia-region[data-og-fp="${p.lcp.fp}"]`) : null;
|
|
117
|
+
if (island && p.lcp.tag && safe_tag(p.lcp.tag)) {
|
|
118
|
+
const inner = island.querySelector(p.lcp.tag);
|
|
119
|
+
if (inner) return [inner];
|
|
120
|
+
}
|
|
121
|
+
return island ? [island] : [];
|
|
122
|
+
};
|
|
123
|
+
const n = find().length;
|
|
124
|
+
if (n) out.push({
|
|
125
|
+
key: "lcp",
|
|
126
|
+
title: "Largest paint",
|
|
127
|
+
detail: `<${p.lcp.tag ?? "?"}>${p.lcp.ms !== null ? ` at ${ms(p.lcp.ms)}` : ""}${p.lcp.fp ? ` in ${p.islands.find((i) => i.fp === p.lcp.fp)?.name ?? "an island"}` : ""}`,
|
|
128
|
+
find,
|
|
129
|
+
count: n
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
const keys = /* @__PURE__ */ new Set();
|
|
133
|
+
return out.filter((a) => !keys.has(a.key) && !!keys.add(a.key));
|
|
134
|
+
}
|
|
135
|
+
export { all_elements, by_tag, elements_by_file, page_anchors, safe_tag, shadow_hosts };
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The devtools' copy of the last SSR profile per page — the native Profiler tab writes it, the Lens
|
|
3
|
+
* roster and the island detail read it (joined by fingerprint, then by entry), so a server cost sits
|
|
4
|
+
* next to the browser's numbers for the same island. Kept SLIM: the profiler's JSON answer is
|
|
5
|
+
* hundreds of KB; the dock needs the score, the forecast, the findings, the islands, the top
|
|
6
|
+
* components and the calls. The slim copy also survives a reload (sessionStorage, best effort).
|
|
7
|
+
*/
|
|
8
|
+
export interface SlimIsland {
|
|
9
|
+
name: string;
|
|
10
|
+
fp: string | null;
|
|
11
|
+
entry: string | null;
|
|
12
|
+
wake: string | null;
|
|
13
|
+
ssr_ms: number | null;
|
|
14
|
+
props_bytes: number | null;
|
|
15
|
+
seed_refs: number;
|
|
16
|
+
culprit: string | null;
|
|
17
|
+
client_p50_ms: number | null;
|
|
18
|
+
recovered: number;
|
|
19
|
+
/** its first line that draws differently in the browser (the profiler's reading of its sources) */
|
|
20
|
+
hazard?: {
|
|
21
|
+
file: string;
|
|
22
|
+
line: number;
|
|
23
|
+
kind: "await" | "browser";
|
|
24
|
+
reads?: string;
|
|
25
|
+
guard?: true;
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
export interface SlimProfile {
|
|
29
|
+
id: string;
|
|
30
|
+
page: string;
|
|
31
|
+
at: number;
|
|
32
|
+
runs: number[];
|
|
33
|
+
render_ms: number;
|
|
34
|
+
/** the Cache-Control the page answered the profiler's renders (what the browser cannot read) */
|
|
35
|
+
cache_control?: string;
|
|
36
|
+
/** the page is prerendered: 'file' (served as the build's file), 'route' (its route prerenders) */
|
|
37
|
+
prerendered?: "file" | "route";
|
|
38
|
+
score: {
|
|
39
|
+
score: number;
|
|
40
|
+
grade: string;
|
|
41
|
+
categories: {
|
|
42
|
+
key: string;
|
|
43
|
+
label: string;
|
|
44
|
+
score: number;
|
|
45
|
+
value: string;
|
|
46
|
+
lost: number;
|
|
47
|
+
detail: string[];
|
|
48
|
+
}[];
|
|
49
|
+
missing: {
|
|
50
|
+
label: string;
|
|
51
|
+
why: string;
|
|
52
|
+
}[];
|
|
53
|
+
/** since the last profile of this page */
|
|
54
|
+
was?: {
|
|
55
|
+
score: number;
|
|
56
|
+
grade: string;
|
|
57
|
+
};
|
|
58
|
+
} | null;
|
|
59
|
+
/** what the page loads at start (a build only) */
|
|
60
|
+
assets: {
|
|
61
|
+
js: number;
|
|
62
|
+
lazy_js: number;
|
|
63
|
+
wire: number;
|
|
64
|
+
files: {
|
|
65
|
+
name: string;
|
|
66
|
+
kind: string;
|
|
67
|
+
bytes: number;
|
|
68
|
+
lazy: boolean;
|
|
69
|
+
contains: string[];
|
|
70
|
+
}[];
|
|
71
|
+
/** one module shipped as two copies, both loaded by this page: its files, their bytes, where each
|
|
72
|
+
* came from, and what the second copy costs */
|
|
73
|
+
twice?: {
|
|
74
|
+
name: string;
|
|
75
|
+
copies: {
|
|
76
|
+
file: string;
|
|
77
|
+
bytes: number;
|
|
78
|
+
from?: string;
|
|
79
|
+
}[];
|
|
80
|
+
extra: number;
|
|
81
|
+
}[];
|
|
82
|
+
} | null;
|
|
83
|
+
forecast: {
|
|
84
|
+
now_ms: number;
|
|
85
|
+
after_ms: number;
|
|
86
|
+
parts: {
|
|
87
|
+
title: string;
|
|
88
|
+
kind: string;
|
|
89
|
+
ms: number;
|
|
90
|
+
wait: boolean;
|
|
91
|
+
}[];
|
|
92
|
+
} | null;
|
|
93
|
+
findings: {
|
|
94
|
+
severity: string;
|
|
95
|
+
code: string;
|
|
96
|
+
message: string;
|
|
97
|
+
fix?: string;
|
|
98
|
+
fps?: string[];
|
|
99
|
+
}[];
|
|
100
|
+
islands: SlimIsland[];
|
|
101
|
+
components: {
|
|
102
|
+
name: string;
|
|
103
|
+
file: string | null;
|
|
104
|
+
self_ms: number;
|
|
105
|
+
total_ms: number;
|
|
106
|
+
instances: number | null;
|
|
107
|
+
}[];
|
|
108
|
+
network: {
|
|
109
|
+
count: number;
|
|
110
|
+
total_ms: number;
|
|
111
|
+
sequential_ms: number;
|
|
112
|
+
calls: {
|
|
113
|
+
method: string;
|
|
114
|
+
url: string;
|
|
115
|
+
status: number | null;
|
|
116
|
+
wait_ms: number;
|
|
117
|
+
}[];
|
|
118
|
+
} | null;
|
|
119
|
+
seed_bytes: number | null;
|
|
120
|
+
links: {
|
|
121
|
+
html: string;
|
|
122
|
+
json: string;
|
|
123
|
+
} | null;
|
|
124
|
+
/** named spans split by an element tag (`span('ds.render', …, { tag })`): the page's elements of it */
|
|
125
|
+
spans: {
|
|
126
|
+
name: string;
|
|
127
|
+
total_ms: number;
|
|
128
|
+
tags: {
|
|
129
|
+
tag: string;
|
|
130
|
+
count: number;
|
|
131
|
+
ms: number;
|
|
132
|
+
}[];
|
|
133
|
+
}[];
|
|
134
|
+
/** the document as bytes: its size, what kind, and the declarative shadow roots in it */
|
|
135
|
+
html: {
|
|
136
|
+
total: number;
|
|
137
|
+
shadow: number;
|
|
138
|
+
shadow_count: number;
|
|
139
|
+
by_kind: Record<string, number>;
|
|
140
|
+
} | null;
|
|
141
|
+
/** the largest paint the browser saw (its tag, the island it sat in) */
|
|
142
|
+
lcp: {
|
|
143
|
+
tag: string | null;
|
|
144
|
+
fp: string | null;
|
|
145
|
+
ms: number | null;
|
|
146
|
+
} | null;
|
|
147
|
+
/** the server's hottest code (own time, per render) */
|
|
148
|
+
hot: {
|
|
149
|
+
name: string;
|
|
150
|
+
file: string | null;
|
|
151
|
+
line: number | null;
|
|
152
|
+
ms: number;
|
|
153
|
+
kind: string;
|
|
154
|
+
}[];
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Profile a same-origin path on the server, now (the profiler's `/page?format=json`; a 409 = the
|
|
158
|
+
* recorder is busy: wait and ask again). Stores the slim copy and returns it.
|
|
159
|
+
*/
|
|
160
|
+
export declare function run_profile(path: string, runs?: number, base?: string): Promise<SlimProfile>;
|
|
161
|
+
/**
|
|
162
|
+
* A record of the profiler's JSON answer, untyped on the wire (and possibly from another version):
|
|
163
|
+
* read deep and tolerant (`r.since?.score?.a`), with every value checked before use (`num`, `str`,
|
|
164
|
+
* `arr`, `String`), so a missing or odd field reads as empty instead of throwing.
|
|
165
|
+
*/
|
|
166
|
+
type WireRecord = Record<string, any>;
|
|
167
|
+
/** The profiler's JSON answer (`/page?format=json`) cut to what the dock shows. Tolerant: a field
|
|
168
|
+
* the report lacks is left empty, never a throw. */
|
|
169
|
+
export declare function slim_profile(r: WireRecord): SlimProfile;
|
|
170
|
+
/** The server row for an island: by fingerprint, else by entry (the same island with other props). */
|
|
171
|
+
export declare function island_row(p: SlimProfile | null, fp: string | null, entry: string | null): SlimIsland | null;
|
|
172
|
+
export declare function set_profile(p: SlimProfile): void;
|
|
173
|
+
export declare function profile_for(page: string): SlimProfile | null;
|
|
174
|
+
export declare function profiles_version(): number;
|
|
175
|
+
export {};
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
// src/devtools/profile-store.ts
|
|
2
|
+
/**
|
|
3
|
+
* Profile a same-origin path on the server, now (the profiler's `/page?format=json`; a 409 = the
|
|
4
|
+
* recorder is busy: wait and ask again). Stores the slim copy and returns it.
|
|
5
|
+
*/
|
|
6
|
+
async function run_profile(path, runs = 3, base = "/__profiler") {
|
|
7
|
+
const url = `${base}/page?p=${encodeURIComponent(path)}&runs=${runs}&format=json`;
|
|
8
|
+
for (let tries = 0; tries < 30; tries++) {
|
|
9
|
+
const r = await fetch(url, { headers: { accept: "application/json" } });
|
|
10
|
+
if (r.status === 409) {
|
|
11
|
+
await new Promise((ok) => setTimeout(ok, 2e3));
|
|
12
|
+
continue;
|
|
13
|
+
}
|
|
14
|
+
if (!r.ok) {
|
|
15
|
+
const text = await r.text();
|
|
16
|
+
let said = text;
|
|
17
|
+
try {
|
|
18
|
+
const j = JSON.parse(text);
|
|
19
|
+
said = typeof j.error === "string" ? j.error : typeof j.message === "string" ? j.message : text;
|
|
20
|
+
} catch {}
|
|
21
|
+
throw new Error(r.status === 503 ? said.slice(0, 600) : `the profiler answered ${r.status}: ${said.slice(0, 300)}`);
|
|
22
|
+
}
|
|
23
|
+
const p = slim_profile(await r.json());
|
|
24
|
+
set_profile(p);
|
|
25
|
+
return p;
|
|
26
|
+
}
|
|
27
|
+
throw new Error("another profile kept the recorder busy for a minute");
|
|
28
|
+
}
|
|
29
|
+
const num = (v) => typeof v === "number" && Number.isFinite(v) ? v : null;
|
|
30
|
+
const str = (v) => typeof v === "string" ? v : null;
|
|
31
|
+
const arr = (v) => Array.isArray(v) ? v.filter((x) => x && typeof x === "object") : [];
|
|
32
|
+
/** an island row's first hazard line — a value it renders before a guard around browser-only work */
|
|
33
|
+
function slim_hazard(v) {
|
|
34
|
+
const list = arr(v).filter((h) => typeof h.file === "string" && typeof h.line === "number" && (h.kind === "await" || h.kind === "browser"));
|
|
35
|
+
const h = list.find((x) => !x.guard) ?? list[0];
|
|
36
|
+
return h ? { hazard: {
|
|
37
|
+
file: h.file,
|
|
38
|
+
line: h.line,
|
|
39
|
+
kind: h.kind,
|
|
40
|
+
...typeof h.reads === "string" ? { reads: h.reads } : {},
|
|
41
|
+
...h.guard === true ? { guard: true } : {}
|
|
42
|
+
} } : {};
|
|
43
|
+
}
|
|
44
|
+
function median(xs) {
|
|
45
|
+
if (!xs.length) return 0;
|
|
46
|
+
const s = [...xs].sort((a, b) => a - b);
|
|
47
|
+
const m = s.length >> 1;
|
|
48
|
+
return s.length % 2 ? s[m] : (s[m - 1] + s[m]) / 2;
|
|
49
|
+
}
|
|
50
|
+
/** The profiler's JSON answer (`/page?format=json`) cut to what the dock shows. Tolerant: a field
|
|
51
|
+
* the report lacks is left empty, never a throw. */
|
|
52
|
+
function slim_profile(r) {
|
|
53
|
+
const target = r?.target ?? {};
|
|
54
|
+
const runs = (Array.isArray(target.runs) ? target.runs : []).filter((x) => typeof x === "number");
|
|
55
|
+
const sc = r?.score;
|
|
56
|
+
const fc = r?.forecast;
|
|
57
|
+
const net = r?.network;
|
|
58
|
+
const order = {
|
|
59
|
+
error: 0,
|
|
60
|
+
warn: 1,
|
|
61
|
+
info: 2
|
|
62
|
+
};
|
|
63
|
+
return {
|
|
64
|
+
id: String(r?.id ?? ""),
|
|
65
|
+
page: String(target.page ?? ""),
|
|
66
|
+
at: num(r?.created) ?? Date.now(),
|
|
67
|
+
runs,
|
|
68
|
+
render_ms: Math.round(median(runs) * 10) / 10,
|
|
69
|
+
...typeof target.cache_control === "string" && target.cache_control ? { cache_control: target.cache_control.slice(0, 200) } : {},
|
|
70
|
+
...target.prerendered === "file" || target.prerendered === "route" ? { prerendered: target.prerendered } : {},
|
|
71
|
+
score: sc && typeof sc.score === "number" ? {
|
|
72
|
+
score: sc.score,
|
|
73
|
+
grade: String(sc.grade ?? ""),
|
|
74
|
+
categories: arr(sc.categories).map((c) => ({
|
|
75
|
+
key: String(c.key),
|
|
76
|
+
label: String(c.label ?? c.key),
|
|
77
|
+
score: num(c.score) ?? 0,
|
|
78
|
+
value: String(c.value ?? ""),
|
|
79
|
+
lost: num(c.lost) ?? 0,
|
|
80
|
+
detail: Array.isArray(c.detail) ? c.detail.map(String).slice(0, 4) : []
|
|
81
|
+
})),
|
|
82
|
+
missing: arr(sc.missing).map((m) => ({
|
|
83
|
+
label: String(m.label ?? m.key),
|
|
84
|
+
why: String(m.why ?? "")
|
|
85
|
+
})),
|
|
86
|
+
...typeof r?.since?.score?.a === "number" ? { was: {
|
|
87
|
+
score: r.since.score.a,
|
|
88
|
+
grade: String(r.since.score.a_grade ?? "")
|
|
89
|
+
} } : {}
|
|
90
|
+
} : null,
|
|
91
|
+
assets: r?.assets?.totals ? {
|
|
92
|
+
js: num(r.assets.totals.js) ?? 0,
|
|
93
|
+
lazy_js: num(r.assets.totals.lazy_js) ?? 0,
|
|
94
|
+
wire: num(r.assets.totals.wire) ?? 0,
|
|
95
|
+
files: arr(r.assets.files).slice(0, 10).map((f) => ({
|
|
96
|
+
name: String(f.url ?? "").split("?")[0].split("/").pop() ?? "",
|
|
97
|
+
kind: String(f.kind ?? ""),
|
|
98
|
+
bytes: num(f.bytes) ?? 0,
|
|
99
|
+
lazy: !!f.lazy,
|
|
100
|
+
contains: Array.isArray(f.contains) ? f.contains.map(String).slice(0, 4) : []
|
|
101
|
+
})),
|
|
102
|
+
...(() => {
|
|
103
|
+
const twice = arr(r.assets.twice).slice(0, 8).map((d) => ({
|
|
104
|
+
name: String(d.name ?? ""),
|
|
105
|
+
copies: arr(d.copies).slice(0, 3).map((c) => ({
|
|
106
|
+
file: String(c.file ?? ""),
|
|
107
|
+
bytes: num(c.bytes) ?? 0,
|
|
108
|
+
...typeof c.from === "string" ? { from: c.from } : {}
|
|
109
|
+
})),
|
|
110
|
+
extra: num(d.extra) ?? 0
|
|
111
|
+
})).filter((d) => d.name && d.copies.length >= 2);
|
|
112
|
+
return twice.length ? { twice } : {};
|
|
113
|
+
})()
|
|
114
|
+
} : null,
|
|
115
|
+
forecast: fc && typeof fc.now_ms === "number" ? {
|
|
116
|
+
now_ms: fc.now_ms,
|
|
117
|
+
after_ms: num(fc.after_ms) ?? fc.now_ms,
|
|
118
|
+
parts: arr(fc.parts).slice(0, 8).map((p) => ({
|
|
119
|
+
title: String(p.title ?? p.kind),
|
|
120
|
+
kind: String(p.kind ?? ""),
|
|
121
|
+
ms: num(p.ms) ?? 0,
|
|
122
|
+
wait: !!p.wait
|
|
123
|
+
}))
|
|
124
|
+
} : null,
|
|
125
|
+
findings: arr(r?.findings).filter((f) => f.code !== "summary").map((f) => ({
|
|
126
|
+
severity: String(f.severity ?? "info"),
|
|
127
|
+
code: String(f.code ?? ""),
|
|
128
|
+
message: String(f.message ?? ""),
|
|
129
|
+
...f.fix ? { fix: String(f.fix) } : {},
|
|
130
|
+
...Array.isArray(f.fps) && f.fps.length ? { fps: f.fps.slice(0, 40).map(String) } : {}
|
|
131
|
+
})).sort((a, b) => (order[a.severity] ?? 3) - (order[b.severity] ?? 3)).slice(0, 30),
|
|
132
|
+
islands: arr(r?.ogygia?.island_rows).map((i) => ({
|
|
133
|
+
name: String(i.name ?? ""),
|
|
134
|
+
fp: str(i.fp),
|
|
135
|
+
entry: str(i.entry),
|
|
136
|
+
wake: str(i.wake),
|
|
137
|
+
ssr_ms: num(i.ssr_ms),
|
|
138
|
+
props_bytes: num(i.props_bytes),
|
|
139
|
+
seed_refs: num(i.seed_refs) ?? 0,
|
|
140
|
+
culprit: str(i.devalue_culprit),
|
|
141
|
+
client_p50_ms: num(i.client?.p50_ms),
|
|
142
|
+
recovered: num(i.client?.recovered) ?? 0,
|
|
143
|
+
...slim_hazard(i.hazards)
|
|
144
|
+
})),
|
|
145
|
+
components: arr(r?.components).slice(0, 12).map((c) => ({
|
|
146
|
+
name: String(c.name ?? ""),
|
|
147
|
+
file: str(c.file),
|
|
148
|
+
self_ms: num(c.self_ms) ?? 0,
|
|
149
|
+
total_ms: num(c.total_ms) ?? 0,
|
|
150
|
+
instances: num(c.instances)
|
|
151
|
+
})),
|
|
152
|
+
network: net ? {
|
|
153
|
+
count: num(net.count) ?? 0,
|
|
154
|
+
total_ms: num(net.total_ms) ?? 0,
|
|
155
|
+
sequential_ms: num(net.sequential_ms) ?? 0,
|
|
156
|
+
calls: arr(net.calls).slice(0, 20).map((c) => ({
|
|
157
|
+
method: String(c.method ?? "GET"),
|
|
158
|
+
url: String(c.url ?? ""),
|
|
159
|
+
status: num(c.status),
|
|
160
|
+
wait_ms: num(c.wait_ms) ?? 0
|
|
161
|
+
}))
|
|
162
|
+
} : null,
|
|
163
|
+
seed_bytes: num(r?.ogygia?.seed_bytes),
|
|
164
|
+
links: r?.links?.html ? {
|
|
165
|
+
html: String(r.links.html),
|
|
166
|
+
json: String(r.links.json ?? "")
|
|
167
|
+
} : null,
|
|
168
|
+
spans: arr(r?.spans).filter((s) => Array.isArray(s?.by?.tag) && s.by.tag.length).slice(0, 6).map((s) => ({
|
|
169
|
+
name: String(s.name ?? ""),
|
|
170
|
+
total_ms: num(s.total_ms) ?? 0,
|
|
171
|
+
tags: arr(s.by.tag).slice(0, 8).map((t) => ({
|
|
172
|
+
tag: String(t.value ?? ""),
|
|
173
|
+
count: num(t.count) ?? 0,
|
|
174
|
+
ms: num(t.share_ms) ?? num(t.wall_ms) ?? 0
|
|
175
|
+
}))
|
|
176
|
+
})),
|
|
177
|
+
html: r?.strip ? {
|
|
178
|
+
total: num(r.strip.total) ?? 0,
|
|
179
|
+
shadow: num(r.strip.by_kind?.shadow) ?? 0,
|
|
180
|
+
shadow_count: num(r.strip.shadow_count) ?? 0,
|
|
181
|
+
by_kind: Object.fromEntries(Object.entries(r.strip.by_kind ?? {}).filter(([, v]) => typeof v === "number"))
|
|
182
|
+
} : null,
|
|
183
|
+
lcp: r?.browser?.visit?.paints ? {
|
|
184
|
+
tag: str(r.browser.visit.paints.lcp_tag),
|
|
185
|
+
fp: str(r.browser.visit.paints.lcp_fp),
|
|
186
|
+
ms: num(r.browser.visit.paints.lcp)
|
|
187
|
+
} : null,
|
|
188
|
+
hot: arr(r?.hot_functions).filter((f) => f.category !== "profiler" && f.category !== "idle").slice(0, 6).map((f) => ({
|
|
189
|
+
name: String(f.label || f.name || "(anonymous)").slice(0, 80),
|
|
190
|
+
file: str(f.file),
|
|
191
|
+
line: num(f.line),
|
|
192
|
+
ms: Math.round((num(f.self_ms) ?? 0) / Math.max(1, runs.length) * 10) / 10,
|
|
193
|
+
kind: String(f.category ?? "")
|
|
194
|
+
}))
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
/** The server row for an island: by fingerprint, else by entry (the same island with other props). */
|
|
198
|
+
function island_row(p, fp, entry) {
|
|
199
|
+
if (!p) return null;
|
|
200
|
+
if (fp) {
|
|
201
|
+
const hit = p.islands.find((i) => i.fp === fp);
|
|
202
|
+
if (hit) return hit;
|
|
203
|
+
}
|
|
204
|
+
if (entry) {
|
|
205
|
+
const tail = entry.slice(entry.lastIndexOf("/") + 1);
|
|
206
|
+
return p.islands.find((i) => i.entry && i.entry.slice(i.entry.lastIndexOf("/") + 1) === tail) ?? null;
|
|
207
|
+
}
|
|
208
|
+
return null;
|
|
209
|
+
}
|
|
210
|
+
const KEY = "ogygia:devtools:profiles:v1";
|
|
211
|
+
const by_page = /* @__PURE__ */ new Map();
|
|
212
|
+
let loaded = false;
|
|
213
|
+
let version = 0;
|
|
214
|
+
function load() {
|
|
215
|
+
if (loaded) return;
|
|
216
|
+
loaded = true;
|
|
217
|
+
try {
|
|
218
|
+
const raw = sessionStorage.getItem(KEY);
|
|
219
|
+
const list = raw ? JSON.parse(raw) : [];
|
|
220
|
+
for (const p of list) if (p && p.page) by_page.set(p.page, p);
|
|
221
|
+
} catch {}
|
|
222
|
+
}
|
|
223
|
+
function set_profile(p) {
|
|
224
|
+
load();
|
|
225
|
+
by_page.set(p.page, p);
|
|
226
|
+
version++;
|
|
227
|
+
try {
|
|
228
|
+
const list = [...by_page.values()].sort((a, b) => b.at - a.at).slice(0, 8);
|
|
229
|
+
sessionStorage.setItem(KEY, JSON.stringify(list));
|
|
230
|
+
} catch {}
|
|
231
|
+
}
|
|
232
|
+
function profile_for(page) {
|
|
233
|
+
load();
|
|
234
|
+
return by_page.get(page) ?? null;
|
|
235
|
+
}
|
|
236
|
+
function profiles_version() {
|
|
237
|
+
return version;
|
|
238
|
+
}
|
|
239
|
+
export { island_row, profile_for, profiles_version, run_profile, set_profile, slim_profile };
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { IslandHazard } from "@ogygia/core/insights";
|
|
2
|
+
import { DevtoolsEvent } from "@ogygia/core/events";
|
|
3
|
+
/** Region kind, from the two dials the compiler stamps. */
|
|
4
|
+
export type RegionKind = "island" | "lake" | "hole";
|
|
5
|
+
export declare function region_kind(el: Element): RegionKind;
|
|
6
|
+
/** One region's identity + live state, read straight off its `<ogygia-region>`. */
|
|
7
|
+
export interface RegionInfo {
|
|
8
|
+
el: Element;
|
|
9
|
+
kind: RegionKind;
|
|
10
|
+
wake: string;
|
|
11
|
+
/** its identity (the stable URL every name and key uses) */
|
|
12
|
+
entry: string | null;
|
|
13
|
+
/** its location: the content-hashed file it loads (a build names one; else the entry) */
|
|
14
|
+
src: string | null;
|
|
15
|
+
fp: string | null;
|
|
16
|
+
hydrated: boolean;
|
|
17
|
+
/** a region inside an awake island: it does not wake itself, it rides the island's hydration
|
|
18
|
+
* (`data-nested`) — awake when that island is, with no `data-hydrated` of its own */
|
|
19
|
+
rides?: Element;
|
|
20
|
+
}
|
|
21
|
+
export declare function region_info(el: Element): RegionInfo;
|
|
22
|
+
/** Every `<ogygia-region>` on the page, as {@link RegionInfo}. */
|
|
23
|
+
export declare function all_regions(): RegionInfo[];
|
|
24
|
+
/**
|
|
25
|
+
* The raw devalue text of a region's `<script data-ogygia-props>` sidecar (the props that crossed the
|
|
26
|
+
* boundary), or null. Mirrors the runtime's own sibling walk (skips `<link>` CSS hints). Returned as
|
|
27
|
+
* TEXT so this module stays devalue-free; the detail view decodes it.
|
|
28
|
+
*/
|
|
29
|
+
export declare function region_props_sidecar(el: Element): string | null;
|
|
30
|
+
/** basename of a URL/path (drops query + directory). */
|
|
31
|
+
export declare function basename(url: string): string;
|
|
32
|
+
/** basename without a trailing `.js`, for a compact chunk label. */
|
|
33
|
+
export declare function short_chunk(url: string | null): string;
|
|
34
|
+
/** The island id from an entry URL, or '': `<id>` in the dev server's `virtual:ogygia/island/<id>.js`
|
|
35
|
+
* and in a build's `og-region.<id>.js` (the name maps key the bare id — with the prefix left on, every
|
|
36
|
+
* tab in a build showed hashes). */
|
|
37
|
+
export declare function island_id(entry: string | null | undefined): string;
|
|
38
|
+
/**
|
|
39
|
+
* The compiler's `island id → component name` map (dev-only), published on `window` by the
|
|
40
|
+
* `virtual:ogygia/devtools-names` side-effect module the runtime/devtools boot pulls in. Empty off a
|
|
41
|
+
* devtools build. Lets every tab label a region "Counter" instead of a hashed entry.
|
|
42
|
+
*/
|
|
43
|
+
export declare function region_names(): Record<string, string>;
|
|
44
|
+
/** Component name for an island entry URL, or `short_chunk(entry)` when unknown. */
|
|
45
|
+
export declare function region_name(entry: string | null | undefined): string;
|
|
46
|
+
/**
|
|
47
|
+
* Transitive dev size for an island entry — the compiler's module-graph estimate (wrapper +
|
|
48
|
+
* component + everything imported), published on `window.__ogygia_region_bytes` by the dock's mount
|
|
49
|
+
* fetch. `null` when the island is still cold (never woken → no subgraph yet) or off a dev build.
|
|
50
|
+
*/
|
|
51
|
+
export declare function region_transitive(entry: string | null | undefined): {
|
|
52
|
+
bytes: number;
|
|
53
|
+
modules: number;
|
|
54
|
+
top?: {
|
|
55
|
+
file: string;
|
|
56
|
+
bytes: number;
|
|
57
|
+
}[];
|
|
58
|
+
barrels?: {
|
|
59
|
+
file: string;
|
|
60
|
+
fanout: number;
|
|
61
|
+
}[];
|
|
62
|
+
hazards?: IslandHazard[];
|
|
63
|
+
} | null;
|
|
64
|
+
/** Component name for a region fingerprint — joined to its live `<ogygia-region>` in the DOM. */
|
|
65
|
+
export declare function region_name_by_fp(fp: string | null | undefined): string;
|
|
66
|
+
/** kB with one decimal. */
|
|
67
|
+
export declare function kb(bytes: number): string;
|
|
68
|
+
/** Resource-timing entry for a chunk, matched by hashed basename (unique per build). */
|
|
69
|
+
export declare function timing_for(entry: string): PerformanceResourceTiming | undefined;
|
|
70
|
+
/** Over-the-wire + decoded bytes for a chunk entry (0/0 when not yet loaded or size hidden). */
|
|
71
|
+
export declare function chunk_bytes(entry: string): {
|
|
72
|
+
wire: number;
|
|
73
|
+
raw: number;
|
|
74
|
+
loaded: boolean;
|
|
75
|
+
};
|
|
76
|
+
/** Latest buffered event matching a predicate (newest wins), or null. */
|
|
77
|
+
export declare function latest_event(pred: (e: DevtoolsEvent) => boolean): DevtoolsEvent | null;
|