@ogygia/devtools 0.0.0-stage → 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.
Files changed (310) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +25 -2
  3. package/dist/client.d.ts +3 -0
  4. package/dist/client.js +24 -0
  5. package/dist/devtools/BoundaryOverlay.svelte +396 -0
  6. package/dist/devtools/BoundaryOverlay.svelte.d.ts +19 -0
  7. package/dist/devtools/Devtools.svelte +663 -0
  8. package/dist/devtools/Devtools.svelte.d.ts +15 -0
  9. package/dist/devtools/HubTab.svelte +206 -0
  10. package/dist/devtools/HubTab.svelte.d.ts +11 -0
  11. package/dist/devtools/HydrationTab.svelte +237 -0
  12. package/dist/devtools/HydrationTab.svelte.d.ts +13 -0
  13. package/dist/devtools/IslandDetail.svelte +615 -0
  14. package/dist/devtools/IslandDetail.svelte.d.ts +15 -0
  15. package/dist/devtools/LedgerTab.svelte +287 -0
  16. package/dist/devtools/LedgerTab.svelte.d.ts +11 -0
  17. package/dist/devtools/LensTab.svelte +290 -0
  18. package/dist/devtools/LensTab.svelte.d.ts +19 -0
  19. package/dist/devtools/NavTab.svelte +229 -0
  20. package/dist/devtools/NavTab.svelte.d.ts +11 -0
  21. package/dist/devtools/PageTab.svelte +1122 -0
  22. package/dist/devtools/PageTab.svelte.d.ts +15 -0
  23. package/dist/devtools/ProfilerTab.svelte +755 -0
  24. package/dist/devtools/ProfilerTab.svelte.d.ts +15 -0
  25. package/dist/devtools/PropsTree.svelte +155 -0
  26. package/dist/devtools/PropsTree.svelte.d.ts +15 -0
  27. package/dist/devtools/SessionTab.svelte +796 -0
  28. package/dist/devtools/SessionTab.svelte.d.ts +11 -0
  29. package/dist/devtools/TimelineTab.svelte +267 -0
  30. package/dist/devtools/TimelineTab.svelte.d.ts +11 -0
  31. package/dist/devtools/WireTab.svelte +149 -0
  32. package/dist/devtools/WireTab.svelte.d.ts +11 -0
  33. package/dist/devtools/bytes.d.ts +70 -0
  34. package/dist/devtools/bytes.js +82 -0
  35. package/dist/devtools/cache-headers.d.ts +20 -0
  36. package/dist/devtools/cache-headers.js +81 -0
  37. package/dist/devtools/highlight.d.ts +7 -0
  38. package/dist/devtools/highlight.js +83 -0
  39. package/dist/devtools/hydration.d.ts +48 -0
  40. package/dist/devtools/hydration.js +93 -0
  41. package/dist/devtools/index.d.ts +3 -0
  42. package/dist/devtools/index.js +3 -0
  43. package/dist/devtools/ledger-dom.d.ts +7 -0
  44. package/dist/devtools/ledger-dom.js +151 -0
  45. package/dist/devtools/leftovers.d.ts +77 -0
  46. package/dist/devtools/leftovers.js +319 -0
  47. package/dist/devtools/names.d.ts +1 -0
  48. package/dist/devtools/names.js +34 -0
  49. package/dist/devtools/page.d.ts +96 -0
  50. package/dist/devtools/page.js +957 -0
  51. package/dist/devtools/profile-anchors.d.ts +22 -0
  52. package/dist/devtools/profile-anchors.js +135 -0
  53. package/dist/devtools/profile-store.d.ts +175 -0
  54. package/dist/devtools/profile-store.js +239 -0
  55. package/dist/devtools/regions.d.ts +77 -0
  56. package/dist/devtools/regions.js +132 -0
  57. package/dist/devtools/session.d.ts +31 -0
  58. package/dist/devtools/session.js +455 -0
  59. package/dist/devtools/since-load.d.ts +50 -0
  60. package/dist/devtools/since-load.js +89 -0
  61. package/dist/devtools/source-lines.d.ts +24 -0
  62. package/dist/devtools/source-lines.js +106 -0
  63. package/dist/devtools/styles.d.ts +52 -0
  64. package/dist/devtools/styles.js +248 -0
  65. package/dist/devtools/testing-engine.d.ts +83 -0
  66. package/dist/devtools/testing-engine.js +220 -0
  67. package/dist/devtools/ui-app.d.ts +5 -0
  68. package/dist/devtools/ui-app.js +69 -0
  69. package/dist/devtools/ui.d.ts +50 -0
  70. package/dist/devtools/ui.js +175 -0
  71. package/dist/profiler/alloc.d.ts +75 -0
  72. package/dist/profiler/alloc.js +76 -0
  73. package/dist/profiler/analyze.d.ts +437 -0
  74. package/dist/profiler/analyze.js +1941 -0
  75. package/dist/profiler/app-path.d.ts +7 -0
  76. package/dist/profiler/app-path.js +26 -0
  77. package/dist/profiler/async-io.d.ts +58 -0
  78. package/dist/profiler/async-io.js +187 -0
  79. package/dist/profiler/beacon-standalone.d.ts +4 -0
  80. package/dist/profiler/beacon-standalone.js +409 -0
  81. package/dist/profiler/browser-findings.d.ts +25 -0
  82. package/dist/profiler/browser-findings.js +260 -0
  83. package/dist/profiler/byte-strip.d.ts +58 -0
  84. package/dist/profiler/byte-strip.js +228 -0
  85. package/dist/profiler/client-files.d.ts +16 -0
  86. package/dist/profiler/client-files.js +60 -0
  87. package/dist/profiler/client-windows.d.ts +49 -0
  88. package/dist/profiler/client-windows.js +95 -0
  89. package/dist/profiler/client.d.ts +40 -0
  90. package/dist/profiler/client.js +82 -0
  91. package/dist/profiler/compare.d.ts +308 -0
  92. package/dist/profiler/compare.js +733 -0
  93. package/dist/profiler/compress.d.ts +10 -0
  94. package/dist/profiler/compress.js +36 -0
  95. package/dist/profiler/contention.d.ts +60 -0
  96. package/dist/profiler/contention.js +86 -0
  97. package/dist/profiler/crypto.d.ts +19 -0
  98. package/dist/profiler/crypto.js +101 -0
  99. package/dist/profiler/doc-diff.d.ts +47 -0
  100. package/dist/profiler/doc-diff.js +214 -0
  101. package/dist/profiler/dom-diff.d.ts +43 -0
  102. package/dist/profiler/dom-diff.js +121 -0
  103. package/dist/profiler/drill.d.ts +89 -0
  104. package/dist/profiler/drill.js +553 -0
  105. package/dist/profiler/embedded-maps.d.ts +37 -0
  106. package/dist/profiler/embedded-maps.js +135 -0
  107. package/dist/profiler/env.d.ts +6 -0
  108. package/dist/profiler/env.js +10 -0
  109. package/dist/profiler/forecast.d.ts +76 -0
  110. package/dist/profiler/forecast.js +225 -0
  111. package/dist/profiler/frames.d.ts +11 -0
  112. package/dist/profiler/frames.js +42 -0
  113. package/dist/profiler/gc.d.ts +144 -0
  114. package/dist/profiler/gc.js +271 -0
  115. package/dist/profiler/hole-slots.d.ts +31 -0
  116. package/dist/profiler/hole-slots.js +90 -0
  117. package/dist/profiler/index.d.ts +288 -0
  118. package/dist/profiler/index.js +4921 -0
  119. package/dist/profiler/insights.d.ts +96 -0
  120. package/dist/profiler/insights.js +239 -0
  121. package/dist/profiler/ledger.d.ts +121 -0
  122. package/dist/profiler/ledger.js +440 -0
  123. package/dist/profiler/lineage.d.ts +81 -0
  124. package/dist/profiler/lineage.js +359 -0
  125. package/dist/profiler/module-map.d.ts +12 -0
  126. package/dist/profiler/module-map.js +94 -0
  127. package/dist/profiler/net.d.ts +169 -0
  128. package/dist/profiler/net.js +586 -0
  129. package/dist/profiler/on-demand.d.ts +11 -0
  130. package/dist/profiler/on-demand.js +15 -0
  131. package/dist/profiler/own-requests.d.ts +9 -0
  132. package/dist/profiler/own-requests.js +17 -0
  133. package/dist/profiler/patterns.d.ts +476 -0
  134. package/dist/profiler/patterns.js +2203 -0
  135. package/dist/profiler/profiler-router.d.ts +245 -0
  136. package/dist/profiler/profiler-router.js +113 -0
  137. package/dist/profiler/refresh-stub.d.ts +2 -0
  138. package/dist/profiler/refresh-stub.js +37 -0
  139. package/dist/profiler/report.d.ts +1404 -0
  140. package/dist/profiler/report.js +2638 -0
  141. package/dist/profiler/river.d.ts +65 -0
  142. package/dist/profiler/river.js +298 -0
  143. package/dist/profiler/route-files.d.ts +22 -0
  144. package/dist/profiler/route-files.js +55 -0
  145. package/dist/profiler/route-imports.d.ts +14 -0
  146. package/dist/profiler/route-imports.js +83 -0
  147. package/dist/profiler/saving.d.ts +22 -0
  148. package/dist/profiler/saving.js +42 -0
  149. package/dist/profiler/score.d.ts +109 -0
  150. package/dist/profiler/score.js +261 -0
  151. package/dist/profiler/session-cookie.d.ts +28 -0
  152. package/dist/profiler/session-cookie.js +45 -0
  153. package/dist/profiler/sink.d.ts +81 -0
  154. package/dist/profiler/sink.js +124 -0
  155. package/dist/profiler/site-fixes.d.ts +25 -0
  156. package/dist/profiler/site-fixes.js +62 -0
  157. package/dist/profiler/source-scan.d.ts +210 -0
  158. package/dist/profiler/source-scan.js +1724 -0
  159. package/dist/profiler/span.d.ts +90 -0
  160. package/dist/profiler/span.js +109 -0
  161. package/dist/profiler/stacks.d.ts +43 -0
  162. package/dist/profiler/stacks.js +107 -0
  163. package/dist/profiler/standalone.d.ts +27 -0
  164. package/dist/profiler/standalone.js +98 -0
  165. package/dist/profiler/steps.d.ts +39 -0
  166. package/dist/profiler/steps.js +74 -0
  167. package/dist/profiler/storage/index.d.ts +87 -0
  168. package/dist/profiler/storage/index.js +76 -0
  169. package/dist/profiler/storage/postgres.d.ts +12 -0
  170. package/dist/profiler/storage/postgres.js +94 -0
  171. package/dist/profiler/storage/redis.d.ts +19 -0
  172. package/dist/profiler/storage/redis.js +148 -0
  173. package/dist/profiler/storage/sqlite.d.ts +2 -0
  174. package/dist/profiler/storage/sqlite.js +85 -0
  175. package/dist/profiler/storage/visits.d.ts +4 -0
  176. package/dist/profiler/storage/visits.js +6 -0
  177. package/dist/profiler/stream-tail.d.ts +26 -0
  178. package/dist/profiler/stream-tail.js +74 -0
  179. package/dist/profiler/timeline.d.ts +279 -0
  180. package/dist/profiler/timeline.js +690 -0
  181. package/dist/profiler/ui/AllocStrip.svelte +146 -0
  182. package/dist/profiler/ui/AllocStrip.svelte.d.ts +17 -0
  183. package/dist/profiler/ui/Awaits.svelte +363 -0
  184. package/dist/profiler/ui/Awaits.svelte.d.ts +16 -0
  185. package/dist/profiler/ui/ByteStrip.svelte +182 -0
  186. package/dist/profiler/ui/ByteStrip.svelte.d.ts +18 -0
  187. package/dist/profiler/ui/Compare.svelte +24 -0
  188. package/dist/profiler/ui/Compare.svelte.d.ts +16 -0
  189. package/dist/profiler/ui/CompareBody.svelte +429 -0
  190. package/dist/profiler/ui/CompareBody.svelte.d.ts +8 -0
  191. package/dist/profiler/ui/ComponentsTable.svelte +265 -0
  192. package/dist/profiler/ui/ComponentsTable.svelte.d.ts +20 -0
  193. package/dist/profiler/ui/Dashboard.svelte +492 -0
  194. package/dist/profiler/ui/Dashboard.svelte.d.ts +31 -0
  195. package/dist/profiler/ui/DataRiver.svelte +206 -0
  196. package/dist/profiler/ui/DataRiver.svelte.d.ts +13 -0
  197. package/dist/profiler/ui/DomTravel.svelte +207 -0
  198. package/dist/profiler/ui/DomTravel.svelte.d.ts +9 -0
  199. package/dist/profiler/ui/DownloadHtml.svelte +52 -0
  200. package/dist/profiler/ui/DownloadHtml.svelte.d.ts +10 -0
  201. package/dist/profiler/ui/Drill.svelte +276 -0
  202. package/dist/profiler/ui/Drill.svelte.d.ts +12 -0
  203. package/dist/profiler/ui/ExportButton.svelte +59 -0
  204. package/dist/profiler/ui/ExportButton.svelte.d.ts +8 -0
  205. package/dist/profiler/ui/Flame.svelte +222 -0
  206. package/dist/profiler/ui/Flame.svelte.d.ts +12 -0
  207. package/dist/profiler/ui/FrameDetails.svelte +308 -0
  208. package/dist/profiler/ui/FrameDetails.svelte.d.ts +16 -0
  209. package/dist/profiler/ui/FunctionsTable.svelte +156 -0
  210. package/dist/profiler/ui/FunctionsTable.svelte.d.ts +17 -0
  211. package/dist/profiler/ui/IslandHeatmap.svelte +151 -0
  212. package/dist/profiler/ui/IslandHeatmap.svelte.d.ts +16 -0
  213. package/dist/profiler/ui/IslandsTable.svelte +448 -0
  214. package/dist/profiler/ui/IslandsTable.svelte.d.ts +16 -0
  215. package/dist/profiler/ui/KeepLocal.svelte +46 -0
  216. package/dist/profiler/ui/KeepLocal.svelte.d.ts +8 -0
  217. package/dist/profiler/ui/Ledger.svelte +196 -0
  218. package/dist/profiler/ui/Ledger.svelte.d.ts +18 -0
  219. package/dist/profiler/ui/LocalCompare.svelte +48 -0
  220. package/dist/profiler/ui/LocalCompare.svelte.d.ts +8 -0
  221. package/dist/profiler/ui/LocalReports.svelte +68 -0
  222. package/dist/profiler/ui/LocalReports.svelte.d.ts +7 -0
  223. package/dist/profiler/ui/Login.svelte +13 -0
  224. package/dist/profiler/ui/Login.svelte.d.ts +12 -0
  225. package/dist/profiler/ui/LoginForm.svelte +42 -0
  226. package/dist/profiler/ui/LoginForm.svelte.d.ts +7 -0
  227. package/dist/profiler/ui/Message.svelte +10 -0
  228. package/dist/profiler/ui/Message.svelte.d.ts +12 -0
  229. package/dist/profiler/ui/OnThisPage.svelte +137 -0
  230. package/dist/profiler/ui/OnThisPage.svelte.d.ts +3 -0
  231. package/dist/profiler/ui/OneClock.svelte +297 -0
  232. package/dist/profiler/ui/OneClock.svelte.d.ts +27 -0
  233. package/dist/profiler/ui/Panel.svelte +10 -0
  234. package/dist/profiler/ui/Panel.svelte.d.ts +16 -0
  235. package/dist/profiler/ui/Paths.svelte +269 -0
  236. package/dist/profiler/ui/Paths.svelte.d.ts +15 -0
  237. package/dist/profiler/ui/Patterns.svelte +364 -0
  238. package/dist/profiler/ui/Patterns.svelte.d.ts +15 -0
  239. package/dist/profiler/ui/PermalinkGate.svelte +143 -0
  240. package/dist/profiler/ui/PermalinkGate.svelte.d.ts +10 -0
  241. package/dist/profiler/ui/RenderSteps.svelte +218 -0
  242. package/dist/profiler/ui/RenderSteps.svelte.d.ts +13 -0
  243. package/dist/profiler/ui/Report.svelte +28 -0
  244. package/dist/profiler/ui/Report.svelte.d.ts +20 -0
  245. package/dist/profiler/ui/ReportBody.svelte +2592 -0
  246. package/dist/profiler/ui/ReportBody.svelte.d.ts +21 -0
  247. package/dist/profiler/ui/Run.svelte +17 -0
  248. package/dist/profiler/ui/Run.svelte.d.ts +14 -0
  249. package/dist/profiler/ui/RunView.svelte +146 -0
  250. package/dist/profiler/ui/RunView.svelte.d.ts +10 -0
  251. package/dist/profiler/ui/ScoreCard.svelte +197 -0
  252. package/dist/profiler/ui/ScoreCard.svelte.d.ts +7 -0
  253. package/dist/profiler/ui/Scrub.svelte +293 -0
  254. package/dist/profiler/ui/Scrub.svelte.d.ts +18 -0
  255. package/dist/profiler/ui/SeedExplainer.svelte +193 -0
  256. package/dist/profiler/ui/SeedExplainer.svelte.d.ts +16 -0
  257. package/dist/profiler/ui/ShareLink.svelte +148 -0
  258. package/dist/profiler/ui/ShareLink.svelte.d.ts +8 -0
  259. package/dist/profiler/ui/Shell.svelte +50 -0
  260. package/dist/profiler/ui/Shell.svelte.d.ts +27 -0
  261. package/dist/profiler/ui/Sidebar.svelte +312 -0
  262. package/dist/profiler/ui/Sidebar.svelte.d.ts +7 -0
  263. package/dist/profiler/ui/Site.svelte +20 -0
  264. package/dist/profiler/ui/Site.svelte.d.ts +23 -0
  265. package/dist/profiler/ui/SiteView.svelte +385 -0
  266. package/dist/profiler/ui/SiteView.svelte.d.ts +27 -0
  267. package/dist/profiler/ui/ThemeSwitch.svelte +78 -0
  268. package/dist/profiler/ui/ThemeSwitch.svelte.d.ts +3 -0
  269. package/dist/profiler/ui/Timeline.svelte +672 -0
  270. package/dist/profiler/ui/Timeline.svelte.d.ts +22 -0
  271. package/dist/profiler/ui/Treemap.svelte +278 -0
  272. package/dist/profiler/ui/Treemap.svelte.d.ts +20 -0
  273. package/dist/profiler/ui/Upload.svelte +19 -0
  274. package/dist/profiler/ui/Upload.svelte.d.ts +10 -0
  275. package/dist/profiler/ui/UploadForm.svelte +59 -0
  276. package/dist/profiler/ui/UploadForm.svelte.d.ts +6 -0
  277. package/dist/profiler/ui/WaitingTable.svelte +55 -0
  278. package/dist/profiler/ui/WaitingTable.svelte.d.ts +14 -0
  279. package/dist/profiler/ui/Waterfall.svelte +371 -0
  280. package/dist/profiler/ui/Waterfall.svelte.d.ts +7 -0
  281. package/dist/profiler/ui/WhatIf.svelte +87 -0
  282. package/dist/profiler/ui/WhatIf.svelte.d.ts +18 -0
  283. package/dist/profiler/ui/fake-progress.svelte.d.ts +15 -0
  284. package/dist/profiler/ui/fake-progress.svelte.js +38 -0
  285. package/dist/profiler/ui/format.d.ts +20 -0
  286. package/dist/profiler/ui/format.js +71 -0
  287. package/dist/profiler/ui/permalink.d.ts +9 -0
  288. package/dist/profiler/ui/permalink.js +108 -0
  289. package/dist/profiler/ui/press.d.ts +5 -0
  290. package/dist/profiler/ui/press.js +19 -0
  291. package/dist/profiler/ui/profiler.css +650 -0
  292. package/dist/profiler/ui/report-data.d.ts +189 -0
  293. package/dist/profiler/ui/report-data.js +393 -0
  294. package/dist/profiler/ui/report-request.d.ts +6 -0
  295. package/dist/profiler/ui/report-request.js +50 -0
  296. package/dist/profiler/ui/row-anchor.svelte.d.ts +12 -0
  297. package/dist/profiler/ui/row-anchor.svelte.js +44 -0
  298. package/dist/profiler/ui/sort.svelte.d.ts +14 -0
  299. package/dist/profiler/ui/sort.svelte.js +37 -0
  300. package/dist/profiler/ui/store.d.ts +45 -0
  301. package/dist/profiler/ui/store.js +92 -0
  302. package/dist/profiler/visit.d.ts +389 -0
  303. package/dist/profiler/visit.js +1002 -0
  304. package/dist/report.d.ts +5 -0
  305. package/dist/report.js +4 -0
  306. package/dist/server.d.ts +11 -0
  307. package/dist/server.js +38 -0
  308. package/dist/vite.d.ts +12 -0
  309. package/dist/vite.js +78 -0
  310. package/package.json +103 -3
