lighthouse 13.5.0-dev.20261019 → 13.5.0-dev.20261020

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.
@@ -1,4 +1,4 @@
1
- // Jest Snapshot v1, https://goo.gl/fbAQLP
1
+ // Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
2
2
 
3
3
  exports[`getAssertionReport works (multiple failing) 1`] = `
4
4
  "X difference at cumulative-layout-shift audit.details.items.length
@@ -18,7 +18,7 @@ const UIStrings = {
18
18
  */
19
19
  description:
20
20
  'Lists web features used on the page and their Baseline status as of {date}. ' +
21
- '[Learn more about Baseline](https://webstatus.dev/).',
21
+ '[Learn more about Baseline](https://developer.chrome.com/docs/lighthouse/best-practices/baseline-features).',
22
22
  /** Label for the column displaying the feature ID. */
23
23
  columnFeature: 'Web-features',
24
24
  /** Label for the column displaying the feature\'s baseline status. */
@@ -176,12 +176,23 @@ class ScriptTreemapDataAudit extends Audit {
176
176
  const nodes = [];
177
177
  /** @type {Map<string, LH.Treemap.Node>} */
178
178
  const htmlNodesByFrameId = new Map();
179
+ /** @type {Set<LH.Artifacts.NetworkRequest>} */
180
+ const seenNetworkRecords = new Set();
179
181
  const bundles = await JSBundles.request(artifacts, context);
180
182
  const duplicationByPath = await ModuleDuplication.request(artifacts, context);
181
183
 
182
184
  for (const script of artifacts.Scripts) {
183
185
  if (script.scriptLanguage !== 'JavaScript') continue;
184
186
 
187
+ const isInlineScript = isInline(script);
188
+ const networkRecord = isInlineScript ?
189
+ undefined :
190
+ getRequestForScript(networkRecords, script);
191
+ if (networkRecord) {
192
+ if (seenNetworkRecords.has(networkRecord)) continue;
193
+ seenNetworkRecords.add(networkRecord);
194
+ }
195
+
185
196
  const name = script.url;
186
197
  const bundle = bundles.find(bundle => script.scriptId === bundle.script.scriptId) ?? null;
187
198
  const scriptCoverage = /** @type {LH.Artifacts['JsUsage'][string] | undefined} */
@@ -246,7 +257,7 @@ class ScriptTreemapDataAudit extends Audit {
246
257
 
247
258
  // If this is an inline script, place the node inside a top-level (aka depth-one) node.
248
259
  // Also separate each iframe / the main page's inline scripts into their own top-level nodes.
249
- if (isInline(script)) {
260
+ if (isInlineScript) {
250
261
  let htmlNode = htmlNodesByFrameId.get(script.executionContextAuxData.frameId);
251
262
  if (!htmlNode) {
252
263
  htmlNode = {
@@ -269,7 +280,6 @@ class ScriptTreemapDataAudit extends Audit {
269
280
  // Non-inline scripts each have their own top-level node.
270
281
  nodes.push(node);
271
282
 
272
- const networkRecord = getRequestForScript(networkRecords, script);
273
283
  if (networkRecord) {
274
284
  const bodyTransferSize =
275
285
  networkRecord.transferSize - networkRecord.responseHeadersTransferSize;
@@ -11,6 +11,7 @@ import {
11
11
  getDevicePixelRatio,
12
12
  } from './driver/environment.js';
13
13
  import {isEqual} from '../lib/arbitrary-equality-map.js';
14
+ import {Sentry} from '../lib/sentry.js';
14
15
 
15
16
  /**
16
17
  * @param {LH.Config.ResolvedConfig} resolvedConfig
@@ -23,6 +24,11 @@ async function getBaseArtifacts(resolvedConfig, driver, context) {
23
24
  const {userAgent, product} = await getBrowserVersion(driver.defaultSession);
24
25
  const HostDPR = await getDevicePixelRatio(driver.executionContext);
25
26
 
27
+ const [name, version] = product.split('/');
28
+ if (name && version) {
29
+ Sentry.setContext('browser', {name, version});
30
+ }
31
+
26
32
  return {
27
33
  // Meta artifacts.
28
34
  fetchTime: new Date().toJSON(),
@@ -95,7 +95,7 @@ class FullPageScreenshot extends BaseGatherer {
95
95
  mobile: deviceMetrics.mobile,
96
96
  deviceScaleFactor: 1,
97
97
  height,
98
- width: 0, // Leave width unchanged
98
+ width: deviceMetrics.width,
99
99
  });
100
100
 
101
101
  // Now that the viewport is taller, give the page some time to fetch new resources that
@@ -235,7 +235,7 @@ class FullPageScreenshot extends BaseGatherer {
235
235
  mobile: deviceMetrics.mobile,
236
236
  deviceScaleFactor: deviceMetrics.deviceScaleFactor,
237
237
  height: deviceMetrics.height,
238
- width: 0, // Leave width unchanged
238
+ width: deviceMetrics.width,
239
239
  });
240
240
  }
241
241
  }
@@ -1,4 +1,3 @@
1
- export type LhlMessages = import("../../../shared/localization/locales").LhlMessages;
2
1
  export namespace UIStrings {
3
2
  let ms: string;
4
3
  let seconds: string;
@@ -4,12 +4,9 @@
4
4
  * SPDX-License-Identifier: Apache-2.0
5
5
  */
6
6
 
7
- /** @typedef {import('../../../shared/localization/locales').LhlMessages} LhlMessages */
8
-
9
7
  import path from 'path';
10
8
  import url from 'url';
11
9
 
12
- import lookupClosestLocale from 'lookup-closest-locale';
13
10
  import log from 'lighthouse-logger';
14
11
 
15
12
  import {getAvailableLocales} from '../../../shared/localization/format.js';
@@ -144,12 +141,22 @@ function lookupLocale(locales, possibleLocales) {
144
141
  // Filter by what's available in this runtime.
145
142
  const availableLocales = Intl.NumberFormat.supportedLocalesOf(canonicalLocales);
146
143
 
147
- // Get available locales and transform into object to match `lookupClosestLocale`'s API.
148
- const localesWithMessages = possibleLocales || getAvailableLocales();
149
- const localesWithmessagesObj = /** @type {Record<LH.Locale, LhlMessages>} */ (
150
- Object.fromEntries(localesWithMessages.map(l => [l, {}])));
151
-
152
- const closestLocale = lookupClosestLocale(availableLocales, localesWithmessagesObj);
144
+ const localesWithMessages = new Set(possibleLocales || getAvailableLocales());
145
+
146
+ /** @type {LH.Locale|undefined} */
147
+ let closestLocale;
148
+ for (const locale of availableLocales) {
149
+ const parts = locale.split('-');
150
+ while (parts.length) {
151
+ const candidate = /** @type {LH.Locale} */ (parts.join('-'));
152
+ if (localesWithMessages.has(candidate)) {
153
+ closestLocale = candidate;
154
+ break;
155
+ }
156
+ parts.pop();
157
+ }
158
+ if (closestLocale) break;
159
+ }
153
160
 
154
161
  if (!closestLocale) {
155
162
  // Log extra info if we're pretty sure this version of Node was built with `--with-intl=small-icu`.
@@ -3,6 +3,7 @@ export namespace Sentry {
3
3
  export { noop as captureMessage };
4
4
  export { noop as captureBreadcrumb };
5
5
  export { noop as getContext };
6
+ export { noop as setContext };
6
7
  export let captureException: (error: Error, options: {
7
8
  level?: string;
8
9
  tags?: {
@@ -32,6 +32,8 @@ const sentryDelegate = {
32
32
  captureBreadcrumb: noop,
33
33
  /** @type {() => any} */
34
34
  getContext: noop,
35
+ /** @type {(name: string, context: {[key: string]: any} | null) => void} */
36
+ setContext: noop,
35
37
  /** @type {(error: Error, options: {level?: string, tags?: {[key: string]: any}, extra?: {[key: string]: any}}) => Promise<void>} */
36
38
  captureException: async () => { },
37
39
  _shouldSample() {
@@ -88,6 +90,7 @@ async function init(opts) {
88
90
  sentryDelegate.captureMessage = (...args) => Sentry.captureMessage(...args);
89
91
  sentryDelegate.captureBreadcrumb = (...args) => Sentry.addBreadcrumb(...args);
90
92
  sentryDelegate.getContext = () => extras;
93
+ sentryDelegate.setContext = (...args) => Sentry.setContext(...args);
91
94
 
92
95
  // Keep a record of exceptions per audit/gatherer so we can just report once
93
96
  const sentryExceptionCache = new Map();
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "lighthouse",
3
3
  "type": "module",
4
- "version": "13.5.0-dev.20261019",
4
+ "version": "13.5.0-dev.20261020",
5
5
  "description": "Automated auditing, performance metrics, and best practices for the web.",
6
6
  "main": "./core/index.js",
7
7
  "bin": {
@@ -37,7 +37,7 @@
37
37
  "reset-link": "(yarn unlink || true) && yarn link && yarn link lighthouse",
38
38
  "clean": "rm -r dist proto/scripts/*.json proto/scripts/*_pb2.* proto/scripts/*_pb.* proto/scripts/__pycache__ proto/scripts/*.pyc *.report.html *.report.dom.html *.report.json *.devtoolslog.json *.trace.json shared/localization/locales/*.ctc.json || true",
39
39
  "clean-types": "git clean -xfq '*.d.ts' '*.d.cts' -e 'node_modules/' -e 'dist/' -e '.tmp/' -e '**/types/'",
40
- "lint": "[ \"$CI\" = true ] && eslint --quiet -f codeframe . || eslint .",
40
+ "lint": "[ \"$CI\" = true ] && eslint --quiet . || eslint .",
41
41
  "zizmor": "uvx zizmor@1.25.2 --gh-token=\"$(gh auth token)\" .github/workflows",
42
42
  "smoke": "node --enable-source-maps cli/test/smokehouse/frontends/smokehouse-bin.js",
43
43
  "debug": "node --inspect-brk ./cli/index.js",
@@ -102,7 +102,7 @@
102
102
  "devDependencies": {
103
103
  "@eslint/js": "^9.28.0",
104
104
  "@formatjs/icu-messageformat-parser": "^2.6.2",
105
- "@jest/fake-timers": "^29.7.0",
105
+ "@jest/fake-timers": "^30.5.2",
106
106
  "@testing-library/preact": "^3.1.1",
107
107
  "@testing-library/preact-hooks": "^1.1.0",
108
108
  "@types/archiver": "^8.0.0",
@@ -118,10 +118,7 @@
118
118
  "@types/pako": "^1.0.1",
119
119
  "@types/yargs": "^17.0.35",
120
120
  "@types/yargs-parser": "^21.0.3",
121
- "@typescript-eslint/eslint-plugin": "^8.60.1",
122
- "@typescript-eslint/parser": "^8.60.1",
123
121
  "acorn": "^8.5.0",
124
- "angular": "^1.7.4",
125
122
  "archiver": "^8.0.0",
126
123
  "chrome-devtools-frontend": "1.0.1705227",
127
124
  "concurrently": "^9.2.1",
@@ -130,15 +127,14 @@
130
127
  "csv-validator": "^0.0.3",
131
128
  "esbuild": "0.28.0",
132
129
  "eslint": "^9.28.0",
133
- "eslint-formatter-codeframe": "^7.32.1",
134
130
  "eslint-plugin-import": "^2.32.0",
135
- "expect": "^28.1.0",
131
+ "expect": "^30.5.2",
136
132
  "firebase": "^12.14.0",
137
133
  "gh-pages": "^2.0.1",
138
134
  "globals": "^15.14.0",
139
135
  "idb-keyval": "2.2.0",
140
- "jest-mock": "^29.7.0",
141
- "jest-snapshot": "^29.7.0",
136
+ "jest-mock": "^30.5.2",
137
+ "jest-snapshot": "^30.5.2",
142
138
  "jsdom": "^29.1.1",
143
139
  "lighthouse-plugin-soft-navigation": "^1.1.0",
144
140
  "magic-string": "^0.25.7",
@@ -148,12 +144,12 @@
148
144
  "preact": "^10.7.2",
149
145
  "pretty-json-stringify": "^0.0.2",
150
146
  "puppeteer": "^25.12.0",
151
- "rollup-plugin-polyfill-node": "^0.12.0",
152
147
  "terser": "^5.48.0",
153
148
  "testdouble": "^3.20.2",
154
149
  "tsx": "^4.23.15",
155
150
  "typed-query-selector": "^2.12.0",
156
151
  "typescript": "6.0.3",
152
+ "typescript-eslint": "^8.60.1",
157
153
  "wait-for-expect": "^3.0.2",
158
154
  "web-features": "^3.40.0",
159
155
  "webtreemap-cdt": "^3.2.1"
@@ -174,7 +170,6 @@
174
170
  "js-library-detector": "^6.7.0",
175
171
  "lighthouse-logger": "^2.0.2",
176
172
  "lighthouse-stack-packs": "1.12.3",
177
- "lookup-closest-locale": "6.2.0",
178
173
  "open": "^8.4.0",
179
174
  "puppeteer-core": "^25.12.0",
180
175
  "robots-parser": "^3.0.1",
@@ -708,7 +708,7 @@
708
708
  "message": "Baseline Status"
709
709
  },
710
710
  "core/audits/baseline.js | description": {
711
- "message": "Lists web features used on the page and their Baseline status as of {date}. [Learn more about Baseline](https://webstatus.dev/)."
711
+ "message": "Lists web features used on the page and their Baseline status as of {date}. [Learn more about Baseline](https://developer.chrome.com/docs/lighthouse/best-practices/baseline-features)."
712
712
  },
713
713
  "core/audits/baseline.js | title": {
714
714
  "message": "Baseline Features"
@@ -708,7 +708,7 @@
708
708
  "message": "B̂áŝél̂ín̂é Ŝt́ât́ûś"
709
709
  },
710
710
  "core/audits/baseline.js | description": {
711
- "message": "L̂íŝt́ŝ ẃêb́ f̂éât́ûŕêś ûśêd́ ôń t̂h́ê ṕâǵê án̂d́ t̂h́êír̂ B́âśêĺîńê śt̂át̂úŝ áŝ óf̂ {date}. [Ĺêár̂ń m̂ór̂é âb́ôút̂ B́âśêĺîńê](https://webstatus.dev/)."
711
+ "message": "L̂íŝt́ŝ ẃêb́ f̂éât́ûŕêś ûśêd́ ôń t̂h́ê ṕâǵê án̂d́ t̂h́êír̂ B́âśêĺîńê śt̂át̂úŝ áŝ óf̂ {date}. [Ĺêár̂ń m̂ór̂é âb́ôút̂ B́âśêĺîńê](https://developer.chrome.com/docs/lighthouse/best-practices/baseline-features)."
712
712
  },
713
713
  "core/audits/baseline.js | title": {
714
714
  "message": "B̂áŝél̂ín̂é F̂éât́ûŕêś"
package/tsconfig.json CHANGED
@@ -19,7 +19,6 @@
19
19
  "types.js",
20
20
  "./types/**/*.d.ts",
21
21
  "third-party/axe/valid-langs.js",
22
- "third-party/esbuild-plugins-polyfills/esbuild-polyfills.js",
23
22
  "third-party/ard/**/*.js",
24
23
 
25
24
  "core/test/results/sample_v2.json",
@@ -29,6 +29,7 @@ declare module 'expect' {
29
29
  */
30
30
  // tslint:disable-next-line: no-unnecessary-generics
31
31
  toMatchInlineSnapshot<U extends { [P in keyof T]: any }>(propertyMatchers: Partial<U>, snapshot?: string): R;
32
+ toMatchInlineSnapshot(snapshot?: string): R;
32
33
 
33
34
  // The following are custom matchers defined in expect-setup.js
34
35
 
@@ -1,3 +0,0 @@
1
- MIT
2
-
3
- https://github.com/remorses/esbuild-plugins/blob/d757c3bf5dff37a754d254168d4a3d88611e8175/package.json#L37
@@ -1,7 +0,0 @@
1
- # @esbuild-plugins/node-modules-polyfill
2
-
3
- This is `@esbuild-plugins/node-modules-polyfill`, with the following changes:
4
-
5
- - using a better library for polyfills as proposed by this PR ... https://github.com/remorses/esbuild-plugins/pull/19
6
- - ...but there are a couple correctness changes on top of the above PR, as described here: https://github.com/remorses/esbuild-plugins/pull/19#discussion_r990493080 (the PR has landed but is still broken for our use case)
7
- - File converted from TS to JS
@@ -1,9 +0,0 @@
1
- export function nodeModulesPolyfillPlugin(options?: {}): {
2
- name: any;
3
- setup: ({ onLoad, onResolve, initialOptions }: {
4
- onLoad: any;
5
- onResolve: any;
6
- initialOptions: any;
7
- }) => void;
8
- };
9
- //# sourceMappingURL=esbuild-polyfills.d.ts.map
@@ -1,129 +0,0 @@
1
- // @ts-nocheck
2
- /* eslint-disable */
3
-
4
- import path from 'path';
5
-
6
- import escapeStringRegexp from 'escape-string-regexp';
7
- import {getModules as builtinsPolyfills} from 'rollup-plugin-polyfill-node/dist/modules.js';
8
- import polyfillLib from 'rollup-plugin-polyfill-node/dist/polyfills.js';
9
-
10
- const NAME = 'node-modules-polyfills';
11
- const NAMESPACE = NAME;
12
-
13
- function removeEndingSlash(importee) {
14
- if (importee && importee.slice(-1) === '/') {
15
- importee = importee.slice(0, -1);
16
- }
17
- return importee;
18
- }
19
-
20
- function nodeModulesPolyfillPlugin(
21
- options = {}
22
- ) {
23
- const {namespace = NAMESPACE, name = NAME} = options;
24
- if (namespace.endsWith('commonjs')) {
25
- throw new Error(`namespace ${namespace} must not end with commonjs`);
26
- }
27
- // this namespace is needed to make ES modules expose their default export to require: require('assert') will give you import('assert').default
28
- const commonjsNamespace = namespace + '-commonjs';
29
- const polyfilledBuiltins = builtinsPolyfills();
30
- const polyfilledBuiltinsNames = [...polyfilledBuiltins.keys()];
31
-
32
- return {
33
- name,
34
- setup: function setup({onLoad, onResolve, initialOptions}) {
35
- // polyfills contain global keyword, it must be defined
36
- if (initialOptions?.define && !initialOptions.define?.global) {
37
- initialOptions.define['global'] = 'globalThis';
38
- } else if (!initialOptions?.define) {
39
- initialOptions.define = {global: 'globalThis'};
40
- }
41
-
42
- // TODO these polyfill module cannot import anything, is that ok?
43
- async function loader(
44
- args
45
- ) {
46
- try {
47
- const isCommonjs = args.namespace.endsWith('commonjs');
48
-
49
- const key = removeEndingSlash(args.path);
50
- const contents = polyfilledBuiltins.get(key) || polyfillLib[key + '.js'];
51
- const resolveDir = path.dirname(key);
52
-
53
- if (isCommonjs) {
54
- return {
55
- loader: 'js',
56
- contents: commonJsTemplate({
57
- importPath: args.path,
58
- }),
59
- resolveDir,
60
- };
61
- }
62
- return {
63
- loader: 'js',
64
- contents,
65
- resolveDir,
66
- };
67
- } catch (e) {
68
- console.error('node-modules-polyfill', e);
69
- return {
70
- contents: `export {}`,
71
- loader: 'js',
72
- };
73
- }
74
- }
75
- onLoad({filter: /.*/, namespace}, loader);
76
- onLoad({filter: /.*/, namespace: commonjsNamespace}, loader);
77
- const filter = new RegExp(
78
- polyfilledBuiltinsNames.map(escapeStringRegexp).join('|') // TODO builtins could end with slash, keep in mind in regex
79
- );
80
- async function resolver(args) {
81
- const ignoreRequire = args.namespace === commonjsNamespace;
82
-
83
- let key;
84
- if (args.path.startsWith('\0polyfill-node.')) {
85
- key = args.path.replace('\0polyfill-node.', '');
86
- } else {
87
- if (args.path.startsWith('.')) {
88
- key = path.join(path.dirname(args.importer), args.path);
89
- } else {
90
- key = args.path;
91
- }
92
- }
93
-
94
- if (!polyfilledBuiltins.has(key) && !polyfillLib[key + '.js']) {
95
- return;
96
- }
97
-
98
- const isCommonjs =
99
- !ignoreRequire && args.kind === 'require-call';
100
-
101
- return {
102
- namespace: isCommonjs ? commonjsNamespace : namespace,
103
- path: key,
104
- };
105
- }
106
- onResolve({filter}, resolver);
107
- onResolve({filter: /.*/, namespace}, resolver);
108
- },
109
- };
110
- }
111
-
112
- function commonJsTemplate({importPath}) {
113
- return `
114
- const polyfill = require('${importPath}')
115
-
116
- if (polyfill && polyfill.default) {
117
- module.exports = polyfill.default
118
- for (let k in polyfill) {
119
- module.exports[k] = polyfill[k]
120
- }
121
- } else if (polyfill) {
122
- module.exports = polyfill
123
- }
124
-
125
-
126
- `;
127
- }
128
-
129
- export {nodeModulesPolyfillPlugin};
@@ -1,13 +0,0 @@
1
- /**
2
- * @license
3
- * Copyright 2018 Google LLC
4
- * SPDX-License-Identifier: Apache-2.0
5
- */
6
-
7
- type Locale = import('../lhr/settings.js').Locale;
8
-
9
- declare module 'lookup-closest-locale' {
10
- function lookupClosestLocale(locale: string[]|string|undefined, available: Record<Locale, any>): Locale|undefined;
11
-
12
- export = lookupClosestLocale;
13
- }