@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,81 @@
|
|
|
1
|
+
import { all_regions, region_name } from "./regions.js";
|
|
2
|
+
import { tool_fetch, tool_made } from "@ogygia/core/internal/instruments";
|
|
3
|
+
// src/devtools/cache-headers.ts
|
|
4
|
+
/**
|
|
5
|
+
* HOW THE HOST CACHES THE ISLANDS' FILES. In a build every island's file and the runtime are named by
|
|
6
|
+
* their content (the region's `src`, the runtime script's `src`), so the right cache for them is a
|
|
7
|
+
* year-long `immutable` one — a new build names new files. A host that serves them `no-cache`, or
|
|
8
|
+
* with a short `max-age`, makes every returning visitor download (or revalidate) every island again.
|
|
9
|
+
* The browser's timing API does not expose response headers, so this asks the server once for each
|
|
10
|
+
* file's (a HEAD request, past the cache) when the Page tab opens. Off a build (no `src`): nothing.
|
|
11
|
+
*/
|
|
12
|
+
/** A day: a content-named file cached for less than this is fetched again on most return visits. */
|
|
13
|
+
const MIN_MAX_AGE_S = 86400;
|
|
14
|
+
/** Read one `cache-control`: whether it caches the file long enough to count. Split, not a regex. */
|
|
15
|
+
function cached_long(cache_control) {
|
|
16
|
+
if (!cache_control) return false;
|
|
17
|
+
let max_age = -1;
|
|
18
|
+
let immutable = false;
|
|
19
|
+
for (const raw of cache_control.toLowerCase().split(",")) {
|
|
20
|
+
const d = raw.trim();
|
|
21
|
+
if (d === "no-store" || d === "no-cache") return false;
|
|
22
|
+
if (d === "immutable") immutable = true;
|
|
23
|
+
if (d.startsWith("max-age=")) max_age = Number(d.slice(8));
|
|
24
|
+
}
|
|
25
|
+
return immutable ? max_age !== 0 : max_age >= MIN_MAX_AGE_S;
|
|
26
|
+
}
|
|
27
|
+
/** Every content-named file the page loads (the islands' locations, the runtime's), each once. */
|
|
28
|
+
function content_named_files(doc = document) {
|
|
29
|
+
const out = /* @__PURE__ */ new Map();
|
|
30
|
+
const abs = (u) => new URL(u, doc.baseURI).href;
|
|
31
|
+
for (const r of all_regions()) {
|
|
32
|
+
const src = r.el.getAttribute("src");
|
|
33
|
+
if (src && r.entry && src !== r.entry && !out.has(abs(src))) out.set(abs(src), region_name(r.entry));
|
|
34
|
+
}
|
|
35
|
+
const rt = doc.querySelector("script[data-ogygia-runtime]")?.getAttribute("src");
|
|
36
|
+
if (rt && !out.has(abs(rt))) out.set(abs(rt), "the runtime");
|
|
37
|
+
return [...out].map(([url, name]) => ({
|
|
38
|
+
url,
|
|
39
|
+
name
|
|
40
|
+
}));
|
|
41
|
+
}
|
|
42
|
+
/** Ask the server how it caches each file (HEAD, past the browser's cache), at most `limit` files. */
|
|
43
|
+
async function probe_cache(doc = document, limit = 40) {
|
|
44
|
+
const sizes = /* @__PURE__ */ new Map();
|
|
45
|
+
for (const r of performance.getEntriesByType("resource")) if (!tool_made(r)) sizes.set(r.name, r.decodedBodySize || 0);
|
|
46
|
+
const files = content_named_files(doc).slice(0, limit);
|
|
47
|
+
return Promise.all(files.map(async ({ url, name }) => {
|
|
48
|
+
let cache_control = null;
|
|
49
|
+
try {
|
|
50
|
+
cache_control = (await tool_fetch(url, {
|
|
51
|
+
method: "HEAD",
|
|
52
|
+
cache: "no-store"
|
|
53
|
+
})).headers.get("cache-control");
|
|
54
|
+
} catch {
|
|
55
|
+
cache_control = null;
|
|
56
|
+
}
|
|
57
|
+
return {
|
|
58
|
+
url,
|
|
59
|
+
name,
|
|
60
|
+
cache_control,
|
|
61
|
+
bytes: sizes.get(url) ?? 0
|
|
62
|
+
};
|
|
63
|
+
}));
|
|
64
|
+
}
|
|
65
|
+
/** The finding: content-named files the host does not cache long, named, with the bytes at stake. */
|
|
66
|
+
function cache_findings(probes) {
|
|
67
|
+
const short = probes.filter((p) => !cached_long(p.cache_control));
|
|
68
|
+
if (!short.length) return [];
|
|
69
|
+
const kb = (n) => n >= 1024 ? `${(n / 1024).toFixed(1)} KB` : `${n} B`;
|
|
70
|
+
const bytes = short.reduce((s, p) => s + p.bytes, 0);
|
|
71
|
+
const seen = [...new Set(short.map((p) => p.cache_control ?? "no cache-control"))].slice(0, 3);
|
|
72
|
+
const names = [...new Set([...short].sort((a, b) => b.bytes - a.bytes).map((p) => p.name))];
|
|
73
|
+
const list = names.length > 4 ? `${names.slice(0, 4).join(", ")} and ${names.length - 4} more` : names.join(", ");
|
|
74
|
+
return [{
|
|
75
|
+
code: "island-files-uncached",
|
|
76
|
+
severity: "warn",
|
|
77
|
+
message: `${short.length} of the page's content-named files (${list}) are served with \`${seen.join("`, `")}\`: a returning visitor downloads${bytes ? ` ${kb(bytes)}` : ""} again (or waits to revalidate) on every visit. Their names change with their content, so they can be cached for good.`,
|
|
78
|
+
fix: "Serve `_app/immutable/` with `cache-control: public, max-age=31536000, immutable` (SvelteKit's adapters do; a proxy, a CDN rule or a custom server in front can override it)."
|
|
79
|
+
}];
|
|
80
|
+
}
|
|
81
|
+
export { cache_findings, cached_long, content_named_files, probe_cache };
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/** The dock's shadow root (ui-app.ts sets it at mount). */
|
|
2
|
+
export declare function set_highlight_root(r: ShadowRoot): void;
|
|
3
|
+
/** Show boxes over `els` (with a caption); `scroll` brings the first into view. */
|
|
4
|
+
export declare function highlight(els: Element[], caption?: string, scroll?: boolean): void;
|
|
5
|
+
export declare function clear_highlight(): void;
|
|
6
|
+
/** How many boxes are shown now (tests). */
|
|
7
|
+
export declare function highlighted_count(): number;
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
// src/devtools/highlight.ts
|
|
2
|
+
/**
|
|
3
|
+
* Light up MANY elements on the page at once — the profile's "these 290 shadow roots", "every
|
|
4
|
+
* element Icon.svelte rendered", "the largest paint". Boxes live in the dock's own shadow root (a
|
|
5
|
+
* fixed, click-through layer), follow scroll and resize while shown, and cap at a few hundred so a
|
|
6
|
+
* huge match stays cheap. No Svelte: plain DOM, one layer, replaced on each call.
|
|
7
|
+
*/
|
|
8
|
+
let root = null;
|
|
9
|
+
let layer = null;
|
|
10
|
+
let current = [];
|
|
11
|
+
let label = "";
|
|
12
|
+
let frame = 0;
|
|
13
|
+
const MAX_BOXES = 400;
|
|
14
|
+
/** The dock's shadow root (ui-app.ts sets it at mount). */
|
|
15
|
+
function set_highlight_root(r) {
|
|
16
|
+
root = r;
|
|
17
|
+
}
|
|
18
|
+
function draw() {
|
|
19
|
+
frame = 0;
|
|
20
|
+
if (!layer) return;
|
|
21
|
+
layer.textContent = "";
|
|
22
|
+
const vw = innerWidth;
|
|
23
|
+
const vh = innerHeight;
|
|
24
|
+
let n = 0;
|
|
25
|
+
for (const el of current) {
|
|
26
|
+
if (n >= MAX_BOXES) break;
|
|
27
|
+
const r = el.getBoundingClientRect();
|
|
28
|
+
if (!r.width && !r.height) continue;
|
|
29
|
+
if (r.bottom < 0 || r.top > vh || r.right < 0 || r.left > vw) continue;
|
|
30
|
+
const b = document.createElement("div");
|
|
31
|
+
b.style.cssText = `position:fixed;left:${r.left}px;top:${r.top}px;width:${r.width}px;height:${r.height}px;border:1.5px solid #f472b6;background:rgba(244,114,182,.12);border-radius:3px;box-sizing:border-box`;
|
|
32
|
+
layer.appendChild(b);
|
|
33
|
+
n++;
|
|
34
|
+
}
|
|
35
|
+
if (label) {
|
|
36
|
+
const tag = document.createElement("div");
|
|
37
|
+
tag.textContent = label;
|
|
38
|
+
tag.style.cssText = "position:fixed;left:12px;top:12px;padding:4px 9px;border-radius:7px;background:#831843;color:#fce7f3;font:600 11px/1.4 ui-monospace,monospace;box-shadow:0 4px 12px rgba(0,0,0,.35)";
|
|
39
|
+
layer.appendChild(tag);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
const schedule = () => {
|
|
43
|
+
if (!frame) frame = requestAnimationFrame(draw);
|
|
44
|
+
};
|
|
45
|
+
/** Show boxes over `els` (with a caption); `scroll` brings the first into view. */
|
|
46
|
+
function highlight(els, caption = "", scroll = false) {
|
|
47
|
+
if (!root) return;
|
|
48
|
+
current = els;
|
|
49
|
+
label = caption;
|
|
50
|
+
if (!layer) {
|
|
51
|
+
layer = document.createElement("div");
|
|
52
|
+
layer.setAttribute("data-og-highlight", "");
|
|
53
|
+
layer.style.cssText = "position:fixed;inset:0;pointer-events:none;z-index:2147483600";
|
|
54
|
+
root.prepend(layer);
|
|
55
|
+
addEventListener("scroll", schedule, {
|
|
56
|
+
passive: true,
|
|
57
|
+
capture: true
|
|
58
|
+
});
|
|
59
|
+
addEventListener("resize", schedule, { passive: true });
|
|
60
|
+
}
|
|
61
|
+
if (scroll && els[0]) try {
|
|
62
|
+
els[0].scrollIntoView({
|
|
63
|
+
behavior: "smooth",
|
|
64
|
+
block: "center"
|
|
65
|
+
});
|
|
66
|
+
} catch {}
|
|
67
|
+
schedule();
|
|
68
|
+
}
|
|
69
|
+
function clear_highlight() {
|
|
70
|
+
current = [];
|
|
71
|
+
label = "";
|
|
72
|
+
if (layer) {
|
|
73
|
+
layer.remove();
|
|
74
|
+
layer = null;
|
|
75
|
+
removeEventListener("scroll", schedule, { capture: true });
|
|
76
|
+
removeEventListener("resize", schedule);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
/** How many boxes are shown now (tests). */
|
|
80
|
+
function highlighted_count() {
|
|
81
|
+
return current.length;
|
|
82
|
+
}
|
|
83
|
+
export { clear_highlight, highlight, highlighted_count, set_highlight_root };
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { HtmlDiff, HydrationStatus, HydrationStatus as HydrationStatus$1 } from "@ogygia/core/insights";
|
|
2
|
+
import { AttrChange } from "@ogygia/core/internal/instruments";
|
|
3
|
+
export declare const STATUS_RANK: Record<HydrationStatus$1, number>;
|
|
4
|
+
export interface IslandHydration {
|
|
5
|
+
fp: string;
|
|
6
|
+
name: string;
|
|
7
|
+
wake: string;
|
|
8
|
+
status: HydrationStatus$1;
|
|
9
|
+
reason: string;
|
|
10
|
+
el: Element;
|
|
11
|
+
/** `from`: a big island keeps only the window around its first change (runtime/beacon.ts
|
|
12
|
+
* `snapshot_of`) — where that window starts in its markup without comments */
|
|
13
|
+
snap: {
|
|
14
|
+
ssr: string;
|
|
15
|
+
hydrated: string;
|
|
16
|
+
from?: number;
|
|
17
|
+
} | null;
|
|
18
|
+
}
|
|
19
|
+
export declare function hydration_rows(): {
|
|
20
|
+
rows: IslandHydration[];
|
|
21
|
+
measured: boolean;
|
|
22
|
+
};
|
|
23
|
+
/** The same, as plain data (no elements), with each changed island's diff — for tests. `t0` / `done`:
|
|
24
|
+
* when it began to wake and when it could answer, on the document's clock (ms from its start; null
|
|
25
|
+
* for an island that has not woken), so a test can tell which of its steps woke it. */
|
|
26
|
+
export declare function hydration_report(): {
|
|
27
|
+
measured: boolean;
|
|
28
|
+
islands: {
|
|
29
|
+
fp: string;
|
|
30
|
+
name: string;
|
|
31
|
+
wake: string;
|
|
32
|
+
status: HydrationStatus$1;
|
|
33
|
+
reason: string;
|
|
34
|
+
diff: HtmlDiff | null;
|
|
35
|
+
t0: number | null;
|
|
36
|
+
done: number | null;
|
|
37
|
+
/** a `changed` island whose only differences are attributes (the same elements and text — often a
|
|
38
|
+
* custom element setting its own as it upgrades); false when text or elements differ */
|
|
39
|
+
attributesOnly: boolean;
|
|
40
|
+
/** with `attributesOnly`, each attribute that differs */
|
|
41
|
+
attributes: AttrChange[] | null;
|
|
42
|
+
/** input landed on it while it hydrated (a pointer over it, a press, a key, focus): its DOM may
|
|
43
|
+
* have answered that input */
|
|
44
|
+
inputDuringHydration: boolean;
|
|
45
|
+
window_from?: number;
|
|
46
|
+
}[];
|
|
47
|
+
};
|
|
48
|
+
export type { HydrationStatus };
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { all_regions, region_name } from "./regions.js";
|
|
2
|
+
import { html_diff } from "@ogygia/core/insights";
|
|
3
|
+
import { beacon_page } from "@ogygia/core/internal/instruments";
|
|
4
|
+
import { snapshot } from "@ogygia/core/events";
|
|
5
|
+
// src/devtools/hydration.ts
|
|
6
|
+
/**
|
|
7
|
+
* Every island's hydration status, from the bus (failed / recovered / healed), the beacon's
|
|
8
|
+
* before-and-after markup (changed), the element (`data-og-recovered`, `data-hydrated`). Shared by
|
|
9
|
+
* the Hydration tab and the testing API (`window.__ogygia_testing.hydration()`).
|
|
10
|
+
*/
|
|
11
|
+
const STATUS_RANK = {
|
|
12
|
+
failed: 0,
|
|
13
|
+
recovered: 1,
|
|
14
|
+
healed: 2,
|
|
15
|
+
changed: 3,
|
|
16
|
+
asleep: 4,
|
|
17
|
+
clean: 5
|
|
18
|
+
};
|
|
19
|
+
function hydration_rows() {
|
|
20
|
+
const why = /* @__PURE__ */ new Map();
|
|
21
|
+
for (const e of snapshot()) if (e.name === "region.hydrate.failed") {
|
|
22
|
+
if (e.fp) why.set(e.fp, {
|
|
23
|
+
status: "failed",
|
|
24
|
+
reason: e.message.split("\n")[0]
|
|
25
|
+
});
|
|
26
|
+
} else if (e.name === "region.hydrate.recovered") {
|
|
27
|
+
if (e.fp) why.set(e.fp, {
|
|
28
|
+
status: "recovered",
|
|
29
|
+
reason: e.reason ?? ""
|
|
30
|
+
});
|
|
31
|
+
} else if (e.name === "region.hydrate.healed") {
|
|
32
|
+
if (e.fp && why.get(e.fp)?.status !== "recovered") why.set(e.fp, {
|
|
33
|
+
status: "healed",
|
|
34
|
+
reason: e.reason ?? ""
|
|
35
|
+
});
|
|
36
|
+
} else if (e.name === "region.hydrate.done") {
|
|
37
|
+
if (e.fp && why.get(e.fp)?.status === "failed") why.delete(e.fp);
|
|
38
|
+
}
|
|
39
|
+
const page = beacon_page();
|
|
40
|
+
const changed = new Set((page?.islands ?? []).filter((i) => i.changed).map((i) => i.fp));
|
|
41
|
+
const snaps = new Map((page?.snapshots ?? []).map((s) => [s.fp, s]));
|
|
42
|
+
const rows = [];
|
|
43
|
+
for (const r of all_regions()) {
|
|
44
|
+
if (r.kind !== "island" || !r.fp) continue;
|
|
45
|
+
const w = why.get(r.fp);
|
|
46
|
+
const attr = r.el.getAttribute("data-og-recovered");
|
|
47
|
+
const status = w?.status ?? (attr !== null ? "recovered" : changed.has(r.fp) ? "changed" : r.hydrated ? "clean" : "asleep");
|
|
48
|
+
const s = snaps.get(r.fp);
|
|
49
|
+
rows.push({
|
|
50
|
+
fp: r.fp,
|
|
51
|
+
name: region_name(r.entry),
|
|
52
|
+
wake: r.wake,
|
|
53
|
+
status,
|
|
54
|
+
reason: w?.reason || attr || (r.rides ? `rides ${region_name(r.rides.getAttribute("entry"))}, the island around it (it wakes with it, not on its own)` : ""),
|
|
55
|
+
el: r.el,
|
|
56
|
+
snap: s ? {
|
|
57
|
+
ssr: s.ssr,
|
|
58
|
+
hydrated: s.hydrated,
|
|
59
|
+
...s.from !== void 0 ? { from: s.from } : {}
|
|
60
|
+
} : null
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
rows.sort((a, b) => STATUS_RANK[a.status] - STATUS_RANK[b.status] || a.name.localeCompare(b.name));
|
|
64
|
+
return {
|
|
65
|
+
rows,
|
|
66
|
+
measured: !!page
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
/** The same, as plain data (no elements), with each changed island's diff — for tests. `t0` / `done`:
|
|
70
|
+
* when it began to wake and when it could answer, on the document's clock (ms from its start; null
|
|
71
|
+
* for an island that has not woken), so a test can tell which of its steps woke it. */
|
|
72
|
+
function hydration_report() {
|
|
73
|
+
const { rows, measured } = hydration_rows();
|
|
74
|
+
const woke = new Map((beacon_page()?.islands ?? []).map((i) => [i.fp, i]));
|
|
75
|
+
return {
|
|
76
|
+
measured,
|
|
77
|
+
islands: rows.map((r) => ({
|
|
78
|
+
fp: r.fp,
|
|
79
|
+
name: r.name,
|
|
80
|
+
wake: r.wake,
|
|
81
|
+
status: r.status,
|
|
82
|
+
reason: r.reason,
|
|
83
|
+
diff: r.snap && r.status !== "clean" ? html_diff(r.snap.ssr, r.snap.hydrated) : null,
|
|
84
|
+
t0: woke.get(r.fp)?.t0 ?? null,
|
|
85
|
+
done: woke.get(r.fp)?.done ?? null,
|
|
86
|
+
attributesOnly: r.status === "changed" && !!woke.get(r.fp)?.attrs,
|
|
87
|
+
attributes: r.status === "changed" ? woke.get(r.fp)?.attrs ?? null : null,
|
|
88
|
+
inputDuringHydration: !!woke.get(r.fp)?.input,
|
|
89
|
+
...r.snap?.from !== void 0 ? { window_from: r.snap.from } : {}
|
|
90
|
+
}))
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
export { STATUS_RANK, hydration_report, hydration_rows };
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import { install_devtools_ui } from "./ui.js";
|
|
2
|
+
import { CompileEventInput, DEVTOOLS, DEVTOOLS_SCHEMA_VERSION, DevtoolsDomain, DevtoolsEnvelope, DevtoolsEvent, DevtoolsEventInput, DevtoolsSink, DevtoolsTrace, DevtoolsWindowHook, HubEventInput, NavEventInput, RuntimeEventInput, ServerEventInput, WireEventInput, add_sink, clear, configure, emit, ingest, ingest_server_events, install_console_sink, install_postmessage_sink, install_window_sink, snapshot, to_trace } from "@ogygia/core/events";
|
|
3
|
+
export { type CompileEventInput, DEVTOOLS, DEVTOOLS_SCHEMA_VERSION, type DevtoolsDomain, type DevtoolsEnvelope, type DevtoolsEvent, type DevtoolsEventInput, type DevtoolsSink, type DevtoolsTrace, type DevtoolsWindowHook, type HubEventInput, type NavEventInput, type RuntimeEventInput, type ServerEventInput, type WireEventInput, add_sink, clear, configure, emit, ingest, ingest_server_events, install_console_sink, install_devtools_ui, install_postmessage_sink, install_window_sink, snapshot, to_trace };
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import { install_devtools_ui } from "./ui.js";
|
|
2
|
+
import { DEVTOOLS, DEVTOOLS_SCHEMA_VERSION, add_sink, clear, configure, emit, ingest, ingest_server_events, install_console_sink, install_postmessage_sink, install_window_sink, snapshot, to_trace } from "@ogygia/core/events";
|
|
3
|
+
export { DEVTOOLS, DEVTOOLS_SCHEMA_VERSION, add_sink, clear, configure, emit, ingest, ingest_server_events, install_console_sink, install_devtools_ui, install_postmessage_sink, install_window_sink, snapshot, to_trace };
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { Ledger } from "./bytes.js";
|
|
2
|
+
export declare function page_ledger(): Ledger | null;
|
|
3
|
+
/** One island's line of the ledger (by its entry), and the islands it shares code with. */
|
|
4
|
+
export declare function island_ledger(entry: string | null | undefined): {
|
|
5
|
+
row: Ledger["rows"][number];
|
|
6
|
+
shares_with: string[];
|
|
7
|
+
} | null;
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import { byte_ledger } from "./bytes.js";
|
|
2
|
+
import { all_regions, region_name } from "./regions.js";
|
|
3
|
+
import { dynamic_imports, static_imports } from "@ogygia/core/insights";
|
|
4
|
+
import { ISLAND_GRAPH_ATTR, decode_island_graph, tool_fetch, tool_made } from "@ogygia/core/internal/instruments";
|
|
5
|
+
// src/devtools/ledger-dom.ts
|
|
6
|
+
/**
|
|
7
|
+
* The byte ledger's DOM side: the page's island graph (`script[data-ogygia-graph]`, a build writes
|
|
8
|
+
* it), the browser's size of every file it loaded, the regions on the page and the runtime's files
|
|
9
|
+
* — handed to the pure `byte_ledger` (bytes.ts). Null off a build (the dev server writes no graph).
|
|
10
|
+
* Shared by the Bytes tab and an island's detail card.
|
|
11
|
+
*/
|
|
12
|
+
const abs = (href) => {
|
|
13
|
+
if (!href) return null;
|
|
14
|
+
try {
|
|
15
|
+
return new URL(href, location.href).href;
|
|
16
|
+
} catch {
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
let parts = null;
|
|
21
|
+
/** globals only the devtools' own code sets (the runtime sets none: it publishes `__ogygia_devtools`
|
|
22
|
+
* alone, from its own chunk, never a root) — a part carrying one is the devtools', not the page's */
|
|
23
|
+
const DOCK_MARKS = [
|
|
24
|
+
"__ogygia_session",
|
|
25
|
+
"__ogygia_testing",
|
|
26
|
+
"__ogygia_region_bytes",
|
|
27
|
+
"__ogygia_region_names",
|
|
28
|
+
"__ogygia_styles",
|
|
29
|
+
"__ogygia_devtools_meta"
|
|
30
|
+
];
|
|
31
|
+
function runtime_parts(src) {
|
|
32
|
+
if (parts?.src !== src) {
|
|
33
|
+
parts = {
|
|
34
|
+
src,
|
|
35
|
+
read: null
|
|
36
|
+
};
|
|
37
|
+
read_parts(src).then((read) => {
|
|
38
|
+
if (parts?.src === src) parts.read = read;
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
return parts.read;
|
|
42
|
+
}
|
|
43
|
+
async function read_parts(src) {
|
|
44
|
+
const cache = /* @__PURE__ */ new Map();
|
|
45
|
+
const text = (u) => {
|
|
46
|
+
let t = cache.get(u);
|
|
47
|
+
if (!t) {
|
|
48
|
+
t = tool_fetch(u, { cache: "force-cache" }).then((r) => r.ok ? r.text() : "").catch(() => "");
|
|
49
|
+
cache.set(u, t);
|
|
50
|
+
}
|
|
51
|
+
return t;
|
|
52
|
+
};
|
|
53
|
+
const self = import.meta.url;
|
|
54
|
+
const roots = /* @__PURE__ */ new Map();
|
|
55
|
+
const dock = [];
|
|
56
|
+
for (const spec of dynamic_imports(await text(src))) {
|
|
57
|
+
const root = abs_from(spec, src);
|
|
58
|
+
if (!root || roots.has(root) || dock.includes(root)) continue;
|
|
59
|
+
const closure = /* @__PURE__ */ new Set([root]);
|
|
60
|
+
const queue = [root];
|
|
61
|
+
let docked = false;
|
|
62
|
+
while (queue.length && closure.size < 300) {
|
|
63
|
+
const at = queue.shift();
|
|
64
|
+
const body = await text(at);
|
|
65
|
+
if (!docked && DOCK_MARKS.some((m) => body.includes(m))) docked = true;
|
|
66
|
+
for (const s of static_imports(body)) {
|
|
67
|
+
const u = abs_from(s, at);
|
|
68
|
+
if (u && !closure.has(u)) {
|
|
69
|
+
closure.add(u);
|
|
70
|
+
queue.push(u);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
if (docked || closure.has(self)) dock.push(root);
|
|
75
|
+
else roots.set(root, closure);
|
|
76
|
+
}
|
|
77
|
+
return {
|
|
78
|
+
roots,
|
|
79
|
+
dock
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
const abs_from = (spec, base) => {
|
|
83
|
+
try {
|
|
84
|
+
return new URL(spec, base).href;
|
|
85
|
+
} catch {
|
|
86
|
+
return null;
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
function page_ledger() {
|
|
90
|
+
if (typeof document === "undefined") return null;
|
|
91
|
+
const graph = /* @__PURE__ */ new Map();
|
|
92
|
+
for (const s of document.querySelectorAll(`script[${ISLAND_GRAPH_ATTR}]`)) for (const [entry, hrefs] of decode_island_graph(s.textContent ?? "")) {
|
|
93
|
+
const key = abs(entry);
|
|
94
|
+
if (key && !graph.has(key)) graph.set(key, hrefs.map(abs).filter((x) => !!x));
|
|
95
|
+
}
|
|
96
|
+
if (!graph.size) return null;
|
|
97
|
+
const sizes = /* @__PURE__ */ new Map();
|
|
98
|
+
for (const r of performance.getEntriesByType("resource")) {
|
|
99
|
+
if (tool_made(r)) continue;
|
|
100
|
+
const wire = r.encodedBodySize || r.transferSize || r.decodedBodySize || 0;
|
|
101
|
+
sizes.set(r.name, {
|
|
102
|
+
wire,
|
|
103
|
+
raw: r.decodedBodySize || wire
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
const islands = /* @__PURE__ */ new Map();
|
|
107
|
+
for (const r of all_regions()) {
|
|
108
|
+
const entry = abs(r.entry);
|
|
109
|
+
if (!entry) continue;
|
|
110
|
+
const g = islands.get(entry);
|
|
111
|
+
const file = abs(r.src);
|
|
112
|
+
if (g) g.count++;
|
|
113
|
+
else islands.set(entry, {
|
|
114
|
+
entry,
|
|
115
|
+
...file ? { file } : {},
|
|
116
|
+
name: region_name(r.entry),
|
|
117
|
+
kind: r.kind,
|
|
118
|
+
wake: r.wake,
|
|
119
|
+
count: 1
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
const runtime_src = abs(document.querySelector("script[data-ogygia-runtime]")?.getAttribute("src"));
|
|
123
|
+
const runtime = [runtime_src, ...[...document.querySelectorAll("link[data-ogygia-runtime-dep]")].map((l) => abs(l.getAttribute("href")))].filter((x) => !!x);
|
|
124
|
+
const rp = runtime_src ? runtime_parts(runtime_src) : null;
|
|
125
|
+
if (rp) {
|
|
126
|
+
const island_files = /* @__PURE__ */ new Set();
|
|
127
|
+
for (const list of graph.values()) for (const u of list) island_files.add(u);
|
|
128
|
+
for (const i of islands.values()) island_files.add(i.file ?? i.entry);
|
|
129
|
+
for (const [root, closure] of rp.roots) if (sizes.has(root)) {
|
|
130
|
+
for (const u of closure) if (sizes.has(u) && !island_files.has(u) && !runtime.includes(u)) runtime.push(u);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
return byte_ledger([...islands.values()], graph, (u) => sizes.get(u) ?? null, runtime);
|
|
134
|
+
}
|
|
135
|
+
/** One island's line of the ledger (by its entry), and the islands it shares code with. */
|
|
136
|
+
function island_ledger(entry) {
|
|
137
|
+
const l = page_ledger();
|
|
138
|
+
const key = abs(entry);
|
|
139
|
+
if (!l || !key) return null;
|
|
140
|
+
const row = l.rows.find((r) => r.entry === key);
|
|
141
|
+
if (!row) return null;
|
|
142
|
+
const with_ = /* @__PURE__ */ new Set();
|
|
143
|
+
for (const s of l.shared) if (s.users.includes(row.name)) {
|
|
144
|
+
for (const u of s.users) if (u !== row.name) with_.add(u);
|
|
145
|
+
}
|
|
146
|
+
return {
|
|
147
|
+
row,
|
|
148
|
+
shares_with: [...with_]
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
export { island_ledger, page_ledger };
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
export interface LeftoverReg {
|
|
2
|
+
kind: "interval" | "listener";
|
|
3
|
+
/** a listener's target and event */
|
|
4
|
+
target?: "window" | "document";
|
|
5
|
+
type?: string;
|
|
6
|
+
capture?: boolean;
|
|
7
|
+
/** the files of the first calls above the registration (its caller first) */
|
|
8
|
+
frames: string[];
|
|
9
|
+
/** when it was made (performance.now) */
|
|
10
|
+
t: number;
|
|
11
|
+
/** an interval's runs so far, and the last one */
|
|
12
|
+
fires: number;
|
|
13
|
+
last?: number;
|
|
14
|
+
}
|
|
15
|
+
/** One island entry as this tab saw it: where its code lives, its instances, when one last left. */
|
|
16
|
+
export interface LeftoverIsland {
|
|
17
|
+
entry: string;
|
|
18
|
+
/** its file locations (the entry, and the content-hashed file a build loads) as absolute URLs */
|
|
19
|
+
urls: string[];
|
|
20
|
+
/** when each instance seen alive now started (performance.now) */
|
|
21
|
+
live: number[];
|
|
22
|
+
/** when its first instance started, and when one last left */
|
|
23
|
+
first: number;
|
|
24
|
+
gone?: number;
|
|
25
|
+
}
|
|
26
|
+
/** What the report says: per island, what it left behind. */
|
|
27
|
+
export interface Leftover {
|
|
28
|
+
name: string;
|
|
29
|
+
entry: string;
|
|
30
|
+
intervals: number;
|
|
31
|
+
/** `window 'resize'` … */
|
|
32
|
+
listeners: string[];
|
|
33
|
+
/** the intervals' runs since they were set, and how long ago the last one ran */
|
|
34
|
+
fires: number;
|
|
35
|
+
last_ago?: number;
|
|
36
|
+
}
|
|
37
|
+
/** The file URL of each stack line (Chrome `at f (url:1:2)` / `at url:1:2`, Firefox and Safari
|
|
38
|
+
* `f@url:1:2`), without its position or query. No regex: this runs on every watched registration. */
|
|
39
|
+
export declare function stack_files(stack: string): string[];
|
|
40
|
+
/** Watch intervals and the global listeners. Once per page; a no-op off the browser. */
|
|
41
|
+
export declare function watch_leftovers(): void;
|
|
42
|
+
export interface ScrollReg {
|
|
43
|
+
type: string;
|
|
44
|
+
/** `window`, `document`, or the element briefly (`div.scroller`) */
|
|
45
|
+
on: string;
|
|
46
|
+
/** `passive: false` asked for on purpose */
|
|
47
|
+
forced: boolean;
|
|
48
|
+
frames: string[];
|
|
49
|
+
}
|
|
50
|
+
/** The window `unload` listeners still attached, as scroll registrations (the same owner rule). */
|
|
51
|
+
export declare function unload_listeners(): ScrollReg[];
|
|
52
|
+
/** The scroll-holding listeners still attached (an element's, while it is in the page). */
|
|
53
|
+
export declare function scroll_listeners(): ScrollReg[];
|
|
54
|
+
/** Each one's owner, the island whose file is the nearest of its callers (as owned_leftovers
|
|
55
|
+
* decides), else the first caller's file. PURE. */
|
|
56
|
+
export declare function owned_scroll_listeners(regs: ScrollReg[], isls: LeftoverIsland[], name_of: (entry: string) => string): {
|
|
57
|
+
owner: string;
|
|
58
|
+
type: string;
|
|
59
|
+
on: string;
|
|
60
|
+
forced: boolean;
|
|
61
|
+
}[];
|
|
62
|
+
/** An island instance starts (its hydrate begins): `entry` and its file locations. */
|
|
63
|
+
export declare function leftover_island_seen(el: Element, entry: string, urls: string[]): void;
|
|
64
|
+
/** An island instance left the page (its region disconnected and its app was disposed). */
|
|
65
|
+
export declare function leftover_island_gone(el: Element, entry: string): void;
|
|
66
|
+
/** Everything watched, as it stands now (for `owned_leftovers`). */
|
|
67
|
+
export declare function leftover_state(): {
|
|
68
|
+
regs: LeftoverReg[];
|
|
69
|
+
islands: LeftoverIsland[];
|
|
70
|
+
};
|
|
71
|
+
/**
|
|
72
|
+
* Per island, what it left behind (PURE). A registration's owner is the island whose file is one of
|
|
73
|
+
* its first two callers: by location (a build's file, the entry), else by name (dev serves the
|
|
74
|
+
* component's own `.svelte` file, named as the island is). Left behind: an instance of that island
|
|
75
|
+
* was alive before it was made, and every instance alive now started after it.
|
|
76
|
+
*/
|
|
77
|
+
export declare function owned_leftovers(regs: LeftoverReg[], isls: LeftoverIsland[], name_of: (entry: string) => string, now: number): Leftover[];
|