lighthouse 11.1.0-dev.20231003 → 11.1.0-dev.20231005
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/core/audits/audit.js +17 -2
- package/core/audits/bf-cache.js +1 -0
- package/core/audits/bootup-time.js +4 -1
- package/core/audits/byte-efficiency/byte-efficiency-audit.js +1 -1
- package/core/audits/byte-efficiency/duplicated-javascript.js +1 -1
- package/core/audits/byte-efficiency/efficient-animated-content.js +1 -1
- package/core/audits/byte-efficiency/legacy-javascript.js +1 -1
- package/core/audits/byte-efficiency/modern-image-formats.js +1 -1
- package/core/audits/byte-efficiency/offscreen-images.js +1 -1
- package/core/audits/byte-efficiency/render-blocking-resources.js +2 -3
- package/core/audits/byte-efficiency/total-byte-weight.js +3 -1
- package/core/audits/byte-efficiency/unminified-css.js +1 -1
- package/core/audits/byte-efficiency/unminified-javascript.js +1 -1
- package/core/audits/byte-efficiency/unused-css-rules.js +1 -1
- package/core/audits/byte-efficiency/unused-javascript.js +1 -1
- package/core/audits/byte-efficiency/uses-long-cache-ttl.js +2 -7
- package/core/audits/byte-efficiency/uses-optimized-images.js +1 -1
- package/core/audits/byte-efficiency/uses-responsive-images-snapshot.js +1 -0
- package/core/audits/byte-efficiency/uses-responsive-images.js +1 -1
- package/core/audits/byte-efficiency/uses-text-compression.js +1 -1
- package/core/audits/dobetterweb/dom-size.js +3 -1
- package/core/audits/dobetterweb/no-document-write.js +1 -0
- package/core/audits/dobetterweb/uses-http2.js +2 -3
- package/core/audits/dobetterweb/uses-passive-event-listeners.js +1 -0
- package/core/audits/font-display.js +1 -0
- package/core/audits/largest-contentful-paint-element.js +3 -2
- package/core/audits/layout-shift-elements.js +8 -4
- package/core/audits/lcp-lazy-loaded.js +1 -0
- package/core/audits/long-tasks.js +8 -1
- package/core/audits/mainthread-work-breakdown.js +3 -1
- package/core/audits/prioritize-lcp-image.js +2 -3
- package/core/audits/redirects.js +2 -3
- package/core/audits/server-response-time.js +2 -0
- package/core/audits/third-party-facades.js +1 -0
- package/core/audits/third-party-summary.js +5 -1
- package/core/audits/unsized-images.js +1 -1
- package/core/audits/uses-rel-preconnect.js +2 -3
- package/core/audits/uses-rel-preload.js +2 -3
- package/core/audits/viewport.js +1 -0
- package/core/audits/work-during-interaction.js +6 -2
- package/core/gather/session.js +2 -0
- package/dist/report/bundle.esm.js +1 -1
- package/dist/report/flow.js +15 -16
- package/dist/report/standalone.js +1 -1
- package/package.json +6 -6
- package/readme.md +1 -1
- package/report/renderer/performance-category-renderer.js +15 -11
- package/shared/localization/format.d.ts +21 -16
- package/shared/localization/format.js +73 -36
- package/shared/type-verifiers.d.ts +1 -1
- package/types/audit.d.ts +2 -0
- package/types/internal/rxjs.d.ts +3 -0
- package/types/lhr/audit-result.d.ts +7 -0
|
@@ -12,6 +12,19 @@ import {getModuleDirectory} from '../esm-utils.js';
|
|
|
12
12
|
import {isObjectOfUnknownValues, isObjectOrArrayOfUnknownValues} from '../type-verifiers.js';
|
|
13
13
|
import {locales} from './locales.js';
|
|
14
14
|
|
|
15
|
+
// From @formatjs/icu-messageformat-parser - copy here so we don't need to bundle all that.
|
|
16
|
+
const TYPE = /** @type {const} */ ({
|
|
17
|
+
literal: 0,
|
|
18
|
+
argument: 1,
|
|
19
|
+
number: 2,
|
|
20
|
+
date: 3,
|
|
21
|
+
time: 4,
|
|
22
|
+
select: 5,
|
|
23
|
+
plural: 6,
|
|
24
|
+
pound: 7,
|
|
25
|
+
tag: 8,
|
|
26
|
+
});
|
|
27
|
+
|
|
15
28
|
const moduleDir = getModuleDirectory(import.meta);
|
|
16
29
|
|
|
17
30
|
/** Contains available locales with messages. May be an empty object if bundled. */
|
|
@@ -30,11 +43,11 @@ const CANONICAL_LOCALES = fs.readdirSync(moduleDir + '/locales/')
|
|
|
30
43
|
.map(locale => locale.replace('.json', ''))
|
|
31
44
|
.sort();
|
|
32
45
|
|
|
33
|
-
/** @typedef {import('
|
|
34
|
-
/** @typedef {import('intl-messageformat-parser').ArgumentElement} ArgumentElement */
|
|
46
|
+
/** @typedef {import('@formatjs/icu-messageformat-parser').MessageFormatElement} MessageFormatElement */
|
|
35
47
|
|
|
36
48
|
const MESSAGE_I18N_ID_REGEX = / | [^\s]+$/;
|
|
37
49
|
|
|
50
|
+
/** @type {Partial<import('intl-messageformat').Formats>} */
|
|
38
51
|
const formats = {
|
|
39
52
|
number: {
|
|
40
53
|
bytes: {
|
|
@@ -57,40 +70,39 @@ const formats = {
|
|
|
57
70
|
};
|
|
58
71
|
|
|
59
72
|
/**
|
|
60
|
-
* Function to retrieve all
|
|
61
|
-
*
|
|
62
|
-
* differs from
|
|
73
|
+
* Function to retrieve all elements from an ICU message AST that are associated
|
|
74
|
+
* with a named input, like '{varName}' or '{varName, number, bytes}'. This
|
|
75
|
+
* differs from literal message types which are just arbitrary text.
|
|
63
76
|
*
|
|
64
|
-
*
|
|
65
|
-
*
|
|
77
|
+
* This function recursively inspects plural elements for nested elements,
|
|
78
|
+
* but since the output is a Map they are deduplicated.
|
|
79
|
+
* e.g. "=1{hello {icu}} =other{hello {icu}}" will produce one element in the output,
|
|
80
|
+
* with "icu" as its key.
|
|
66
81
|
*
|
|
67
|
-
*
|
|
68
|
-
*
|
|
69
|
-
* the variable "icu" would appear twice if it wasn't de duplicated. And they cannot
|
|
70
|
-
* be stored in a set because they are not equal since their locations are different,
|
|
71
|
-
* thus they are stored via a Map keyed on the "id" which is the ICU varName.
|
|
82
|
+
* TODO: don't do that deduplication because messages within a plural message could be number
|
|
83
|
+
* messages with different styles.
|
|
72
84
|
*
|
|
73
|
-
* @param {Array<
|
|
74
|
-
* @param {Map<string,
|
|
75
|
-
* @return {Map<string,
|
|
85
|
+
* @param {Array<MessageFormatElement>} icuElements
|
|
86
|
+
* @param {Map<string, MessageFormatElement>} [customElements]
|
|
87
|
+
* @return {Map<string, MessageFormatElement>}
|
|
76
88
|
*/
|
|
77
|
-
function collectAllCustomElementsFromICU(icuElements,
|
|
89
|
+
function collectAllCustomElementsFromICU(icuElements, customElements = new Map()) {
|
|
78
90
|
for (const el of icuElements) {
|
|
79
|
-
|
|
80
|
-
if (el.type !== 'argumentElement') continue;
|
|
91
|
+
if (el.type === TYPE.literal || el.type === TYPE.pound) continue;
|
|
81
92
|
|
|
82
|
-
|
|
93
|
+
customElements.set(el.value, el);
|
|
83
94
|
|
|
84
95
|
// Plurals need to be inspected recursively
|
|
85
|
-
if (
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
96
|
+
if (el.type === TYPE.plural) {
|
|
97
|
+
// Look at all options of the plural (=1{} =other{}...)
|
|
98
|
+
for (const option of Object.values(el.options)) {
|
|
99
|
+
// Run collections on each option's elements
|
|
100
|
+
collectAllCustomElementsFromICU(option.value, customElements);
|
|
101
|
+
}
|
|
90
102
|
}
|
|
91
103
|
}
|
|
92
104
|
|
|
93
|
-
return
|
|
105
|
+
return customElements;
|
|
94
106
|
}
|
|
95
107
|
|
|
96
108
|
/**
|
|
@@ -103,15 +115,14 @@ function collectAllCustomElementsFromICU(icuElements, seenElementsById = new Map
|
|
|
103
115
|
* @return {Record<string, string | number>}
|
|
104
116
|
*/
|
|
105
117
|
function _preformatValues(messageFormatter, values = {}, lhlMessage) {
|
|
106
|
-
const
|
|
107
|
-
const argumentElements = [...elementMap.values()];
|
|
118
|
+
const customElements = collectAllCustomElementsFromICU(messageFormatter.getAst());
|
|
108
119
|
|
|
109
120
|
/** @type {Record<string, string | number>} */
|
|
110
121
|
const formattedValues = {};
|
|
111
122
|
|
|
112
|
-
for (const
|
|
123
|
+
for (const [id, element] of customElements) {
|
|
113
124
|
// Throw an error if a message's value isn't provided
|
|
114
|
-
if (
|
|
125
|
+
if (!(id in values)) {
|
|
115
126
|
throw new Error(`ICU Message "${lhlMessage}" contains a value reference ("${id}") ` +
|
|
116
127
|
`that wasn't provided`);
|
|
117
128
|
}
|
|
@@ -119,7 +130,7 @@ function _preformatValues(messageFormatter, values = {}, lhlMessage) {
|
|
|
119
130
|
const value = values[id];
|
|
120
131
|
|
|
121
132
|
// Direct `{id}` replacement and non-numeric values need no formatting.
|
|
122
|
-
if (
|
|
133
|
+
if (element.type !== TYPE.number) {
|
|
123
134
|
formattedValues[id] = value;
|
|
124
135
|
continue;
|
|
125
136
|
}
|
|
@@ -130,13 +141,13 @@ function _preformatValues(messageFormatter, values = {}, lhlMessage) {
|
|
|
130
141
|
}
|
|
131
142
|
|
|
132
143
|
// Format values for known styles.
|
|
133
|
-
if (
|
|
144
|
+
if (element.style === 'milliseconds') {
|
|
134
145
|
// Round all milliseconds to the nearest 10.
|
|
135
146
|
formattedValues[id] = Math.round(value / 10) * 10;
|
|
136
|
-
} else if (
|
|
147
|
+
} else if (element.style === 'seconds' && id === 'timeInMs') {
|
|
137
148
|
// Convert all seconds to the correct unit (currently only for `timeInMs`).
|
|
138
149
|
formattedValues[id] = Math.round(value / 100) / 10;
|
|
139
|
-
} else if (
|
|
150
|
+
} else if (element.style === 'bytes') {
|
|
140
151
|
// Replace all the bytes with KB.
|
|
141
152
|
formattedValues[id] = value / 1024;
|
|
142
153
|
} else {
|
|
@@ -162,6 +173,19 @@ function _preformatValues(messageFormatter, values = {}, lhlMessage) {
|
|
|
162
173
|
return formattedValues;
|
|
163
174
|
}
|
|
164
175
|
|
|
176
|
+
/**
|
|
177
|
+
* Our strings were made when \ was the escape character, but now it is '. To avoid churn,
|
|
178
|
+
* let's convert to the new style in memory.
|
|
179
|
+
* @param {string} message
|
|
180
|
+
* @return {string}
|
|
181
|
+
*/
|
|
182
|
+
function escapeIcuMessage(message) {
|
|
183
|
+
return message
|
|
184
|
+
.replace(/'/g, `''`)
|
|
185
|
+
.replace(/\\{/g, `'{`)
|
|
186
|
+
.replace(/\\}/g, `'}`);
|
|
187
|
+
}
|
|
188
|
+
|
|
165
189
|
/**
|
|
166
190
|
* Format string `message` by localizing `values` and inserting them. `message`
|
|
167
191
|
* is assumed to already be in the given locale.
|
|
@@ -172,19 +196,31 @@ function _preformatValues(messageFormatter, values = {}, lhlMessage) {
|
|
|
172
196
|
* @return {string}
|
|
173
197
|
*/
|
|
174
198
|
function formatMessage(message, values, locale) {
|
|
199
|
+
message = escapeIcuMessage(message);
|
|
200
|
+
|
|
175
201
|
// Parsing and formatting can be slow. Don't attempt if the string can't
|
|
176
202
|
// contain ICU placeholders, in which case formatting is already complete.
|
|
177
|
-
if (!message.includes('{') && values === undefined) return message;
|
|
178
203
|
|
|
179
204
|
// When using accented english, force the use of a different locale for number formatting.
|
|
180
205
|
const localeForMessageFormat = (locale === 'en-XA' || locale === 'en-XL') ? 'de-DE' : locale;
|
|
181
206
|
|
|
182
|
-
|
|
207
|
+
// This package is not correctly bundled.
|
|
208
|
+
/** @type {typeof IntlMessageFormat} */
|
|
209
|
+
// @ts-expect-error bundler woes
|
|
210
|
+
const IntlMessageFormatCtor = IntlMessageFormat.IntlMessageFormat || IntlMessageFormat;
|
|
211
|
+
const formatter = new IntlMessageFormatCtor(message, localeForMessageFormat, formats, {
|
|
212
|
+
ignoreTag: true,
|
|
213
|
+
});
|
|
183
214
|
|
|
184
215
|
// Preformat values for the message format like KB and milliseconds.
|
|
185
216
|
const valuesForMessageFormat = _preformatValues(formatter, values, message);
|
|
186
217
|
|
|
187
|
-
|
|
218
|
+
const formattedResult = formatter.format(valuesForMessageFormat);
|
|
219
|
+
// We only format to strings.
|
|
220
|
+
if (typeof formattedResult !== 'string') {
|
|
221
|
+
throw new Error('unexpected formatted result');
|
|
222
|
+
}
|
|
223
|
+
return formattedResult;
|
|
188
224
|
}
|
|
189
225
|
|
|
190
226
|
/**
|
|
@@ -459,4 +495,5 @@ export {
|
|
|
459
495
|
getIcuMessageIdParts,
|
|
460
496
|
getAvailableLocales,
|
|
461
497
|
getCanonicalLocales,
|
|
498
|
+
escapeIcuMessage,
|
|
462
499
|
};
|
|
@@ -19,5 +19,5 @@ export function isObjectOfUnknownValues(val: unknown): val is Record<string, unk
|
|
|
19
19
|
* @param {unknown} val
|
|
20
20
|
* @return {val is Record<string, unknown>|Array<unknown>}
|
|
21
21
|
*/
|
|
22
|
-
export function isObjectOrArrayOfUnknownValues(val: unknown): val is
|
|
22
|
+
export function isObjectOrArrayOfUnknownValues(val: unknown): val is Record<string, unknown> | unknown[];
|
|
23
23
|
//# sourceMappingURL=type-verifiers.d.ts.map
|
package/types/audit.d.ts
CHANGED
|
@@ -90,6 +90,8 @@ declare module Audit {
|
|
|
90
90
|
metricSavings?: MetricSavings;
|
|
91
91
|
/** Score details including p10 and median for calculating an audit's log-normal score. */
|
|
92
92
|
scoringOptions?: ScoreOptions;
|
|
93
|
+
/** A string identifying how the score should be interpreted for display. Overrides audit meta `scoreDisplayMode` if defined. */
|
|
94
|
+
scoreDisplayMode?: AuditResult.ScoreDisplayMode;
|
|
93
95
|
}
|
|
94
96
|
|
|
95
97
|
/** The Audit.Product type for audits that do not return a `numericValue`. */
|
package/types/internal/rxjs.d.ts
CHANGED
|
@@ -12,6 +12,13 @@ interface ScoreDisplayModes {
|
|
|
12
12
|
NUMERIC: 'numeric';
|
|
13
13
|
/** Pass/fail audit (0 and 1 are only possible scores). */
|
|
14
14
|
BINARY: 'binary';
|
|
15
|
+
/**
|
|
16
|
+
* Audit result score is determined by the metric savings and product score:
|
|
17
|
+
* 1 - audit passed based on product score
|
|
18
|
+
* 0.5 - audit failed and had no metric savings
|
|
19
|
+
* 0 - audit failed and had metric savings
|
|
20
|
+
*/
|
|
21
|
+
METRIC_SAVINGS: 'metricSavings';
|
|
15
22
|
/** The audit exists only to tell you to review something yourself. Score is null and should be ignored. */
|
|
16
23
|
MANUAL: 'manual';
|
|
17
24
|
/** The audit is an FYI only, and can't be interpreted as pass/fail. Score is null and should be ignored. */
|