@@ -0,0 +1,25 @@
1
+ import { ClientWindows, InteractionCpu } from "./client-windows.js";
2
+ import { Visit } from "./visit.js";
3
+ import { HeldOpen, PageReport } from "@ogygia/core/insights";
4
+ export interface BrowserFinding {
5
+ severity: "info" | "warn";
6
+ code: string;
7
+ message: string;
8
+ fix?: string;
9
+ /** the islands it is about (the devtools light them up on the page) */
10
+ fps: string[];
11
+ }
12
+ /** The Page tab's report of one visit. `rows`: the profiled render's islands (fp, entry, name);
13
+ * `windows`: the browser's CPU cut by island (the findings then name the function behind a cost). */
14
+ export declare function browser_page_report(visit: Visit, rows: readonly {
15
+ fp: string;
16
+ entry: string;
17
+ name: string;
18
+ own_bytes?: number;
19
+ }[], windows?: ClientWindows, third?: {
20
+ origin: string;
21
+ named?: string[];
22
+ by_host: Map<string, number> | null;
23
+ }, hole_name?: (id: string, p?: string) => string, hole_server?: (id: string, p?: string) => number | undefined, interaction_cpu?: InteractionCpu, dev?: boolean, held_open?: HeldOpen): PageReport | null;
24
+ /** The report's share of them: the codes it does not already make, severities in its two levels. */
25
+ export declare function browser_findings(report: PageReport | null): BrowserFinding[];
@@ -0,0 +1,260 @@
1
+ import { analyze_page, browser_of } from "@ogygia/core/insights";
2
+ // src/profiler/browser-findings.ts
3
+ /**
4
+ * THE BROWSER'S FINDINGS IN THE REPORT — the devtools Page tab's brain (insights/page-insights.ts)
5
+ * run on the visit the beacon sent, so the report and the Page tab say the same thing about the same
6
+ * load: clicked before it woke, hydrating moved the layout, one long hydrate, waited for its turn,
7
+ * eager below the fold, woke late, the largest paint repainted, a failed island with its error.
8
+ *
9
+ * The regions come from the visit itself (where each sat on the page, how it wakes, whether it
10
+ * woke); their names from the profiled render's island rows, joined by fingerprint, then by entry.
11
+ * Findings the report already makes its own way are left to it: a recovery (hydration-mismatch),
12
+ * an island that never woke (never-hydrated), the vitals, the render-blocking files.
13
+ */
14
+ /** codes the report makes from its own data (render-blocking only when it weighed the page's files:
15
+ * the report.ts caller drops the browser's then — on a dev server it weighs nothing, and the
16
+ * browser's timing of the blocking file is all there is) */
17
+ const SKIP = /* @__PURE__ */ new Set([
18
+ "recovered",
19
+ "never-woke",
20
+ "vital-ttfb",
21
+ "vital-fcp",
22
+ "vital-lcp",
23
+ "vital-cls",
24
+ "vital-inp"
25
+ ]);
26
+ /** `og-region.<id>.<hash>.js` → `og-region.<id>.js` (a location's identity); anything else as is. */
27
+ function identity_file(file) {
28
+ if (!file.startsWith("og-region.")) return file;
29
+ const parts = file.split(".");
30
+ return parts.length === 4 ? `${parts[0]}.${parts[1]}.${parts[3]}` : file;
31
+ }
32
+ function name_from_entry(entry) {
33
+ const q = entry.indexOf("?");
34
+ const path = q === -1 ? entry : entry.slice(0, q);
35
+ const base = path.slice(path.lastIndexOf("/") + 1);
36
+ return base.endsWith(".svelte") ? base.slice(0, -7) : base || entry;
37
+ }
38
+ const kind_of = (c) => c === "component" || c === "app" ? "app" : c === "dependency" ? "dependency" : c === "svelte" ? "svelte" : "browser";
39
+ const as_fn = (f) => ({
40
+ name: f.name,
41
+ file: f.file,
42
+ line: f.line,
43
+ kind: kind_of(f.category),
44
+ self_ms: f.self_ms,
45
+ total_ms: f.total_ms
46
+ });
47
+ /** The browser's CPU cut by island, in the shape the Page tab's analysis quotes from. */
48
+ function cpu_summary(w) {
49
+ const islands = {};
50
+ for (const [fp, v] of Object.entries(w.islands)) islands[fp] = {
51
+ ms: v.ms,
52
+ top: v.top.map(as_fn)
53
+ };
54
+ return {
55
+ window_ms: 0,
56
+ busy_ms: 0,
57
+ by_kind: [],
58
+ fns: [],
59
+ files: [],
60
+ islands,
61
+ outside: {
62
+ ms: w.outside.ms,
63
+ top: w.outside.top.map(as_fn)
64
+ },
65
+ interval_ms: 0
66
+ };
67
+ }
68
+ /** The Page tab's report of one visit. `rows`: the profiled render's islands (fp, entry, name);
69
+ * `windows`: the browser's CPU cut by island (the findings then name the function behind a cost). */
70
+ function browser_page_report(visit, rows, windows, third, hole_name, hole_server, interaction_cpu, dev, held_open) {
71
+ if (!visit.regions?.length && !visit.islands.length && !visit.holes_failed?.length && !visit.holes_answered?.length && !visit.hole_batches?.length && !visit.restores?.length && !visit.preload_misses?.length && !visit.entry_fallbacks?.length && !visit.refetched?.length && !visit.navs?.length && !visit.shifts?.length && !visit.interaction && !visit.paints?.lcp && !visit.dom && !visit.images_oversized?.length && !visit.font_faces?.length && !visit.preloads_unused?.length && !visit.images_eager_below?.length && !visit.forced_layout?.length && !visit.images_unsized?.length && !visit.scroll_jank?.length && !visit.uncompressed?.length && !visit.nav.raw) return null;
72
+ const by_fp = new Map(rows.map((r) => [r.fp, r.name]));
73
+ const by_entry = new Map(rows.map((r) => [r.entry, r.name]));
74
+ const file_of = (entry) => {
75
+ const q = entry.indexOf("?");
76
+ const p = q === -1 ? entry : entry.slice(0, q);
77
+ return p.slice(p.lastIndexOf("/") + 1);
78
+ };
79
+ const by_file = new Map(rows.map((r) => [file_of(r.entry), r.name]));
80
+ const named = (entry) => by_entry.get(entry) ?? by_file.get(file_of(entry)) ?? name_from_entry(entry);
81
+ const name_of = (fp, entry) => by_fp.get(fp) ?? (entry ? named(entry) : fp.slice(0, 8));
82
+ const regions = (visit.regions ?? []).map((r) => {
83
+ const kind = r.defer ? "hole" : r.wake === "none" ? "lake" : "island";
84
+ const own = kind === "island" ? (rows.find((x) => x.fp === r.fp) ?? (r.entry ? rows.find((x) => x.entry === r.entry) : void 0))?.own_bytes : void 0;
85
+ return {
86
+ fp: r.fp,
87
+ name: name_of(r.fp, r.entry),
88
+ kind,
89
+ wake: r.wake || (kind === "hole" ? "fetch" : "load"),
90
+ hydrated: !!r.hydrated,
91
+ top: r.top,
92
+ height: r.height,
93
+ ...own ? { own_bytes: own } : {},
94
+ ...kind === "island" && r.hidden ? {
95
+ hidden: true,
96
+ ...r.headless ? { headless: true } : {}
97
+ } : {}
98
+ };
99
+ });
100
+ const known = new Set(regions.map((r) => r.fp));
101
+ for (const i of visit.islands) if (!known.has(i.fp)) {
102
+ known.add(i.fp);
103
+ regions.push({
104
+ fp: i.fp,
105
+ name: name_of(i.fp, i.entry),
106
+ kind: "island",
107
+ wake: rows.find((r) => r.fp === i.fp) ? "load" : "",
108
+ hydrated: true
109
+ });
110
+ }
111
+ const failures = (visit.regions ?? []).filter((r) => r.failed !== void 0).map((r) => ({
112
+ fp: r.fp,
113
+ message: r.failed || "it threw",
114
+ ...r.failed_span ? { span: r.failed_span } : {},
115
+ ...r.failed_turn !== void 0 ? { turn: r.failed_turn } : {}
116
+ }));
117
+ const span = (s) => s ? {
118
+ ms: s.ms,
119
+ top: s.top.map(as_fn)
120
+ } : null;
121
+ const browser = browser_of(visit.ua);
122
+ const input = {
123
+ ...dev ? { dev: true } : {},
124
+ ...browser ? { browser } : {},
125
+ ...visit.dock_open_at !== void 0 ? { dock_open_at: visit.dock_open_at } : {},
126
+ ...held_open ? { held_open } : {},
127
+ ...interaction_cpu ? { interaction_cpu: {
128
+ t: interaction_cpu.t,
129
+ mapped: interaction_cpu.mapped === true,
130
+ wait: span(interaction_cpu.wait),
131
+ handler: span(interaction_cpu.handler)
132
+ } } : {},
133
+ vitals: visit.vitals ?? {},
134
+ visit: {
135
+ nav: visit.nav.start_hidden ? (({ req_start: _q, res_start: _s, res_end, ...rest }) => res_end > 0 ? {
136
+ ...rest,
137
+ res_end
138
+ } : rest)(visit.nav) : visit.nav,
139
+ paints: visit.paints,
140
+ resources: visit.resources,
141
+ resource_totals: visit.resource_totals,
142
+ viewport: visit.viewport,
143
+ ...visit.scripts ? { scripts: visit.scripts } : {},
144
+ ...visit.warnings ? { warnings: visit.warnings } : {},
145
+ ...visit.preload_misses ? { preload_misses: visit.preload_misses } : {},
146
+ ...visit.font_faces ? { font_faces: visit.font_faces } : {},
147
+ ...visit.images_oversized ? { images_oversized: visit.images_oversized } : {},
148
+ ...visit.dom ? { dom: visit.dom } : {},
149
+ ...visit.preloads_unused ? { preloads_unused: visit.preloads_unused } : {},
150
+ ...visit.uncompressed ? { uncompressed: visit.uncompressed } : {},
151
+ ...visit.images_eager_below ? { images_eager_below: visit.images_eager_below } : {},
152
+ ...visit.images_unsized ? { images_unsized: visit.images_unsized } : {},
153
+ ...visit.forced_layout ? { forced_layout: visit.forced_layout } : {},
154
+ ...visit.scroll_jank ? { scroll_jank: visit.scroll_jank.map((j) => ({
155
+ ...j,
156
+ scripts: j.scripts.map((s) => {
157
+ const own = by_file.get(identity_file(file_of(s.url)));
158
+ return own ? {
159
+ ...s,
160
+ island: own
161
+ } : s;
162
+ })
163
+ })) } : {},
164
+ ...visit.navs ? { navs: visit.navs } : {},
165
+ ...visit.entry_fallbacks ? { entry_fallbacks: visit.entry_fallbacks.map((f) => ({
166
+ ...f,
167
+ name: named(f.entry)
168
+ })) } : {},
169
+ ...visit.interaction ? { interaction: {
170
+ ...visit.interaction,
171
+ scripts: visit.interaction.scripts?.map((s) => {
172
+ const own = by_file.get(identity_file(file_of(s.url)));
173
+ return own ? {
174
+ ...s,
175
+ island: own
176
+ } : s;
177
+ })
178
+ } } : {},
179
+ ...visit.refetched ? { refetched: visit.refetched.map((f) => {
180
+ if (!f.entry) return f;
181
+ const own = by_entry.get(f.entry) ?? by_file.get(file_of(f.entry));
182
+ return {
183
+ ...f,
184
+ name: own ?? (f.hole && hole_name ? `the island in ${hole_name(f.hole)}` : named(f.entry))
185
+ };
186
+ }) } : {},
187
+ ...third ? {
188
+ origin: third.origin,
189
+ ...third.named ? { named: third.named } : {}
190
+ } : {}
191
+ },
192
+ islands: visit.islands,
193
+ firsts: visit.firsts,
194
+ shifts: visit.shifts,
195
+ longtasks: visit.longtasks,
196
+ ...visit.longtasks_from ? { longtasks_from: visit.longtasks_from } : {},
197
+ ...visit.shifts_from ? { shifts_from: visit.shifts_from } : {},
198
+ ...visit.holes_failed?.length ? { hole_failures: visit.holes_failed.map((h) => ({
199
+ name: hole_name?.(h.id) ?? `the hole ${h.id}`,
200
+ reason: h.reason,
201
+ ...h.final_path ? { final_url: h.final_path } : {},
202
+ ...h.message ? { message: h.message } : {},
203
+ attempts: h.attempts
204
+ })) } : {},
205
+ ...visit.holes_answered?.length ? { hole_waits: visit.holes_answered.map((h) => {
206
+ const server_ms = hole_server?.(h.id, h.p) ?? h.render;
207
+ const shown_at = Math.max(visit.paints?.fcp ?? 0, h.start);
208
+ return {
209
+ name: hole_name?.(h.id, h.p) ?? `the hole ${h.id}`,
210
+ wait_ms: Math.max(0, Math.round(h.t - shown_at)),
211
+ below_fold: h.below_fold,
212
+ shown_at,
213
+ ...server_ms !== void 0 ? { server_ms } : {},
214
+ ...h.queue !== void 0 ? { server_queue_ms: h.queue } : {},
215
+ ...h.left !== void 0 ? {
216
+ left_at: h.left,
217
+ first_at: h.first,
218
+ end_at: h.end
219
+ } : {},
220
+ ...h.batch !== void 0 ? { batch_size: h.batch } : {}
221
+ };
222
+ }) } : {},
223
+ ...visit.restores?.length ? { restore_events: visit.restores } : {},
224
+ ...visit.hole_batches?.length ? { hole_batches: visit.hole_batches.map((b) => ({
225
+ sent: b.sent,
226
+ delivered: b.delivered,
227
+ status: b.status,
228
+ ...b.refused ? { refused: b.refused } : {},
229
+ ...b.final_url ? { final_url: b.final_url } : {},
230
+ names: b.ids.map((id) => id && hole_name?.(id) || "a hole")
231
+ })) } : {}
232
+ };
233
+ const cpu = windows ? cpu_summary(windows) : null;
234
+ const by_host = third?.by_host ? Object.fromEntries(third.by_host) : void 0;
235
+ return analyze_page(input, regions, failures, Infinity, cpu ? {
236
+ ...cpu,
237
+ by_host
238
+ } : by_host ? {
239
+ ...cpu_summary({
240
+ islands: {},
241
+ outside: {
242
+ ms: 0,
243
+ top: []
244
+ }
245
+ }),
246
+ by_host
247
+ } : null);
248
+ }
249
+ /** The report's share of them: the codes it does not already make, severities in its two levels. */
250
+ function browser_findings(report) {
251
+ if (!report) return [];
252
+ return report.findings.filter((f) => !SKIP.has(f.code)).map((f) => ({
253
+ severity: f.severity === "info" ? "info" : "warn",
254
+ code: f.code,
255
+ message: `In the browser: ${f.message}`,
256
+ ...f.fix ? { fix: f.fix } : {},
257
+ fps: f.fps
258
+ }));
259
+ }
260
+ export { browser_findings, browser_page_report };
@@ -0,0 +1,58 @@
1
+ import { StreamTail } from "./stream-tail.js";
2
+ export type StripKind = "head" | "style" | "css-link" | "runtime" | "script" | "seed" | "remote-seed" | "props" | "holes" | "island" | "lake" | "hole" | "shadow" | "markup";
3
+ export interface StripSegment {
4
+ kind: StripKind;
5
+ start: number;
6
+ end: number;
7
+ /** an island's fingerprint / a script's type — what the report names the segment by */
8
+ label?: string;
9
+ }
10
+ export interface ByteStrip {
11
+ total: number;
12
+ segments: StripSegment[];
13
+ /** bytes per kind over the whole document (shadow DOM counted wherever it sits, islands included) */
14
+ by_kind: Partial<Record<StripKind, number>>;
15
+ /** declarative shadow roots anywhere in the document, bytes — a design system's server render */
16
+ shadow_bytes: number;
17
+ shadow_count: number;
18
+ /** the elements the markup makes: the whole document's, and the islands holding the most */
19
+ elements?: {
20
+ total: number;
21
+ islands: {
22
+ fp: string;
23
+ n: number;
24
+ }[];
25
+ };
26
+ /** the `src` of each `<img>` with no width or height (the first 8): room the browser cannot hold */
27
+ unsized_images?: string[];
28
+ /** when each chunk of the document LEFT the server (end offset, ms after the render began) —
29
+ * present when the page streamed in more than one chunk */
30
+ chunks?: {
31
+ end: number;
32
+ t: number;
33
+ }[];
34
+ /** what held the document open after its early part (stream-tail.ts), when it paused long */
35
+ tail?: StreamTail;
36
+ }
37
+ /** When a byte left the server, from the chunk stamps: the chunk that carried it. */
38
+ export declare function left_at(offset: number, chunks: {
39
+ end: number;
40
+ t: number;
41
+ }[]): number | undefined;
42
+ export declare function byte_strip(html: string): ByteStrip;
43
+ /** The elements markup makes from `start` to `end`: each opening tag once (`walk_tags`). */
44
+ export declare function count_elements(html: string, start: number, end: number): number;
45
+ /** IMAGES THE BROWSER CANNOT HOLD ROOM FOR: each `<img>` with no `width` or no `height` attribute
46
+ * (and not `hidden`): until its file arrives the browser does not know its height, so the page
47
+ * below it moves when it does — unless CSS sizes it. Their `src`s (the first 8), in the order of the
48
+ * page. `lower` is the lowercased document (tags), `raw` the document as sent (to read the `src`). No regex. */
49
+ export declare function unsized_images(lower: string, raw: string): string[];
50
+ /** A segment's arrival at the browser, given the document's download span from navigation timing:
51
+ * bytes are assumed to arrive at a steady rate between the first byte and the last. */
52
+ export declare function arrival_ms(s: {
53
+ start: number;
54
+ end: number;
55
+ }, total: number, res_start: number, res_end: number): {
56
+ first: number;
57
+ last: number;
58
+ };
@@ -0,0 +1,228 @@
1
+ import { scanRegions } from "@ogygia/core/internal/server";
2
+ // src/profiler/byte-strip.ts
3
+ /**
4
+ * THE DOCUMENT AS A BYTE STRIP — what each byte of the HTML the server sent IS: head markup,
5
+ * inline styles, stylesheet links, the runtime bootstrap, other scripts, the page seed, the remote
6
+ * seed, the island props tail, the holes record, each island's markup (named by its fingerprint,
7
+ * the report joins it to the island's row), lakes and holes, declarative shadow DOM a design
8
+ * system rendered, and plain markup. Pure: a string in, ordered segments out. The report draws
9
+ * the strip with the time each byte left the server and the time the browser had it.
10
+ */
11
+ /** When a byte left the server, from the chunk stamps: the chunk that carried it. */
12
+ function left_at(offset, chunks) {
13
+ for (const c of chunks) if (offset < c.end) return c.t;
14
+ return chunks.length ? chunks[chunks.length - 1].t : void 0;
15
+ }
16
+ const SCRIPT_RE = /<script\b([^>]*)>[\s\S]*?<\/script\s*>/gi;
17
+ const STYLE_RE = /<style\b[^>]*>[\s\S]*?<\/style\s*>/gi;
18
+ const LINK_RE = /<link\b[^>]*>/gi;
19
+ const TEMPLATE_RE = /<template\b[^>]*\bshadowrootmode\b[^>]*>[\s\S]*?<\/template\s*>/gi;
20
+ const HEAD_END_RE = /<\/head\s*>/i;
21
+ const REL_STYLESHEET_RE = /\brel\s*=\s*["']?(?:[^"'>\s]*\s)?stylesheet\b/i;
22
+ const FP_ATTR = "data-og-fp";
23
+ function script_kind(attrs) {
24
+ if (/data-ogygia-runtime/.test(attrs)) return { kind: "runtime" };
25
+ if (/data-ogygia-props/.test(attrs)) return { kind: "props" };
26
+ if (/data-ogygia-holes/.test(attrs)) return { kind: "holes" };
27
+ if (/application\/ogygia-page/.test(attrs)) return { kind: "seed" };
28
+ if (/application\/ogygia-remote/.test(attrs)) return { kind: "remote-seed" };
29
+ const type = /\btype\s*=\s*["']?([^"'\s>]+)/i.exec(attrs)?.[1];
30
+ const src = /\bsrc\s*=\s*["']?([^"'\s>]+)/i.exec(attrs)?.[1];
31
+ return {
32
+ kind: "script",
33
+ label: src ? `src ${src.split("/").pop()}` : type ? `type ${type}` : "inline"
34
+ };
35
+ }
36
+ function byte_strip(html) {
37
+ const spans = [];
38
+ let m;
39
+ SCRIPT_RE.lastIndex = 0;
40
+ while (m = SCRIPT_RE.exec(html)) spans.push({
41
+ ...script_kind(m[1]),
42
+ start: m.index,
43
+ end: m.index + m[0].length
44
+ });
45
+ STYLE_RE.lastIndex = 0;
46
+ while (m = STYLE_RE.exec(html)) spans.push({
47
+ kind: "style",
48
+ start: m.index,
49
+ end: m.index + m[0].length
50
+ });
51
+ LINK_RE.lastIndex = 0;
52
+ while (m = LINK_RE.exec(html)) if (REL_STYLESHEET_RE.test(m[0])) spans.push({
53
+ kind: "css-link",
54
+ start: m.index,
55
+ end: m.index + m[0].length,
56
+ label: /\bhref\s*=\s*["']?([^"'\s>]+)/i.exec(m[0])?.[1]?.split("/").pop()
57
+ });
58
+ let shadow_bytes = 0;
59
+ let shadow_count = 0;
60
+ TEMPLATE_RE.lastIndex = 0;
61
+ while (m = TEMPLATE_RE.exec(html)) {
62
+ shadow_bytes += m[0].length;
63
+ shadow_count++;
64
+ spans.push({
65
+ kind: "shadow",
66
+ start: m.index,
67
+ end: m.index + m[0].length
68
+ });
69
+ }
70
+ try {
71
+ for (const r of scanRegions(html)) {
72
+ if (r.depth !== 0) continue;
73
+ const kind = r.kind === "island" ? "island" : r.kind === "lake" ? "lake" : r.kind === "hole" ? "hole" : "markup";
74
+ spans.push({
75
+ kind,
76
+ start: r.start,
77
+ end: r.end,
78
+ label: r.attrs[FP_ATTR] ?? r.attrs.entry ?? r.attrs.name
79
+ });
80
+ }
81
+ } catch {}
82
+ spans.sort((a, b) => a.start - b.start || b.end - a.end);
83
+ const head_end = HEAD_END_RE.exec(html);
84
+ const head_at = head_end ? head_end.index + head_end[0].length : 0;
85
+ const out = [];
86
+ let pos = 0;
87
+ const fill = (to) => {
88
+ if (to <= pos) return;
89
+ if (pos < head_at && to > head_at) {
90
+ out.push({
91
+ kind: "head",
92
+ start: pos,
93
+ end: head_at
94
+ });
95
+ out.push({
96
+ kind: "markup",
97
+ start: head_at,
98
+ end: to
99
+ });
100
+ } else out.push({
101
+ kind: to <= head_at ? "head" : "markup",
102
+ start: pos,
103
+ end: to
104
+ });
105
+ pos = to;
106
+ };
107
+ for (const s of spans) {
108
+ if (s.start < pos) continue;
109
+ fill(s.start);
110
+ out.push(s);
111
+ pos = s.end;
112
+ }
113
+ fill(html.length);
114
+ const by_kind = {};
115
+ for (const s of out) by_kind[s.kind] = (by_kind[s.kind] ?? 0) + (s.end - s.start);
116
+ if (shadow_bytes) by_kind.shadow = shadow_bytes;
117
+ const lower = html.toLowerCase();
118
+ const islands = out.filter((s) => s.kind === "island" && s.label).map((s) => ({
119
+ fp: s.label,
120
+ n: count_elements(lower, s.start, s.end)
121
+ }));
122
+ islands.sort((a, b) => b.n - a.n);
123
+ const unsized = unsized_images(lower, html);
124
+ return {
125
+ total: html.length,
126
+ segments: out,
127
+ by_kind,
128
+ shadow_bytes,
129
+ shadow_count,
130
+ elements: {
131
+ total: count_elements(lower, 0, lower.length),
132
+ islands: islands.slice(0, 3)
133
+ },
134
+ ...unsized.length ? { unsized_images: unsized } : {}
135
+ };
136
+ }
137
+ /** Raw-text elements: their contents are text, not elements. A `<template>`'s contents (a
138
+ * declarative shadow root's too) are not in the document's own elements either. */
139
+ const OPAQUE = [
140
+ "script",
141
+ "style",
142
+ "textarea",
143
+ "title",
144
+ "template",
145
+ "noscript"
146
+ ];
147
+ /** Every opening tag markup makes from `start` to `end`, once: its name and where its `<` is,
148
+ * skipping comments and the contents of raw-text elements and templates. `html` lowercased. No regex. */
149
+ function walk_tags(html, start, end, on_tag) {
150
+ let i = html.indexOf("<", start);
151
+ while (i !== -1 && i < end) {
152
+ const c = html.charCodeAt(i + 1);
153
+ if (c === 33 && html.startsWith("<!--", i)) {
154
+ const e = html.indexOf("-->", i + 4);
155
+ i = e === -1 ? -1 : html.indexOf("<", e + 3);
156
+ continue;
157
+ }
158
+ if (c >= 97 && c <= 122) {
159
+ let j = i + 1;
160
+ while (j < end) {
161
+ const d = html.charCodeAt(j);
162
+ if (!(d >= 97 && d <= 122 || d >= 48 && d <= 57 || d === 45)) break;
163
+ j++;
164
+ }
165
+ const tag = html.slice(i + 1, j);
166
+ on_tag(tag, i);
167
+ if (OPAQUE.includes(tag)) {
168
+ const close = html.indexOf("</" + tag, j);
169
+ i = close === -1 ? -1 : html.indexOf("<", close + 2);
170
+ continue;
171
+ }
172
+ }
173
+ i = html.indexOf("<", i + 1);
174
+ }
175
+ }
176
+ /** The elements markup makes from `start` to `end`: each opening tag once (`walk_tags`). */
177
+ function count_elements(html, start, end) {
178
+ let n = 0;
179
+ walk_tags(html, start, end, () => n++);
180
+ return n;
181
+ }
182
+ /** IMAGES THE BROWSER CANNOT HOLD ROOM FOR: each `<img>` with no `width` or no `height` attribute
183
+ * (and not `hidden`): until its file arrives the browser does not know its height, so the page
184
+ * below it moves when it does — unless CSS sizes it. Their `src`s (the first 8), in the order of the
185
+ * page. `lower` is the lowercased document (tags), `raw` the document as sent (to read the `src`). No regex. */
186
+ function unsized_images(lower, raw) {
187
+ const out = [];
188
+ walk_tags(lower, 0, lower.length, (tag, at) => {
189
+ if (tag !== "img" || out.length >= 8) return;
190
+ const close = lower.indexOf(">", at);
191
+ if (close === -1) return;
192
+ const attrs = lower.slice(at + 4, close);
193
+ const has = (name) => {
194
+ let k = attrs.indexOf(name);
195
+ while (k !== -1) {
196
+ const before = attrs.charCodeAt(k - 1);
197
+ const after = attrs.charCodeAt(k + name.length);
198
+ if ((k === 0 || before === 32 || before === 9 || before === 10 || before === 13) && (after === 61 || after === 32 || after === 62 || Number.isNaN(after))) return true;
199
+ k = attrs.indexOf(name, k + 1);
200
+ }
201
+ return false;
202
+ };
203
+ if (has("width") && has("height") || has("hidden")) return;
204
+ const s = attrs.indexOf("src=");
205
+ if (s === -1) return;
206
+ const q = raw.charAt(at + 4 + s + 4);
207
+ const quoted = q === "\"" || q === "'";
208
+ const from = at + 4 + s + (quoted ? 5 : 4);
209
+ let to = quoted ? raw.indexOf(q, from) : from;
210
+ if (!quoted) while (to < close && raw.charCodeAt(to) > 32 && raw.charCodeAt(to) !== 62) to++;
211
+ out.push(raw.slice(from, to === -1 ? from : to).slice(0, 300));
212
+ });
213
+ return out;
214
+ }
215
+ /** A segment's arrival at the browser, given the document's download span from navigation timing:
216
+ * bytes are assumed to arrive at a steady rate between the first byte and the last. */
217
+ function arrival_ms(s, total, res_start, res_end) {
218
+ if (total <= 0 || res_end <= res_start) return {
219
+ first: res_start,
220
+ last: res_start
221
+ };
222
+ const per = (res_end - res_start) / total;
223
+ return {
224
+ first: res_start + s.start * per,
225
+ last: res_start + s.end * per
226
+ };
227
+ }
228
+ export { arrival_ms, byte_strip, count_elements, left_at, unsized_images };
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Where the BROWSER's scripts live on disk: a browser CPU trace names its frames by URL
3
+ * (`https://site/_app/immutable/chunks/X.js`), and the source maps for those chunks sit next to
4
+ * the built client files, not on the server's chunk paths. These helpers find the client output
5
+ * dir and turn an app asset URL into the local file, so the same resolver that names server
6
+ * frames names browser frames too. Pure; the host supplies `exists`.
7
+ */
8
+ /** Kit's client output dirs to try, most likely first: the adapter's layout (`build/index.js` →
9
+ * `build/client`), Kit's own output during `vite preview`, and the two under the cwd. */
10
+ export declare function client_dir_candidates(entry_dir: string | undefined, cwd: string, join: (...p: string[]) => string): string[];
11
+ /** The path of an app asset inside the client dir (`/base/_app/immutable/chunks/X.js` →
12
+ * `_app/immutable/chunks/X.js`), or undefined when the URL is not one of the app's own. */
13
+ export declare function app_asset_rel(pathname: string): string | undefined;
14
+ /** A finder from URL to local file: same-origin app assets only, the first client dir that has
15
+ * the file wins, cached per URL. `origin` is the page's origin (the beacon's request origin). */
16
+ export declare function client_file_finder(origin: string | undefined, dirs: readonly string[], exists: (p: string) => boolean, join: (...p: string[]) => string): (url: string) => string | undefined;
@@ -0,0 +1,60 @@
1
+ // src/profiler/client-files.ts
2
+ /**
3
+ * Where the BROWSER's scripts live on disk: a browser CPU trace names its frames by URL
4
+ * (`https://site/_app/immutable/chunks/X.js`), and the source maps for those chunks sit next to
5
+ * the built client files, not on the server's chunk paths. These helpers find the client output
6
+ * dir and turn an app asset URL into the local file, so the same resolver that names server
7
+ * frames names browser frames too. Pure; the host supplies `exists`.
8
+ */
9
+ /** Kit's client output dirs to try, most likely first: the adapter's layout (`build/index.js` →
10
+ * `build/client`), Kit's own output during `vite preview`, and the two under the cwd. */
11
+ function client_dir_candidates(entry_dir, cwd, join) {
12
+ const out = [];
13
+ const add = (d) => {
14
+ if (!out.includes(d)) out.push(d);
15
+ };
16
+ if (entry_dir) {
17
+ add(join(entry_dir, "client"));
18
+ add(join(entry_dir, "..", "client"));
19
+ }
20
+ add(join(cwd, ".svelte-kit", "output", "client"));
21
+ add(join(cwd, "build", "client"));
22
+ return out;
23
+ }
24
+ /** The path of an app asset inside the client dir (`/base/_app/immutable/chunks/X.js` →
25
+ * `_app/immutable/chunks/X.js`), or undefined when the URL is not one of the app's own. */
26
+ function app_asset_rel(pathname) {
27
+ const i = pathname.indexOf("/_app/");
28
+ if (i === -1) return void 0;
29
+ const rel = pathname.slice(i + 1);
30
+ if (rel.split("/").some((s) => s === ".." || s === "")) return void 0;
31
+ return rel;
32
+ }
33
+ /** A finder from URL to local file: same-origin app assets only, the first client dir that has
34
+ * the file wins, cached per URL. `origin` is the page's origin (the beacon's request origin). */
35
+ function client_file_finder(origin, dirs, exists, join) {
36
+ const cache = /* @__PURE__ */ new Map();
37
+ let dir;
38
+ return (url) => {
39
+ const hit = cache.get(url);
40
+ if (hit !== void 0 || cache.has(url)) return hit;
41
+ let file;
42
+ try {
43
+ const u = new URL(url, origin ?? "http://x");
44
+ if (!origin || u.origin === origin) {
45
+ const rel = app_asset_rel(u.pathname);
46
+ if (rel) {
47
+ if (dir && exists(join(dir, rel))) file = join(dir, rel);
48
+ else for (const d of dirs) if (exists(join(d, rel))) {
49
+ dir = d;
50
+ file = join(d, rel);
51
+ break;
52
+ }
53
+ }
54
+ }
55
+ } catch {}
56
+ cache.set(url, file);
57
+ return file;
58
+ };
59
+ }
60
+ export { app_asset_rel, client_dir_candidates, client_file_finder };