perfgraph 0.1.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 (263) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +280 -0
  3. package/dist/causal/builder.d.ts +34 -0
  4. package/dist/causal/builder.d.ts.map +1 -0
  5. package/dist/causal/builder.js +132 -0
  6. package/dist/causal/builder.js.map +1 -0
  7. package/dist/causal/index.d.ts +11 -0
  8. package/dist/causal/index.d.ts.map +1 -0
  9. package/dist/causal/index.js +10 -0
  10. package/dist/causal/index.js.map +1 -0
  11. package/dist/causal/rules/js.d.ts +49 -0
  12. package/dist/causal/rules/js.d.ts.map +1 -0
  13. package/dist/causal/rules/js.js +209 -0
  14. package/dist/causal/rules/js.js.map +1 -0
  15. package/dist/causal/rules/layout.d.ts +39 -0
  16. package/dist/causal/rules/layout.d.ts.map +1 -0
  17. package/dist/causal/rules/layout.js +169 -0
  18. package/dist/causal/rules/layout.js.map +1 -0
  19. package/dist/causal/rules/lcp.d.ts +49 -0
  20. package/dist/causal/rules/lcp.d.ts.map +1 -0
  21. package/dist/causal/rules/lcp.js +250 -0
  22. package/dist/causal/rules/lcp.js.map +1 -0
  23. package/dist/causal/rules/network.d.ts +39 -0
  24. package/dist/causal/rules/network.d.ts.map +1 -0
  25. package/dist/causal/rules/network.js +140 -0
  26. package/dist/causal/rules/network.js.map +1 -0
  27. package/dist/causal/types.d.ts +142 -0
  28. package/dist/causal/types.d.ts.map +1 -0
  29. package/dist/causal/types.js +88 -0
  30. package/dist/causal/types.js.map +1 -0
  31. package/dist/cli/analyze.d.ts +33 -0
  32. package/dist/cli/analyze.d.ts.map +1 -0
  33. package/dist/cli/analyze.js +137 -0
  34. package/dist/cli/analyze.js.map +1 -0
  35. package/dist/cli/collect.d.ts +28 -0
  36. package/dist/cli/collect.d.ts.map +1 -0
  37. package/dist/cli/collect.js +125 -0
  38. package/dist/cli/collect.js.map +1 -0
  39. package/dist/cli/extract.d.ts +31 -0
  40. package/dist/cli/extract.d.ts.map +1 -0
  41. package/dist/cli/extract.js +135 -0
  42. package/dist/cli/extract.js.map +1 -0
  43. package/dist/cli/mcp.d.ts +14 -0
  44. package/dist/cli/mcp.d.ts.map +1 -0
  45. package/dist/cli/mcp.js +53 -0
  46. package/dist/cli/mcp.js.map +1 -0
  47. package/dist/cli/normalize.d.ts +58 -0
  48. package/dist/cli/normalize.d.ts.map +1 -0
  49. package/dist/cli/normalize.js +179 -0
  50. package/dist/cli/normalize.js.map +1 -0
  51. package/dist/cli/report.d.ts +33 -0
  52. package/dist/cli/report.d.ts.map +1 -0
  53. package/dist/cli/report.js +137 -0
  54. package/dist/cli/report.js.map +1 -0
  55. package/dist/cli/run.d.ts +49 -0
  56. package/dist/cli/run.d.ts.map +1 -0
  57. package/dist/cli/run.js +243 -0
  58. package/dist/cli/run.js.map +1 -0
  59. package/dist/collect/buffer-monitor.d.ts +25 -0
  60. package/dist/collect/buffer-monitor.d.ts.map +1 -0
  61. package/dist/collect/buffer-monitor.js +69 -0
  62. package/dist/collect/buffer-monitor.js.map +1 -0
  63. package/dist/collect/console.d.ts +25 -0
  64. package/dist/collect/console.d.ts.map +1 -0
  65. package/dist/collect/console.js +82 -0
  66. package/dist/collect/console.js.map +1 -0
  67. package/dist/collect/coverage.d.ts +26 -0
  68. package/dist/collect/coverage.d.ts.map +1 -0
  69. package/dist/collect/coverage.js +99 -0
  70. package/dist/collect/coverage.js.map +1 -0
  71. package/dist/collect/dom.d.ts +37 -0
  72. package/dist/collect/dom.d.ts.map +1 -0
  73. package/dist/collect/dom.js +118 -0
  74. package/dist/collect/dom.js.map +1 -0
  75. package/dist/collect/lighthouse.d.ts +41 -0
  76. package/dist/collect/lighthouse.d.ts.map +1 -0
  77. package/dist/collect/lighthouse.js +111 -0
  78. package/dist/collect/lighthouse.js.map +1 -0
  79. package/dist/collect/network.d.ts +32 -0
  80. package/dist/collect/network.d.ts.map +1 -0
  81. package/dist/collect/network.js +236 -0
  82. package/dist/collect/network.js.map +1 -0
  83. package/dist/collect/orchestrator.d.ts +23 -0
  84. package/dist/collect/orchestrator.d.ts.map +1 -0
  85. package/dist/collect/orchestrator.js +350 -0
  86. package/dist/collect/orchestrator.js.map +1 -0
  87. package/dist/collect/performance.d.ts +28 -0
  88. package/dist/collect/performance.d.ts.map +1 -0
  89. package/dist/collect/performance.js +108 -0
  90. package/dist/collect/performance.js.map +1 -0
  91. package/dist/collect/runtime.d.ts +26 -0
  92. package/dist/collect/runtime.d.ts.map +1 -0
  93. package/dist/collect/runtime.js +81 -0
  94. package/dist/collect/runtime.js.map +1 -0
  95. package/dist/collect/session.d.ts +58 -0
  96. package/dist/collect/session.d.ts.map +1 -0
  97. package/dist/collect/session.js +162 -0
  98. package/dist/collect/session.js.map +1 -0
  99. package/dist/collect/trace.d.ts +40 -0
  100. package/dist/collect/trace.d.ts.map +1 -0
  101. package/dist/collect/trace.js +127 -0
  102. package/dist/collect/trace.js.map +1 -0
  103. package/dist/collect/types.d.ts +317 -0
  104. package/dist/collect/types.d.ts.map +1 -0
  105. package/dist/collect/types.js +12 -0
  106. package/dist/collect/types.js.map +1 -0
  107. package/dist/distill/insights.d.ts +67 -0
  108. package/dist/distill/insights.d.ts.map +1 -0
  109. package/dist/distill/insights.js +317 -0
  110. package/dist/distill/insights.js.map +1 -0
  111. package/dist/distill/summaries.d.ts +71 -0
  112. package/dist/distill/summaries.d.ts.map +1 -0
  113. package/dist/distill/summaries.js +165 -0
  114. package/dist/distill/summaries.js.map +1 -0
  115. package/dist/extract/critical-path.d.ts +17 -0
  116. package/dist/extract/critical-path.d.ts.map +1 -0
  117. package/dist/extract/critical-path.js +95 -0
  118. package/dist/extract/critical-path.js.map +1 -0
  119. package/dist/extract/index.d.ts +16 -0
  120. package/dist/extract/index.d.ts.map +1 -0
  121. package/dist/extract/index.js +32 -0
  122. package/dist/extract/index.js.map +1 -0
  123. package/dist/extract/js-hotspots.d.ts +15 -0
  124. package/dist/extract/js-hotspots.d.ts.map +1 -0
  125. package/dist/extract/js-hotspots.js +22 -0
  126. package/dist/extract/js-hotspots.js.map +1 -0
  127. package/dist/extract/layout-shifts.d.ts +19 -0
  128. package/dist/extract/layout-shifts.d.ts.map +1 -0
  129. package/dist/extract/layout-shifts.js +47 -0
  130. package/dist/extract/layout-shifts.js.map +1 -0
  131. package/dist/extract/lcp-breakdown.d.ts +19 -0
  132. package/dist/extract/lcp-breakdown.d.ts.map +1 -0
  133. package/dist/extract/lcp-breakdown.js +110 -0
  134. package/dist/extract/lcp-breakdown.js.map +1 -0
  135. package/dist/extract/main-thread.d.ts +16 -0
  136. package/dist/extract/main-thread.d.ts.map +1 -0
  137. package/dist/extract/main-thread.js +41 -0
  138. package/dist/extract/main-thread.js.map +1 -0
  139. package/dist/extract/render-blocking.d.ts +16 -0
  140. package/dist/extract/render-blocking.d.ts.map +1 -0
  141. package/dist/extract/render-blocking.js +79 -0
  142. package/dist/extract/render-blocking.js.map +1 -0
  143. package/dist/extract/third-party.d.ts +15 -0
  144. package/dist/extract/third-party.d.ts.map +1 -0
  145. package/dist/extract/third-party.js +127 -0
  146. package/dist/extract/third-party.js.map +1 -0
  147. package/dist/extract/types.d.ts +183 -0
  148. package/dist/extract/types.d.ts.map +1 -0
  149. package/dist/extract/types.js +118 -0
  150. package/dist/extract/types.js.map +1 -0
  151. package/dist/index.d.ts +20 -0
  152. package/dist/index.d.ts.map +1 -0
  153. package/dist/index.js +188 -0
  154. package/dist/index.js.map +1 -0
  155. package/dist/mcp/handlers.d.ts +72 -0
  156. package/dist/mcp/handlers.d.ts.map +1 -0
  157. package/dist/mcp/handlers.js +285 -0
  158. package/dist/mcp/handlers.js.map +1 -0
  159. package/dist/mcp/progress.d.ts +37 -0
  160. package/dist/mcp/progress.d.ts.map +1 -0
  161. package/dist/mcp/progress.js +51 -0
  162. package/dist/mcp/progress.js.map +1 -0
  163. package/dist/mcp/prompts.d.ts +57 -0
  164. package/dist/mcp/prompts.d.ts.map +1 -0
  165. package/dist/mcp/prompts.js +225 -0
  166. package/dist/mcp/prompts.js.map +1 -0
  167. package/dist/mcp/resources.d.ts +48 -0
  168. package/dist/mcp/resources.d.ts.map +1 -0
  169. package/dist/mcp/resources.js +149 -0
  170. package/dist/mcp/resources.js.map +1 -0
  171. package/dist/mcp/server.d.ts +34 -0
  172. package/dist/mcp/server.d.ts.map +1 -0
  173. package/dist/mcp/server.js +452 -0
  174. package/dist/mcp/server.js.map +1 -0
  175. package/dist/mcp/types.d.ts +47 -0
  176. package/dist/mcp/types.d.ts.map +1 -0
  177. package/dist/mcp/types.js +67 -0
  178. package/dist/mcp/types.js.map +1 -0
  179. package/dist/normalize/clock.d.ts +40 -0
  180. package/dist/normalize/clock.d.ts.map +1 -0
  181. package/dist/normalize/clock.js +96 -0
  182. package/dist/normalize/clock.js.map +1 -0
  183. package/dist/normalize/dom-ir.d.ts +16 -0
  184. package/dist/normalize/dom-ir.d.ts.map +1 -0
  185. package/dist/normalize/dom-ir.js +48 -0
  186. package/dist/normalize/dom-ir.js.map +1 -0
  187. package/dist/normalize/index.d.ts +23 -0
  188. package/dist/normalize/index.d.ts.map +1 -0
  189. package/dist/normalize/index.js +118 -0
  190. package/dist/normalize/index.js.map +1 -0
  191. package/dist/normalize/lighthouse-insights.d.ts +27 -0
  192. package/dist/normalize/lighthouse-insights.d.ts.map +1 -0
  193. package/dist/normalize/lighthouse-insights.js +318 -0
  194. package/dist/normalize/lighthouse-insights.js.map +1 -0
  195. package/dist/normalize/lighthouse-ir.d.ts +25 -0
  196. package/dist/normalize/lighthouse-ir.d.ts.map +1 -0
  197. package/dist/normalize/lighthouse-ir.js +151 -0
  198. package/dist/normalize/lighthouse-ir.js.map +1 -0
  199. package/dist/normalize/network-ir.d.ts +23 -0
  200. package/dist/normalize/network-ir.d.ts.map +1 -0
  201. package/dist/normalize/network-ir.js +180 -0
  202. package/dist/normalize/network-ir.js.map +1 -0
  203. package/dist/normalize/runtime-ir.d.ts +18 -0
  204. package/dist/normalize/runtime-ir.d.ts.map +1 -0
  205. package/dist/normalize/runtime-ir.js +114 -0
  206. package/dist/normalize/runtime-ir.js.map +1 -0
  207. package/dist/normalize/trace-ir.d.ts +20 -0
  208. package/dist/normalize/trace-ir.d.ts.map +1 -0
  209. package/dist/normalize/trace-ir.js +224 -0
  210. package/dist/normalize/trace-ir.js.map +1 -0
  211. package/dist/normalize/types.d.ts +536 -0
  212. package/dist/normalize/types.d.ts.map +1 -0
  213. package/dist/normalize/types.js +242 -0
  214. package/dist/normalize/types.js.map +1 -0
  215. package/dist/output/manifest.d.ts +44 -0
  216. package/dist/output/manifest.d.ts.map +1 -0
  217. package/dist/output/manifest.js +232 -0
  218. package/dist/output/manifest.js.map +1 -0
  219. package/dist/output/toon.d.ts +19 -0
  220. package/dist/output/toon.d.ts.map +1 -0
  221. package/dist/output/toon.js +22 -0
  222. package/dist/output/toon.js.map +1 -0
  223. package/dist/output/writer.d.ts +31 -0
  224. package/dist/output/writer.d.ts.map +1 -0
  225. package/dist/output/writer.js +51 -0
  226. package/dist/output/writer.js.map +1 -0
  227. package/dist/report/analyzer.d.ts +29 -0
  228. package/dist/report/analyzer.d.ts.map +1 -0
  229. package/dist/report/analyzer.js +330 -0
  230. package/dist/report/analyzer.js.map +1 -0
  231. package/dist/report/index.d.ts +13 -0
  232. package/dist/report/index.d.ts.map +1 -0
  233. package/dist/report/index.js +12 -0
  234. package/dist/report/index.js.map +1 -0
  235. package/dist/report/markdown.d.ts +21 -0
  236. package/dist/report/markdown.d.ts.map +1 -0
  237. package/dist/report/markdown.js +182 -0
  238. package/dist/report/markdown.js.map +1 -0
  239. package/dist/report/remediations.d.ts +42 -0
  240. package/dist/report/remediations.d.ts.map +1 -0
  241. package/dist/report/remediations.js +179 -0
  242. package/dist/report/remediations.js.map +1 -0
  243. package/dist/report/scorer.d.ts +35 -0
  244. package/dist/report/scorer.d.ts.map +1 -0
  245. package/dist/report/scorer.js +72 -0
  246. package/dist/report/scorer.js.map +1 -0
  247. package/dist/report/types.d.ts +201 -0
  248. package/dist/report/types.d.ts.map +1 -0
  249. package/dist/report/types.js +149 -0
  250. package/dist/report/types.js.map +1 -0
  251. package/dist/shared/fs-utils.d.ts +12 -0
  252. package/dist/shared/fs-utils.d.ts.map +1 -0
  253. package/dist/shared/fs-utils.js +44 -0
  254. package/dist/shared/fs-utils.js.map +1 -0
  255. package/dist/shared/types.d.ts +69 -0
  256. package/dist/shared/types.d.ts.map +1 -0
  257. package/dist/shared/types.js +18 -0
  258. package/dist/shared/types.js.map +1 -0
  259. package/dist/shared/utils.d.ts +6 -0
  260. package/dist/shared/utils.d.ts.map +1 -0
  261. package/dist/shared/utils.js +9 -0
  262. package/dist/shared/utils.js.map +1 -0
  263. package/package.json +67 -0
