lighthouse 13.5.0-dev.20261021 → 13.5.0-dev.20261022

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/cli/cli-flags.js CHANGED
@@ -232,7 +232,8 @@ function getYargsParser(manualArgv) {
232
232
  'output-path': {
233
233
  type: 'string',
234
234
  coerce: coerceOutputPath,
235
- describe: `The file path to output the results. Use 'stdout' to write to stdout.
235
+ describe: `The file or directory path to output the results. Use 'stdout' to write to stdout.
236
+ Directories in the path must already exist. If a directory is specified, the file name will be based on the test URL and date.
236
237
  If using JSON output, default is stdout.
237
238
  If using HTML or CSV output, default is a file in the working directory with a name based on the test URL and date.
238
239
  If using multiple outputs, --output-path is appended with the standard extension for each output type. "reports/my-run" -> "reports/my-run.report.html", "reports/my-run.report.json", etc.
@@ -66,8 +66,21 @@ class UnusedCSS {
66
66
  let usedUncompressedBytes = 0;
67
67
  const totalUncompressedBytes = stylesheetInfo.content.length;
68
68
 
69
- for (const usedRule of stylesheetInfo.usedRules) {
70
- usedUncompressedBytes += usedRule.endOffset - usedRule.startOffset;
69
+ // CSS rule usage tracking can return overlapping ranges (e.g. a nested rule inside its
70
+ // parent rule's range) or duplicate ranges (parent rules are emitted once per used child rule).
71
+ // Sort and merge overlapping ranges so used bytes are only counted once.
72
+ const usedRules = stylesheetInfo.usedRules
73
+ .slice()
74
+ .sort((a, b) => a.startOffset - b.startOffset);
75
+
76
+ let lastEndOffset = -1;
77
+ for (const usedRule of usedRules) {
78
+ if (usedRule.endOffset <= lastEndOffset) {
79
+ continue;
80
+ }
81
+ const startOffset = Math.max(usedRule.startOffset, lastEndOffset);
82
+ usedUncompressedBytes += usedRule.endOffset - startOffset;
83
+ lastEndOffset = usedRule.endOffset;
71
84
  }
72
85
 
73
86
  const compressedSize = estimateCompressedContentSize(
@@ -24,6 +24,19 @@ const config = {
24
24
  'bf-cache',
25
25
  ],
26
26
  },
27
+ audits: [
28
+ // Lightrider does not support WebSocket connections.
29
+ // See https://github.com/GoogleChrome/lighthouse/issues/12459
30
+ {
31
+ path: 'errors-in-console',
32
+ options: {
33
+ ignoredPatterns: [
34
+ 'ERR_BLOCKED_BY_CLIENT.Inspector',
35
+ /^WebSocket connection to 'wss?:\/\/.*' failed:/,
36
+ ],
37
+ },
38
+ },
39
+ ],
27
40
  };
28
41
 
29
42
  export default config;
@@ -24,6 +24,19 @@ const config = {
24
24
  'bf-cache',
25
25
  ],
26
26
  },
27
+ audits: [
28
+ // Lightrider does not support WebSocket connections.
29
+ // See https://github.com/GoogleChrome/lighthouse/issues/12459
30
+ {
31
+ path: 'errors-in-console',
32
+ options: {
33
+ ignoredPatterns: [
34
+ 'ERR_BLOCKED_BY_CLIENT.Inspector',
35
+ /^WebSocket connection to 'wss?:\/\/.*' failed:/,
36
+ ],
37
+ },
38
+ },
39
+ ],
27
40
  };
28
41
 
29
42
  export default config;
@@ -1,6 +1,12 @@
1
1
  export default Trace;
2
2
  declare class Trace extends BaseGatherer {
3
3
  static getDefaultTraceCategories(): string[];
4
+ /**
5
+ * @param {LH.Gatherer.ProtocolSession} session
6
+ * @param {string} handle
7
+ * @return {Promise<LH.Trace>}
8
+ */
9
+ static _readTraceFromStream(session: LH.Gatherer.ProtocolSession, handle: string): Promise<LH.Trace>;
4
10
  /**
5
11
  * @param {LH.Gatherer.ProtocolSession} session
6
12
  * @return {Promise<LH.Trace>}
@@ -73,29 +73,68 @@ class Trace extends BaseGatherer {
73
73
 
74
74
  /**
75
75
  * @param {LH.Gatherer.ProtocolSession} session
76
+ * @param {string} handle
76
77
  * @return {Promise<LH.Trace>}
77
78
  */
78
- static async endTraceAndCollectEvents(session) {
79
+ static async _readTraceFromStream(session, handle) {
79
80
  /** @type {Array<LH.TraceEvent>} */
80
81
  const traceEvents = [];
82
+ const decoder = new TextDecoder();
83
+ let isEof = false;
84
+ let remainder = '';
85
+
86
+ try {
87
+ while (!isEof) {
88
+ const response = await session.sendCommand('IO.read', {handle});
89
+ isEof = Boolean(response.eof);
90
+ remainder += response.base64Encoded ?
91
+ decoder.decode(Buffer.from(response.data, 'base64'), {stream: !isEof}) :
92
+ response.data;
93
+
94
+ // Perfetto's `ExportJson` serializes each trace event and trailing `metadata`
95
+ // as compact single-line JSON (with string newlines escaped as `\\n`), emitting
96
+ // raw `,\n` only between top-level elements of `traceEvents`.
97
+ // Maintain the invariant that `remainder` always starts with `{"traceEvents":[`.
98
+ const lastEventSep = remainder.lastIndexOf('},\n');
99
+ if (lastEventSep !== -1) {
100
+ const {traceEvents: batch} = JSON.parse(`${remainder.slice(0, lastEventSep + 1)}]}`);
101
+ for (const event of batch) {
102
+ traceEvents.push(event);
103
+ }
104
+ remainder = `{"traceEvents":[${remainder.slice(lastEventSep + 3)}`;
105
+ }
106
+ }
107
+ } finally {
108
+ await session.sendCommand('IO.close', {handle});
109
+ }
81
110
 
82
- /**
83
- * Listener for when dataCollected events fire for each trace chunk
84
- * @param {LH.Crdp.Tracing.DataCollectedEvent} data
85
- */
86
- const dataListener = function(data) {
87
- traceEvents.push(...data.value);
88
- };
89
- session.on('Tracing.dataCollected', dataListener);
90
-
91
- return new Promise((resolve, reject) => {
92
- session.once('Tracing.tracingComplete', _ => {
93
- session.off('Tracing.dataCollected', dataListener);
94
- resolve({traceEvents});
95
- });
111
+ const traceJson = JSON.parse(remainder);
112
+ for (const event of traceJson.traceEvents) {
113
+ traceEvents.push(event);
114
+ }
115
+ traceJson.traceEvents = traceEvents;
116
+ return traceJson;
117
+ }
96
118
 
97
- session.sendCommand('Tracing.end').catch(reject);
119
+ /**
120
+ * @param {LH.Gatherer.ProtocolSession} session
121
+ * @return {Promise<LH.Trace>}
122
+ */
123
+ static async endTraceAndCollectEvents(session) {
124
+ /** @type {LH.Crdp.Tracing.TracingCompleteEvent} */
125
+ const completeEvent = await new Promise((resolve, reject) => {
126
+ session.once('Tracing.tracingComplete', resolve);
127
+ session.sendCommand('Tracing.end').catch(err => {
128
+ session.off('Tracing.tracingComplete', resolve);
129
+ reject(err);
130
+ });
98
131
  });
132
+
133
+ if (!completeEvent.stream) {
134
+ throw new Error('Tracing.tracingComplete did not return a stream handle');
135
+ }
136
+
137
+ return Trace._readTraceFromStream(session, completeEvent.stream);
99
138
  }
100
139
 
101
140
  static symbol = Symbol('Trace');
@@ -115,6 +154,7 @@ class Trace extends BaseGatherer {
115
154
  await driver.defaultSession.sendCommand('Page.enable');
116
155
  await driver.defaultSession.sendCommand('Tracing.start', {
117
156
  categories: traceCategories.join(','),
157
+ transferMode: 'ReturnAsStream',
118
158
  options: 'sampling-frequency=10000', // 1000 is default and too slow.
119
159
  });
120
160
 
@@ -345,6 +345,9 @@ class NetworkRequest {
345
345
  this.networkEndTime = data.timestamp * 1000;
346
346
 
347
347
  this._updateResponseHeadersEndTimeIfNecessary();
348
+ this._updateProtocolForLightrider();
349
+ this._updateTransferSizeForLightrider();
350
+ this._updateTimingsForLightrider();
348
351
  }
349
352
 
350
353
  /**
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "lighthouse",
3
3
  "type": "module",
4
- "version": "13.5.0-dev.20261021",
4
+ "version": "13.5.0-dev.20261022",
5
5
  "description": "Automated auditing, performance metrics, and best practices for the web.",
6
6
  "main": "./core/index.js",
7
7
  "bin": {
package/readme.md CHANGED
@@ -112,7 +112,8 @@ Configuration:
112
112
 
113
113
  Output:
114
114
  --output Reporter for the results, supports multiple values. choices: "json", "html", "csv" [array] [default: ["html"]]
115
- --output-path The file path to output the results. Use 'stdout' to write to stdout.
115
+ --output-path The file or directory path to output the results. Use 'stdout' to write to stdout.
116
+ Directories in the path must already exist. If a directory is specified, the file name will be based on the test URL and date.
116
117
  If using JSON output, default is stdout.
117
118
  If using HTML or CSV output, default is a file in the working directory with a name based on the test URL and date.
118
119
  If using multiple outputs, --output-path is appended with the standard extension for each output type. "reports/my-run" -> "reports/my-run.report.html", "reports/my-run.report.json", etc.