browsertime 27.4.0 → 27.4.1

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/CHANGELOG.md CHANGED
@@ -1,5 +1,10 @@
1
1
  # Browsertime changelog (we do [semantic versioning](https://semver.org))
2
2
 
3
+ ## 27.4.1 - 2026-05-25
4
+
5
+ ### Fixed
6
+ * `--chrome.coverage` no longer reports "0 B unused" for functions that executed at least once. The JS used-bytes calculation took the union of every V8 range with count > 0, but V8 returns nested ranges — an outer range over the whole function body and inner ranges over sub-blocks. If a function ran at all, its outer range already covered the whole body and the inner count == 0 ranges (the actual dead branches) got absorbed into the union and contributed nothing. On modern bundles where module-evaluation top-level code runs for almost every function, the per-script unused-% collapsed to roughly zero across the board, leaving sitespeed.io rendering a "0 B unused" column on pages that obviously ship plenty of dead code. The calculation now walks the ranges as nested intervals, painting the innermost range's count at every byte (the same definition Chrome DevTools' Coverage panel uses), so dead branches inside executed functions show up correctly. CSS rule-usage tracking returns flat, non-overlapping ranges and is unchanged [#2484](https://github.com/sitespeedio/browsertime/pull/2484).
7
+
3
8
  ## 27.4.0 - 2026-05-24
4
9
 
5
10
  ### Added
@@ -2,7 +2,8 @@ import { getLogger } from '@sitespeed.io/log';
2
2
 
3
3
  const log = getLogger('browsertime.chrome.coverage');
4
4
 
5
- // Sum the union length of half-open [start, end) ranges.
5
+ // Sum the union length of half-open [start, end) ranges. Used for the
6
+ // flat, non-nested ranges that CSS rule-usage tracking returns.
6
7
  function unionLength(ranges) {
7
8
  if (ranges.length === 0) return 0;
8
9
  const sorted = ranges
@@ -24,6 +25,45 @@ function unionLength(ranges) {
24
25
  return total;
25
26
  }
26
27
 
28
+ // Count used bytes for a script under detailed (block-level) V8
29
+ // coverage. The CDP returns nested ranges: the outermost range is the
30
+ // function body and inner ranges are sub-blocks. An inner range with
31
+ // count == 0 inside a containing range with count > 0 represents dead
32
+ // code in an otherwise-executed function — those bytes must read as
33
+ // unused. A naive union of every range whose count > 0 is wrong: the
34
+ // outer range alone covers the entire function, so inner count == 0
35
+ // ranges get masked and zero-count branches disappear, making typical
36
+ // modern bundles look as if every byte was executed. Walk every range
37
+ // across every function from outermost to innermost (start ascending,
38
+ // length descending), painting a per-byte coverage flag; inner ranges
39
+ // overwrite outer ones, so the final flag at each byte reflects the
40
+ // innermost range's count.
41
+ export function usedJsBytes(scriptCoverage, totalBytes) {
42
+ const ranges = [];
43
+ for (const fn of scriptCoverage.functions) {
44
+ for (const r of fn.ranges) {
45
+ if (r.endOffset > r.startOffset) ranges.push(r);
46
+ }
47
+ }
48
+ if (ranges.length === 0) return 0;
49
+ ranges.sort((a, b) =>
50
+ a.startOffset === b.startOffset
51
+ ? b.endOffset - a.endOffset
52
+ : a.startOffset - b.startOffset
53
+ );
54
+ const used = new Uint8Array(totalBytes);
55
+ for (const r of ranges) {
56
+ const start = Math.max(0, r.startOffset);
57
+ const end = Math.min(totalBytes, r.endOffset);
58
+ if (end > start) used.fill(r.count > 0 ? 1 : 0, start, end);
59
+ }
60
+ let count = 0;
61
+ for (let i = 0; i < totalBytes; i++) {
62
+ if (used[i] === 1) count++;
63
+ }
64
+ return count;
65
+ }
66
+
27
67
  function summarize(files) {
28
68
  let totalBytes = 0;
29
69
  let usedBytes = 0;
@@ -143,13 +183,7 @@ export class Coverage {
143
183
  const totalBytes = source.length;
144
184
  if (totalBytes === 0) continue;
145
185
 
146
- const usedRanges = [];
147
- for (const fn of script.functions) {
148
- for (const r of fn.ranges) {
149
- if (r.count > 0) usedRanges.push(r);
150
- }
151
- }
152
- const usedBytes = unionLength(usedRanges);
186
+ const usedBytes = usedJsBytes(script, totalBytes);
153
187
  const unusedBytes = totalBytes - usedBytes;
154
188
  files.push({
155
189
  url: script.url,
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "browsertime",
3
3
  "description": "Get performance metrics from your web page using Browsertime.",
4
- "version": "27.4.0",
4
+ "version": "27.4.1",
5
5
  "bin": "./bin/browsertime.js",
6
6
  "type": "module",
7
7
  "types": "./scripting.d.ts",