webperf-snippets 1.7.0 → 1.7.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/README.md CHANGED
@@ -73,7 +73,7 @@ npx webperf-snippets https://example.com --json
73
73
  The JSON output records how the page was measured in `conditions`, so a later run can tell whether it measured the same way: the viewport, the workflow (or the path of the snippet with `--snippet`), the wait and the version of the CLI.
74
74
 
75
75
  ```json
76
- "conditions": { "viewport": "mobile", "workflow": "core-web-vitals", "waitMs": 3000, "cliVersion": "1.7.0" }
76
+ "conditions": { "viewport": "mobile", "workflow": "core-web-vitals", "waitMs": 3000, "cliVersion": "1.7.1" }
77
77
  ```
78
78
 
79
79
  Single snippet:
@@ -158,7 +158,7 @@ Without `--report-to` the CLI makes no external calls. With it, the results are
158
158
  "timestamp": "2026-05-06T10:00:00.000Z",
159
159
  "navMs": 1240,
160
160
  "results": [],
161
- "meta": { "viewport": "mobile", "waitMs": 3000, "cli_version": "1.7.0" }
161
+ "meta": { "viewport": "mobile", "waitMs": 3000, "cli_version": "1.7.1" }
162
162
  }
163
163
  ```
164
164
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "webperf-snippets",
3
- "version": "1.7.0",
3
+ "version": "1.7.1",
4
4
  "mcpName": "io.github.nucliweb/webperf-snippets",
5
5
  "description": "Run curated WebPerf Snippets headlessly via Playwright. Diagnose Core Web Vitals beyond what Lighthouse exposes.",
6
6
  "type": "module",
@@ -34,6 +34,14 @@ function uniqueSelector(el) {
34
34
  }
35
35
  // @end-shared uniqueSelector
36
36
 
37
+ // An element with content-visibility: auto has no size before it first renders unless
38
+ // contain-intrinsic-size gives one, so the page moves when it does. content-visibility: auto adds auto
39
+ // to contain-intrinsic-size, so Chrome reports an element with no size as "auto none", also when the CSS
40
+ // says none: what counts is whether the value has a length, as in "auto 500px".
41
+ function lacksIntrinsicSize(value) {
42
+ return !/\d/.test(value);
43
+ }
44
+
37
45
  function detectContentVisibility() {
38
46
  // Create an object to store the results
39
47
  const results = {
@@ -125,7 +133,7 @@ function detectContentVisibility() {
125
133
 
126
134
  // Check for missing contain-intrinsic-size
127
135
  const missingIntrinsicSize = results.autoElements.filter(
128
- (el) => el.containIntrinsicSize === "not set" || el.containIntrinsicSize === "none",
136
+ (el) => lacksIntrinsicSize(el.containIntrinsicSize),
129
137
  );
130
138
 
131
139
  if (missingIntrinsicSize.length > 0) {
@@ -327,7 +335,7 @@ window.analyzeContentVisibilityOpportunities = analyzeContentVisibilityOpportuni
327
335
  ],
328
336
  issues: [
329
337
  ...cvResults.autoElements
330
- .filter(el => el.containIntrinsicSize === "not set" || el.containIntrinsicSize === "none")
338
+ .filter(el => lacksIntrinsicSize(el.containIntrinsicSize))
331
339
  .map(el => ({ code: "missing-intrinsic-size", severity: "warning", message: `${el.selector}: missing contain-intrinsic-size (CLS risk)`, target: el.selector })),
332
340
  ...(cvResults.autoElements.length + cvResults.hiddenElements.length === 0
333
341
  ? [{ code: "not-used", severity: "info", message: "No content-visibility usage found. Consider content-visibility: auto on below-the-fold sections to reduce the initial render cost." }]
@@ -76,8 +76,13 @@
76
76
  }
77
77
  // @end-shared uniqueSelector
78
78
 
79
+ // An SVG scales to any size, so its pixels are not measured. The extension names most of them; the
80
+ // content type of the response names an SVG of the page served without it, such as an image service
81
+ // that takes the format in the query. The browser gives the content type only for a response it can
82
+ // read: an SVG of another origin without the extension is measured as a raster image.
83
+ const typeByUrl = new Map(performance.getEntriesByType("resource").map((e) => [e.name, e.contentType]));
79
84
  function isVector(url) {
80
- return /^data:image\/svg/i.test(url) || /\.svg([?#]|$)/i.test(url);
85
+ return /^data:image\/svg/i.test(url) || /\.svg([?#]|$)/i.test(url) || typeByUrl.get(url) === "image/svg+xml";
81
86
  }
82
87
 
83
88
  // The size of the file in pixels. HTMLImageElement.naturalWidth is corrected by the srcset
@@ -1137,7 +1137,7 @@ Returns buffered long tasks immediately. Ongoing tracking continues. `items` lis
1137
1137
 
1138
1138
  #### Oversized-Images (async)
1139
1139
 
1140
- Compares the pixels of each loaded image file with the pixels drawn (rendered size times `devicePixelRatio`, scaled by `object-fit`). `ratio` is file pixels per pixel drawn: `oversized` at 1.5 or more, `undersized` (blurry) under 0.9. `items` lists the flagged images, the most wasted pixels first (at most 50); `count` and `details` cover all of them. `wastedBytes` and `transferBytes` are `null` when the transfer size is hidden (cross-origin without `Timing-Allow-Origin`); `details.wastedBytes` counts each URL once. Images with a zero-size box, unloaded images and SVGs are counted in `details.skipped*`. `selector` matches only the image of its item, built like `details.element` of LCP, so an image with no id or class has a path such as `div.partners > img:nth-of-type(3)` instead of `img`.
1140
+ Compares the pixels of each loaded image file with the pixels drawn (rendered size times `devicePixelRatio`, scaled by `object-fit`). `ratio` is file pixels per pixel drawn: `oversized` at 1.5 or more, `undersized` (blurry) under 0.9. `items` lists the flagged images, the most wasted pixels first (at most 50); `count` and `details` cover all of them. `wastedBytes` and `transferBytes` are `null` when the transfer size is hidden (cross-origin without `Timing-Allow-Origin`); `details.wastedBytes` counts each URL once. Images with a zero-size box, unloaded images and SVGs are counted in `details.skipped*`; an SVG is known by its extension, a `data:image/svg` URL, or, for a file of the same origin, the `contentType` of its resource entry. `selector` matches only the image of its item, built like `details.element` of LCP, so an image with no id or class has a path such as `div.partners > img:nth-of-type(3)` instead of `img`.
1141
1141
 
1142
1142
  ```json
1143
1143
  {
@@ -1250,7 +1250,7 @@ An issue about one thing among several, one issue per thing, also names it in `t
1250
1250
 
1251
1251
  | Code | Severity | When |
1252
1252
  |------|----------|------|
1253
- | `missing-intrinsic-size` | warning | An element with `content-visibility: auto` and no `contain-intrinsic-size`; `target` is its selector |
1253
+ | `missing-intrinsic-size` | warning | An element with `content-visibility: auto` whose `contain-intrinsic-size` has no length, such as `none` or the `auto none` Chrome reports when no size is declared; `target` is its selector |
1254
1254
  | `not-used` | info | No `content-visibility` on the page |
1255
1255
 
1256
1256
  ### Critical-CSS-Detection