@@ -0,0 +1,95 @@
1
+ /**
2
+ * Critical Path Length extractor.
3
+ *
4
+ * Computes metrics from the critical path chain derived in the Network IR:
5
+ * total chain length (duration sum), request counts, depth, and longest
6
+ * single request.
7
+ */
8
+ /** Safely treat any non-finite number as 0. */
9
+ function safe(v) {
10
+ return Number.isFinite(v) && v >= 0 ? v : 0;
11
+ }
12
+ /**
13
+ * Determine whether a request is considered "render-blocking".
14
+ *
15
+ * - Document & Stylesheet requests are always blocking.
16
+ * - Scripts at VeryHigh priority are considered blocking (render-blocking JS).
17
+ * - Everything else (images, fonts, XHR, fetch, etc.) is non-blocking.
18
+ */
19
+ function isBlocking(req) {
20
+ if (req.failed)
21
+ return false;
22
+ if (req.resourceType === 'Document')
23
+ return true;
24
+ if (req.resourceType === 'Stylesheet')
25
+ return true;
26
+ if (req.resourceType === 'Script' && req.priority === 'VeryHigh')
27
+ return true;
28
+ return false;
29
+ }
30
+ /**
31
+ * Walk the critical path tree recursively and aggregate metrics.
32
+ */
33
+ function walkTree(node, reqMap) {
34
+ let totalChainLength = 0;
35
+ let blockingCount = 0;
36
+ let nonBlockingCount = 0;
37
+ let longestSingleRequest = 0;
38
+ let requestCount = 0;
39
+ let anyMatched = false;
40
+ const req = reqMap.get(node.url);
41
+ if (req) {
42
+ anyMatched = true;
43
+ const dur = safe(req.duration);
44
+ totalChainLength += dur;
45
+ longestSingleRequest = Math.max(longestSingleRequest, dur);
46
+ requestCount++;
47
+ if (isBlocking(req)) {
48
+ blockingCount++;
49
+ }
50
+ else {
51
+ nonBlockingCount++;
52
+ }
53
+ }
54
+ for (const child of node.children ?? []) {
55
+ const childResult = walkTree(child, reqMap);
56
+ totalChainLength += childResult.totalChainLength;
57
+ blockingCount += childResult.blockingCount;
58
+ nonBlockingCount += childResult.nonBlockingCount;
59
+ longestSingleRequest = Math.max(longestSingleRequest, childResult.longestSingleRequest);
60
+ requestCount += childResult.requestCount;
61
+ anyMatched = anyMatched || childResult.anyMatched;
62
+ }
63
+ return { totalChainLength, blockingCount, nonBlockingCount, longestSingleRequest, requestCount, anyMatched };
64
+ }
65
+ /**
66
+ * Extract critical path metrics from an IRBundle.
67
+ *
68
+ * Returns undefined when the critical path info is empty or none of its
69
+ * tree URLs can be matched to a network request.
70
+ */
71
+ export function extractCriticalPath(ir) {
72
+ const cpInfo = ir.network.summary.criticalPath;
73
+ if (!cpInfo || !cpInfo.tree || !cpInfo.tree.url) {
74
+ return undefined;
75
+ }
76
+ // Build a map for O(1) request lookups by URL
77
+ const reqMap = new Map();
78
+ for (const req of ir.network.requests) {
79
+ reqMap.set(req.url, req);
80
+ }
81
+ const result = walkTree(cpInfo.tree, reqMap);
82
+ if (!result.anyMatched) {
83
+ return undefined;
84
+ }
85
+ return {
86
+ totalChainLength: result.totalChainLength,
87
+ requestCount: result.requestCount,
88
+ blockingCount: result.blockingCount,
89
+ nonBlockingCount: result.nonBlockingCount,
90
+ deepestChainDepth: cpInfo.depth,
91
+ longestSingleRequest: result.longestSingleRequest,
92
+ urlsOnLongestPath: cpInfo.urlsOnLongestPath?.length ? cpInfo.urlsOnLongestPath : undefined,
93
+ };
94
+ }
95
+ //# sourceMappingURL=critical-path.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"critical-path.js","sourceRoot":"","sources":["../../src/extract/critical-path.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Feature Extraction — entry point.
3
+ *
4
+ * extract() принимает IRBundle, запускает все extractor-функции и собирает
5
+ * FeatureSet. Каждая фича опциональна (graceful degradation при нехватке данных).
6
+ */
7
+ import type { IRBundle } from '../normalize/types.js';
8
+ import type { FeatureSet } from './types.js';
9
+ /**
10
+ * Извлечь все диагностические сигналы из IRBundle.
11
+ *
12
+ * Чистая функция: одинаковый IRBundle → одинаковый FeatureSet.
13
+ * Возвращает FeatureSet со всеми опциональными полями.
14
+ */
15
+ export declare function extract(ir: IRBundle): FeatureSet;
16
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/extract/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AACtD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAS7C;;;;;GAKG;AACH,wBAAgB,OAAO,CAAC,EAAE,EAAE,QAAQ,GAAG,UAAU,CAWhD"}
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Feature Extraction — entry point.
3
+ *
4
+ * extract() принимает IRBundle, запускает все extractor-функции и собирает
5
+ * FeatureSet. Каждая фича опциональна (graceful degradation при нехватке данных).
6
+ */
7
+ import { extractLCPBreakdown } from './lcp-breakdown.js';
8
+ import { extractCriticalPath } from './critical-path.js';
9
+ import { extractMainThreadBlocking } from './main-thread.js';
10
+ import { extractJSHotspots } from './js-hotspots.js';
11
+ import { extractLayoutShifts } from './layout-shifts.js';
12
+ import { extractThirdPartyOverhead } from './third-party.js';
13
+ import { extractRenderBlocking } from './render-blocking.js';
14
+ /**
15
+ * Извлечь все диагностические сигналы из IRBundle.
16
+ *
17
+ * Чистая функция: одинаковый IRBundle → одинаковый FeatureSet.
18
+ * Возвращает FeatureSet со всеми опциональными полями.
19
+ */
20
+ export function extract(ir) {
21
+ return {
22
+ url: ir.meta.url || undefined,
23
+ lcpBreakdown: extractLCPBreakdown(ir),
24
+ criticalPath: extractCriticalPath(ir),
25
+ mainThreadBlocking: extractMainThreadBlocking(ir),
26
+ jsHotspots: extractJSHotspots(ir),
27
+ layoutShifts: extractLayoutShifts(ir),
28
+ thirdPartyOverhead: extractThirdPartyOverhead(ir),
29
+ renderBlocking: extractRenderBlocking(ir),
30
+ };
31
+ }
32
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/extract/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH,OAAO,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AACzD,OAAO,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AACzD,OAAO,EAAE,yBAAyB,EAAE,MAAM,kBAAkB,CAAC;AAC7D,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AACrD,OAAO,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AACzD,OAAO,EAAE,yBAAyB,EAAE,MAAM,kBAAkB,CAAC;AAC7D,OAAO,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAE7D;;;;;GAKG;AACH,MAAM,UAAU,OAAO,CAAC,EAAY;IAClC,OAAO;QACL,GAAG,EAAE,EAAE,CAAC,IAAI,CAAC,GAAG,IAAI,SAAS;QAC7B,YAAY,EAAE,mBAAmB,CAAC,EAAE,CAAC;QACrC,YAAY,EAAE,mBAAmB,CAAC,EAAE,CAAC;QACrC,kBAAkB,EAAE,yBAAyB,CAAC,EAAE,CAAC;QACjD,UAAU,EAAE,iBAAiB,CAAC,EAAE,CAAC;QACjC,YAAY,EAAE,mBAAmB,CAAC,EAAE,CAAC;QACrC,kBAAkB,EAAE,yBAAyB,CAAC,EAAE,CAAC;QACjD,cAAc,EAAE,qBAAqB,CAAC,EAAE,CAAC;KAC1C,CAAC;AACJ,CAAC"}
@@ -0,0 +1,15 @@
1
+ /**
2
+ * JS Hotspots extractor.
3
+ *
4
+ * Identifies JS execution hotspots from runtime IR data:
5
+ * hydration cost, long tasks, and execution context count.
6
+ */
7
+ import type { IRBundle } from '../normalize/types.js';
8
+ import type { JSHotspots } from './types.js';
9
+ /**
10
+ * Extract JS hotspot metrics from an IRBundle.
11
+ *
12
+ * Never returns undefined — defaults to zero when runtime data is absent.
13
+ */
14
+ export declare function extractJSHotspots(ir: IRBundle): JSHotspots;
15
+ //# sourceMappingURL=js-hotspots.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"js-hotspots.d.ts","sourceRoot":"","sources":["../../src/extract/js-hotspots.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AACtD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAE7C;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,EAAE,EAAE,QAAQ,GAAG,UAAU,CAU1D"}
@@ -0,0 +1,22 @@
1
+ /**
2
+ * JS Hotspots extractor.
3
+ *
4
+ * Identifies JS execution hotspots from runtime IR data:
5
+ * hydration cost, long tasks, and execution context count.
6
+ */
7
+ /**
8
+ * Extract JS hotspot metrics from an IRBundle.
9
+ *
10
+ * Never returns undefined — defaults to zero when runtime data is absent.
11
+ */
12
+ export function extractJSHotspots(ir) {
13
+ const { runtime } = ir;
14
+ return {
15
+ bootupTime: runtime.hydrationCost?.bootupTime ?? 0,
16
+ evaluatedScripts: runtime.hydrationCost?.evaluatedScripts ?? 0,
17
+ longTaskCount: runtime.eventLoopStats?.longTasks ?? 0,
18
+ maxBlockingDuration: runtime.eventLoopStats?.maxBlockingDuration ?? 0,
19
+ contextCount: runtime.executionContexts.length,
20
+ };
21
+ }
22
+ //# sourceMappingURL=js-hotspots.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"js-hotspots.js","sourceRoot":"","sources":["../../src/extract/js-hotspots.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAKH;;;;GAIG;AACH,MAAM,UAAU,iBAAiB,CAAC,EAAY;IAC5C,MAAM,EAAE,OAAO,EAAE,GAAG,EAAE,CAAC;IAEvB,OAAO;QACL,UAAU,EAAE,OAAO,CAAC,aAAa,EAAE,UAAU,IAAI,CAAC;QAClD,gBAAgB,EAAE,OAAO,CAAC,aAAa,EAAE,gBAAgB,IAAI,CAAC;QAC9D,aAAa,EAAE,OAAO,CAAC,cAAc,EAAE,SAAS,IAAI,CAAC;QACrD,mBAAmB,EAAE,OAAO,CAAC,cAAc,EAAE,mBAAmB,IAAI,CAAC;QACrE,YAAY,EAAE,OAAO,CAAC,iBAAiB,CAAC,MAAM;KAC/C,CAAC;AACJ,CAAC"}
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Layout Shifts extractor.
3
+ *
4
+ * Computes layout instability metrics from DOM IR layout shift candidates
5
+ * and the CLS value from Core Web Vitals.
6
+ *
7
+ * clusterScore — композитная оценка 0-1, основанная на CLS и
8
+ * структурных признаках нестабильности layout (сложные поддеревья,
9
+ * глубокое вложение).
10
+ */
11
+ import type { IRBundle } from '../normalize/types.js';
12
+ import type { LayoutShifts } from './types.js';
13
+ /**
14
+ * Extract layout shift metrics from an IRBundle.
15
+ *
16
+ * Never returns undefined — defaults to zero when DOM/Layout data is absent.
17
+ */
18
+ export declare function extractLayoutShifts(ir: IRBundle): LayoutShifts;
19
+ //# sourceMappingURL=layout-shifts.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"layout-shifts.d.ts","sourceRoot":"","sources":["../../src/extract/layout-shifts.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AACtD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAK/C;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,EAAE,EAAE,QAAQ,GAAG,YAAY,CAiC9D"}
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Layout Shifts extractor.
3
+ *
4
+ * Computes layout instability metrics from DOM IR layout shift candidates
5
+ * and the CLS value from Core Web Vitals.
6
+ *
7
+ * clusterScore — композитная оценка 0-1, основанная на CLS и
8
+ * структурных признаках нестабильности layout (сложные поддеревья,
9
+ * глубокое вложение).
10
+ */
11
+ /** Categories that contribute to layout shift risk when over-represented. */
12
+ const HIGH_RISK_TAGS = new Set(['img', 'video', 'iframe', 'canvas', 'svg']);
13
+ /**
14
+ * Extract layout shift metrics from an IRBundle.
15
+ *
16
+ * Never returns undefined — defaults to zero when DOM/Layout data is absent.
17
+ */
18
+ export function extractLayoutShifts(ir) {
19
+ const { layoutShiftCandidates } = ir.dom;
20
+ const cls = ir.performance.coreWebVitals.cls;
21
+ const highComplexitySubtreeCount = layoutShiftCandidates?.highComplexitySubtrees ?? 0;
22
+ const deepNesting = (layoutShiftCandidates?.deepNesting ?? 0) > 0;
23
+ // clusterScore: heuristic weighted score combining CLS and structural risk
24
+ const clsScore = cls !== undefined && Number.isFinite(cls) ? Math.min(1, cls * 50) : 0;
25
+ const complexityScore = Math.min(1, highComplexitySubtreeCount / 10);
26
+ const nestingScore = deepNesting ? 0.3 : 0;
27
+ // Tag risk: look at tag distribution — too many img/video/iframe in small DOM
28
+ let tagRisk = 0;
29
+ const tags = ir.dom.tagDistribution;
30
+ if (tags.length > 0) {
31
+ const totalTags = tags.reduce((sum, t) => sum + t.count, 0);
32
+ if (totalTags > 0) {
33
+ const highRiskCount = tags
34
+ .filter((t) => HIGH_RISK_TAGS.has(t.tag))
35
+ .reduce((sum, t) => sum + t.count, 0);
36
+ tagRisk = Math.min(1, highRiskCount / totalTags);
37
+ }
38
+ }
39
+ const clusterScore = Math.min(1, clsScore * 0.5 + complexityScore * 0.25 + nestingScore * 0.15 + tagRisk * 0.1);
40
+ return {
41
+ cls: cls !== undefined && Number.isFinite(cls) ? cls : undefined,
42
+ highComplexitySubtreeCount,
43
+ deepNesting,
44
+ clusterScore,
45
+ };
46
+ }
47
+ //# sourceMappingURL=layout-shifts.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"layout-shifts.js","sourceRoot":"","sources":["../../src/extract/layout-shifts.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAKH,6EAA6E;AAC7E,MAAM,cAAc,GAAG,IAAI,GAAG,CAAC,CAAC,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,KAAK,CAAC,CAAC,CAAC;AAE5E;;;;GAIG;AACH,MAAM,UAAU,mBAAmB,CAAC,EAAY;IAC9C,MAAM,EAAE,qBAAqB,EAAE,GAAG,EAAE,CAAC,GAAG,CAAC;IACzC,MAAM,GAAG,GAAG,EAAE,CAAC,WAAW,CAAC,aAAa,CAAC,GAAG,CAAC;IAE7C,MAAM,0BAA0B,GAAG,qBAAqB,EAAE,sBAAsB,IAAI,CAAC,CAAC;IACtF,MAAM,WAAW,GAAG,CAAC,qBAAqB,EAAE,WAAW,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC;IAElE,2EAA2E;IAC3E,MAAM,QAAQ,GAAG,GAAG,KAAK,SAAS,IAAI,MAAM,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,GAAG,GAAG,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IACvF,MAAM,eAAe,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,0BAA0B,GAAG,EAAE,CAAC,CAAC;IACrE,MAAM,YAAY,GAAG,WAAW,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;IAE3C,8EAA8E;IAC9E,IAAI,OAAO,GAAG,CAAC,CAAC;IAChB,MAAM,IAAI,GAAG,EAAE,CAAC,GAAG,CAAC,eAAe,CAAC;IACpC,IAAI,IAAI,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACpB,MAAM,SAAS,GAAG,IAAI,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC,GAAG,GAAG,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC;QAC5D,IAAI,SAAS,GAAG,CAAC,EAAE,CAAC;YAClB,MAAM,aAAa,GAAG,IAAI;iBACvB,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,cAAc,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;iBACxC,MAAM,CAAC,CAAC,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC,GAAG,GAAG,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC;YACxC,OAAO,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,aAAa,GAAG,SAAS,CAAC,CAAC;QACnD,CAAC;IACH,CAAC;IAED,MAAM,YAAY,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,QAAQ,GAAG,GAAG,GAAG,eAAe,GAAG,IAAI,GAAG,YAAY,GAAG,IAAI,GAAG,OAAO,GAAG,GAAG,CAAC,CAAC;IAEhH,OAAO;QACL,GAAG,EAAE,GAAG,KAAK,SAAS,IAAI,MAAM,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,SAAS;QAChE,0BAA0B;QAC1B,WAAW;QACX,YAAY;KACb,CAAC;AACJ,CAAC"}
@@ -0,0 +1,19 @@
1
+ /**
2
+ * LCP (Largest Contentful Paint) Breakdown extractor.
3
+ *
4
+ * Splits LCP time into 4 subparts per Chrome spec:
5
+ * TTFB → Resource Load Delay → Resource Load Time → Element Render Delay
6
+ */
7
+ import type { IRBundle } from '../normalize/types.js';
8
+ import type { LCPBreakdown } from './types.js';
9
+ /**
10
+ * Extract LCP breakdown from an IRBundle.
11
+ *
12
+ * Uses a fallback chain for the total LCP value:
13
+ * 1. Trace LCP from coreWebVitals.lcp
14
+ * 2. Lighthouse LCP from lighthouse.lcpNumericValue
15
+ *
16
+ * Returns undefined if neither source has LCP data.
17
+ */
18
+ export declare function extractLCPBreakdown(ir: IRBundle): LCPBreakdown | undefined;
19
+ //# sourceMappingURL=lcp-breakdown.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lcp-breakdown.d.ts","sourceRoot":"","sources":["../../src/extract/lcp-breakdown.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAqB,MAAM,uBAAuB,CAAC;AACzE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAoD/C;;;;;;;;GAQG;AACH,wBAAgB,mBAAmB,CAAC,EAAE,EAAE,QAAQ,GAAG,YAAY,GAAG,SAAS,CAwD1E"}
@@ -0,0 +1,110 @@
1
+ /**
2
+ * LCP (Largest Contentful Paint) Breakdown extractor.
3
+ *
4
+ * Splits LCP time into 4 subparts per Chrome spec:
5
+ * TTFB → Resource Load Delay → Resource Load Time → Element Render Delay
6
+ */
7
+ /** Safely clamp a number to 0 if it's negative, NaN, or Infinity. */
8
+ function clamp(value) {
9
+ return Number.isFinite(value) && value >= 0 ? value : 0;
10
+ }
11
+ /**
12
+ * Find the Document request (first page navigation) from the network log.
13
+ */
14
+ function findDocumentRequest(requests) {
15
+ return requests.find((r) => r.resourceType === 'Document');
16
+ }
17
+ /**
18
+ * Find the most likely LCP resource among network requests.
19
+ * Chrome LCP is typically the largest image (by bytes) or a text node.
20
+ * Returns undefined for text-only LCP.
21
+ */
22
+ function findLcpResource(requests) {
23
+ const images = requests.filter((r) => r.resourceType === 'Image' && !r.failed);
24
+ if (images.length === 0)
25
+ return undefined;
26
+ return images.reduce((a, b) => (a.bytes >= b.bytes ? a : b));
27
+ }
28
+ /**
29
+ * Extract TTFB from the network document request or fallback to Lighthouse.
30
+ *
31
+ * Primary: sum of DNS + Connect + SSL + Wait from the Document request's timing.
32
+ * Fallback: Lighthouse audit 'server-response-time' numericValue.
33
+ * Last resort: 0.
34
+ */
35
+ function extractTTFB(ir) {
36
+ // Primary: Document request timing
37
+ const doc = findDocumentRequest(ir.network.requests);
38
+ if (doc?.timing) {
39
+ const { dns = 0, connect = 0, ssl = 0, wait = 0 } = doc.timing;
40
+ const ttfb = dns + connect + ssl + wait;
41
+ if (Number.isFinite(ttfb))
42
+ return Math.max(0, ttfb);
43
+ }
44
+ // Fallback: Lighthouse server-response-time audit
45
+ const lhAudit = ir.lighthouse.failedAudits.find((a) => a.id === 'server-response-time');
46
+ if (lhAudit?.numericValue !== undefined && Number.isFinite(lhAudit.numericValue)) {
47
+ return Math.max(0, lhAudit.numericValue);
48
+ }
49
+ return 0;
50
+ }
51
+ /**
52
+ * Extract LCP breakdown from an IRBundle.
53
+ *
54
+ * Uses a fallback chain for the total LCP value:
55
+ * 1. Trace LCP from coreWebVitals.lcp
56
+ * 2. Lighthouse LCP from lighthouse.lcpNumericValue
57
+ *
58
+ * Returns undefined if neither source has LCP data.
59
+ */
60
+ export function extractLCPBreakdown(ir) {
61
+ // Fallback chain: trace → Lighthouse
62
+ const totalLCP = ir.performance.coreWebVitals.lcp ?? ir.lighthouse.lcpNumericValue;
63
+ if (totalLCP === undefined || !Number.isFinite(totalLCP)) {
64
+ return undefined;
65
+ }
66
+ // Determine source
67
+ const hasTrace = ir.performance.coreWebVitals.lcp !== undefined;
68
+ const hasLighthouse = ir.lighthouse.lcpNumericValue !== undefined;
69
+ const source = hasTrace && hasLighthouse ? 'mixed' :
70
+ hasLighthouse ? 'lighthouse' : 'trace';
71
+ const ttfb = extractTTFB(ir);
72
+ const lcpResource = findLcpResource(ir.network.requests);
73
+ // Build lcpElement if Lighthouse provides selector info
74
+ const lcpElement = ir.lighthouse.lcpElementSelector
75
+ ? {
76
+ selector: ir.lighthouse.lcpElementSelector,
77
+ snippet: ir.lighthouse.lcpElementSnippet,
78
+ nodeLabel: ir.lighthouse.lcpElementNodeLabel,
79
+ }
80
+ : undefined;
81
+ // Text-only LCP: no image resource to load
82
+ if (!lcpResource) {
83
+ return {
84
+ ttfb,
85
+ resourceLoadDelay: 0,
86
+ resourceLoadTime: 0,
87
+ elementRenderDelay: clamp(totalLCP - ttfb),
88
+ totalLCP,
89
+ lcpElementUrl: undefined,
90
+ lcpResourceType: undefined,
91
+ lcpElement,
92
+ source,
93
+ };
94
+ }
95
+ const resourceLoadDelay = clamp(lcpResource.startTime - ttfb);
96
+ const resourceLoadTime = clamp(lcpResource.duration);
97
+ const elementRenderDelay = clamp(totalLCP - ttfb - resourceLoadDelay - resourceLoadTime);
98
+ return {
99
+ ttfb,
100
+ resourceLoadDelay,
101
+ resourceLoadTime,
102
+ elementRenderDelay,
103
+ totalLCP,
104
+ lcpElementUrl: lcpResource.url,
105
+ lcpResourceType: lcpResource.resourceType,
106
+ lcpElement,
107
+ source,
108
+ };
109
+ }
110
+ //# sourceMappingURL=lcp-breakdown.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lcp-breakdown.js","sourceRoot":"","sources":["../../src/extract/lcp-breakdown.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Main Thread Blocking Score extractor.
3
+ *
4
+ * Computes a blocking score from thread activity and main thread busyness.
5
+ * Combines trace-level thread activity (by category) with the global
6
+ * mainThreadBusyness ratio to produce a 0-1 blocking score.
7
+ */
8
+ import type { IRBundle } from '../normalize/types.js';
9
+ import type { MainThreadBlocking } from './types.js';
10
+ /**
11
+ * Extract main thread blocking metrics from an IRBundle.
12
+ *
13
+ * Returns undefined when traceSummary data is missing (zero duration).
14
+ */
15
+ export declare function extractMainThreadBlocking(ir: IRBundle): MainThreadBlocking | undefined;
16
+ //# sourceMappingURL=main-thread.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"main-thread.d.ts","sourceRoot":"","sources":["../../src/extract/main-thread.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AACtD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAErD;;;;GAIG;AACH,wBAAgB,yBAAyB,CAAC,EAAE,EAAE,QAAQ,GAAG,kBAAkB,GAAG,SAAS,CAiCtF"}
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Main Thread Blocking Score extractor.
3
+ *
4
+ * Computes a blocking score from thread activity and main thread busyness.
5
+ * Combines trace-level thread activity (by category) with the global
6
+ * mainThreadBusyness ratio to produce a 0-1 blocking score.
7
+ */
8
+ /**
9
+ * Extract main thread blocking metrics from an IRBundle.
10
+ *
11
+ * Returns undefined when traceSummary data is missing (zero duration).
12
+ */
13
+ export function extractMainThreadBlocking(ir) {
14
+ const { traceSummary } = ir.performance;
15
+ const totalDuration = traceSummary.totalDuration;
16
+ if (!totalDuration || totalDuration <= 0) {
17
+ return undefined;
18
+ }
19
+ const busyMs = traceSummary.threadActivity.totalMs;
20
+ // idleMs = totalDuration - busyMs can produce absurd values (e.g. 210M ms ≈ 58h)
21
+ // when totalDuration (trace event timestamps, wall-clock anchored) and busyMs
22
+ // (CDP Tracing durations, monotonic clock) come from different clock domains.
23
+ // Clamp idleMs to a sane maximum so clock-domain mismatch doesn't pollute the output.
24
+ const IDLE_MS_MAX = 120_000; // 2 minutes — reasonable upper bound for idle time
25
+ const idleMs = Math.min(IDLE_MS_MAX, Math.max(0, totalDuration - busyMs));
26
+ const blockingRatio = totalDuration > 0 ? Math.min(1, Math.max(0, busyMs / totalDuration)) : 0;
27
+ // blockingScore blends mainThreadBusyness with the raw busy ratio
28
+ // to smooth out edge cases where one source is more reliable
29
+ const busynessScore = ir.performance.mainThreadBusyness;
30
+ const blockingScore = Number.isFinite(busynessScore)
31
+ ? Math.min(1, Math.max(0, (busynessScore + blockingRatio) / 2))
32
+ : blockingRatio;
33
+ return {
34
+ blockingScore,
35
+ busyMs,
36
+ idleMs,
37
+ blockingRatio,
38
+ categories: { ...traceSummary.threadActivity.byCategory },
39
+ };
40
+ }
41
+ //# sourceMappingURL=main-thread.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"main-thread.js","sourceRoot":"","sources":["../../src/extract/main-thread.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAKH;;;;GAIG;AACH,MAAM,UAAU,yBAAyB,CAAC,EAAY;IACpD,MAAM,EAAE,YAAY,EAAE,GAAG,EAAE,CAAC,WAAW,CAAC;IACxC,MAAM,aAAa,GAAG,YAAY,CAAC,aAAa,CAAC;IAEjD,IAAI,CAAC,aAAa,IAAI,aAAa,IAAI,CAAC,EAAE,CAAC;QACzC,OAAO,SAAS,CAAC;IACnB,CAAC;IAED,MAAM,MAAM,GAAG,YAAY,CAAC,cAAc,CAAC,OAAO,CAAC;IAEnD,iFAAiF;IACjF,8EAA8E;IAC9E,8EAA8E;IAC9E,sFAAsF;IACtF,MAAM,WAAW,GAAG,OAAO,CAAC,CAAC,mDAAmD;IAChF,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,WAAW,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,aAAa,GAAG,MAAM,CAAC,CAAC,CAAC;IAE1E,MAAM,aAAa,GAAG,aAAa,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,MAAM,GAAG,aAAa,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IAE/F,kEAAkE;IAClE,6DAA6D;IAC7D,MAAM,aAAa,GAAG,EAAE,CAAC,WAAW,CAAC,kBAAkB,CAAC;IACxD,MAAM,aAAa,GAAG,MAAM,CAAC,QAAQ,CAAC,aAAa,CAAC;QAClD,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,aAAa,GAAG,aAAa,CAAC,GAAG,CAAC,CAAC,CAAC;QAC/D,CAAC,CAAC,aAAa,CAAC;IAElB,OAAO;QACL,aAAa;QACb,MAAM;QACN,MAAM;QACN,aAAa;QACb,UAAU,EAAE,EAAE,GAAG,YAAY,CAAC,cAAc,CAAC,UAAU,EAAE;KAC1D,CAAC;AACJ,CAAC"}
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Render-Blocking Score extractor.
3
+ *
4
+ * Identifies render-blocking resources (stylesheets, render-blocking scripts)
5
+ * and computes a weighted score combining count, bytes, and Lighthouse
6
+ * numeric value when available.
7
+ */
8
+ import type { IRBundle } from '../normalize/types.js';
9
+ import type { RenderBlockingScore } from './types.js';
10
+ /**
11
+ * Extract render-blocking metrics from an IRBundle.
12
+ *
13
+ * Returns undefined when there are no network requests.
14
+ */
15
+ export declare function extractRenderBlocking(ir: IRBundle): RenderBlockingScore | undefined;
16
+ //# sourceMappingURL=render-blocking.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"render-blocking.d.ts","sourceRoot":"","sources":["../../src/extract/render-blocking.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAqB,MAAM,uBAAuB,CAAC;AACzE,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;AA0BtD;;;;GAIG;AACH,wBAAgB,qBAAqB,CAAC,EAAE,EAAE,QAAQ,GAAG,mBAAmB,GAAG,SAAS,CAgDnF"}
@@ -0,0 +1,79 @@
1
+ /**
2
+ * Render-Blocking Score extractor.
3
+ *
4
+ * Identifies render-blocking resources (stylesheets, render-blocking scripts)
5
+ * and computes a weighted score combining count, bytes, and Lighthouse
6
+ * numeric value when available.
7
+ */
8
+ /** Tags considered render-blocking by default. */
9
+ const BLOCKING_TYPES = new Set(['Stylesheet', 'Document']);
10
+ /**
11
+ * Check whether a request is render-blocking.
12
+ *
13
+ * - Stylesheet and Document are always blocking.
14
+ * - Scripts at VeryHigh priority are considered blocking.
15
+ * - Font preloads and other early-critical resources could be added here.
16
+ */
17
+ function isRenderBlocking(req) {
18
+ if (req.failed)
19
+ return false;
20
+ if (BLOCKING_TYPES.has(req.resourceType))
21
+ return true;
22
+ if (req.resourceType === 'Script' && req.priority === 'VeryHigh')
23
+ return true;
24
+ return false;
25
+ }
26
+ /**
27
+ * Clamp a number to non-negative, returning 0 for NaN/Infinity.
28
+ */
29
+ function safe(v) {
30
+ return Number.isFinite(v) && v >= 0 ? v : 0;
31
+ }
32
+ /**
33
+ * Extract render-blocking metrics from an IRBundle.
34
+ *
35
+ * Returns undefined when there are no network requests.
36
+ */
37
+ export function extractRenderBlocking(ir) {
38
+ const requests = ir.network.requests;
39
+ if (requests.length === 0)
40
+ return undefined;
41
+ let blockingRequestCount = 0;
42
+ let blockingBytes = 0;
43
+ let blockingDuration = 0;
44
+ for (const req of requests) {
45
+ if (isRenderBlocking(req)) {
46
+ blockingRequestCount++;
47
+ blockingBytes += safe(req.bytes);
48
+ blockingDuration += safe(req.duration);
49
+ }
50
+ }
51
+ // Lighthouse render-blocking-resources audit
52
+ const lhAudit = ir.lighthouse.failedAudits.find((a) => a.id === 'render-blocking-resources');
53
+ const lhRenderBlockingMs = lhAudit?.numericValue !== undefined && Number.isFinite(lhAudit.numericValue)
54
+ ? lhAudit.numericValue
55
+ : undefined;
56
+ // Score: 0 = lots of blocking, 1 = no blocking
57
+ // Blend count (up to 20 resources = max penalty) and Lighthouse value
58
+ const countScore = Math.max(0, 1 - blockingRequestCount / 20);
59
+ const lhScore = lhAudit !== undefined && lhAudit.score !== undefined && Number.isFinite(lhAudit.score)
60
+ ? lhAudit.score
61
+ : undefined;
62
+ const renderBlockingScore = lhScore !== undefined
63
+ ? (countScore + lhScore) / 2
64
+ : countScore;
65
+ // Use Lighthouse insight data for URL-level details
66
+ const lighthouseResources = ir.lighthouse.renderBlockingResources;
67
+ const totalWastedMs = lighthouseResources
68
+ ?.reduce((sum, r) => sum + (r.wastedMs ?? 0), 0);
69
+ return {
70
+ blockingRequestCount,
71
+ blockingBytes,
72
+ blockingDuration,
73
+ renderBlockingScore,
74
+ lhRenderBlockingMs,
75
+ resources: lighthouseResources,
76
+ totalWastedMs: lighthouseResources ? totalWastedMs : undefined,
77
+ };
78
+ }
79
+ //# sourceMappingURL=render-blocking.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"render-blocking.js","sourceRoot":"","sources":["../../src/extract/render-blocking.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAKH,kDAAkD;AAClD,MAAM,cAAc,GAAG,IAAI,GAAG,CAAC,CAAC,YAAY,EAAE,UAAU,CAAC,CAAC,CAAC;AAE3D;;;;;;GAMG;AACH,SAAS,gBAAgB,CAAC,GAAsB;IAC9C,IAAI,GAAG,CAAC,MAAM;QAAE,OAAO,KAAK,CAAC;IAC7B,IAAI,cAAc,CAAC,GAAG,CAAC,GAAG,CAAC,YAAY,CAAC;QAAE,OAAO,IAAI,CAAC;IACtD,IAAI,GAAG,CAAC,YAAY,KAAK,QAAQ,IAAI,GAAG,CAAC,QAAQ,KAAK,UAAU;QAAE,OAAO,IAAI,CAAC;IAC9E,OAAO,KAAK,CAAC;AACf,CAAC;AAED;;GAEG;AACH,SAAS,IAAI,CAAC,CAAS;IACrB,OAAO,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AAC9C,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,qBAAqB,CAAC,EAAY;IAChD,MAAM,QAAQ,GAAG,EAAE,CAAC,OAAO,CAAC,QAAQ,CAAC;IACrC,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,SAAS,CAAC;IAE5C,IAAI,oBAAoB,GAAG,CAAC,CAAC;IAC7B,IAAI,aAAa,GAAG,CAAC,CAAC;IACtB,IAAI,gBAAgB,GAAG,CAAC,CAAC;IAEzB,KAAK,MAAM,GAAG,IAAI,QAAQ,EAAE,CAAC;QAC3B,IAAI,gBAAgB,CAAC,GAAG,CAAC,EAAE,CAAC;YAC1B,oBAAoB,EAAE,CAAC;YACvB,aAAa,IAAI,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;YACjC,gBAAgB,IAAI,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;QACzC,CAAC;IACH,CAAC;IAED,6CAA6C;IAC7C,MAAM,OAAO,GAAG,EAAE,CAAC,UAAU,CAAC,YAAY,CAAC,IAAI,CAC7C,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,2BAA2B,CAC5C,CAAC;IACF,MAAM,kBAAkB,GAAG,OAAO,EAAE,YAAY,KAAK,SAAS,IAAI,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC,YAAY,CAAC;QACrG,CAAC,CAAC,OAAO,CAAC,YAAY;QACtB,CAAC,CAAC,SAAS,CAAC;IAEd,+CAA+C;IAC/C,sEAAsE;IACtE,MAAM,UAAU,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,GAAG,oBAAoB,GAAG,EAAE,CAAC,CAAC;IAC9D,MAAM,OAAO,GAAG,OAAO,KAAK,SAAS,IAAI,OAAO,CAAC,KAAK,KAAK,SAAS,IAAI,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC,KAAK,CAAC;QACpG,CAAC,CAAC,OAAO,CAAC,KAAK;QACf,CAAC,CAAC,SAAS,CAAC;IACd,MAAM,mBAAmB,GAAG,OAAO,KAAK,SAAS;QAC/C,CAAC,CAAC,CAAC,UAAU,GAAG,OAAO,CAAC,GAAG,CAAC;QAC5B,CAAC,CAAC,UAAU,CAAC;IAEf,oDAAoD;IACpD,MAAM,mBAAmB,GAAG,EAAE,CAAC,UAAU,CAAC,uBAAuB,CAAC;IAClE,MAAM,aAAa,GAAG,mBAAmB;QACvC,EAAE,MAAM,CAAC,CAAC,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC,QAAQ,IAAI,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;IAEnD,OAAO;QACL,oBAAoB;QACpB,aAAa;QACb,gBAAgB;QAChB,mBAAmB;QACnB,kBAAkB;QAClB,SAAS,EAAE,mBAAmB;QAC9B,aAAa,EAAE,mBAAmB,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,SAAS;KAC/D,CAAC;AACJ,CAAC"}
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Third-Party Overhead extractor.
3
+ *
4
+ * Identifies and categorises requests to third-party origins and computes
5
+ * aggregate metrics: count, bytes, duration, and ratio vs first-party.
6
+ */
7
+ import type { IRBundle } from '../normalize/types.js';
8
+ import type { ThirdPartyOverhead } from './types.js';
9
+ /**
10
+ * Extract third-party overhead metrics from an IRBundle.
11
+ *
12
+ * Returns undefined when there are no network requests.
13
+ */
14
+ export declare function extractThirdPartyOverhead(ir: IRBundle): ThirdPartyOverhead | undefined;
15
+ //# sourceMappingURL=third-party.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"third-party.d.ts","sourceRoot":"","sources":["../../src/extract/third-party.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAqB,MAAM,uBAAuB,CAAC;AACzE,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AA8DrD;;;;GAIG;AACH,wBAAgB,yBAAyB,CAAC,EAAE,EAAE,QAAQ,GAAG,kBAAkB,GAAG,SAAS,CA6DtF"}