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 +5 -0
- package/lib/chrome/coverage.js +42 -8
- package/package.json +1 -1
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
|
package/lib/chrome/coverage.js
CHANGED
|
@@ -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
|
|
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