lighthouse 11.1.0-dev.20231004 → 11.1.0-dev.20231006
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/dist/report/flow.js +15 -16
- package/package.json +3 -3
- package/readme.md +1 -1
- package/shared/localization/format.d.ts +21 -16
- package/shared/localization/format.js +73 -36
- package/shared/type-verifiers.d.ts +1 -1
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lighthouse",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "11.1.0-dev.
|
|
4
|
+
"version": "11.1.0-dev.20231006",
|
|
5
5
|
"description": "Automated auditing, performance metrics, and best practices for the web.",
|
|
6
6
|
"main": "./core/index.js",
|
|
7
7
|
"bin": {
|
|
@@ -98,6 +98,7 @@
|
|
|
98
98
|
},
|
|
99
99
|
"devDependencies": {
|
|
100
100
|
"@build-tracker/cli": "^1.0.0-beta.15",
|
|
101
|
+
"@formatjs/icu-messageformat-parser": "^2.6.2",
|
|
101
102
|
"@esbuild-kit/esm-loader": "^2.1.1",
|
|
102
103
|
"@esbuild-plugins/node-modules-polyfill": "^0.1.4",
|
|
103
104
|
"@jest/fake-timers": "^28.1.0",
|
|
@@ -152,7 +153,6 @@
|
|
|
152
153
|
"gh-pages": "^2.0.1",
|
|
153
154
|
"glob": "^7.1.3",
|
|
154
155
|
"idb-keyval": "2.2.0",
|
|
155
|
-
"intl-messageformat-parser": "^1.8.1",
|
|
156
156
|
"jest-mock": "^27.3.0",
|
|
157
157
|
"jest-snapshot": "^28.1.0",
|
|
158
158
|
"jsdom": "^12.2.0",
|
|
@@ -188,7 +188,7 @@
|
|
|
188
188
|
"devtools-protocol": "0.0.1200039",
|
|
189
189
|
"enquirer": "^2.3.6",
|
|
190
190
|
"http-link-header": "^1.1.1",
|
|
191
|
-
"intl-messageformat": "^
|
|
191
|
+
"intl-messageformat": "^10.5.3",
|
|
192
192
|
"jpeg-js": "^0.4.4",
|
|
193
193
|
"js-library-detector": "^6.7.0",
|
|
194
194
|
"lighthouse-logger": "^2.0.1",
|
package/readme.md
CHANGED
|
@@ -338,7 +338,7 @@ This section details services that have integrated Lighthouse data. If you're wo
|
|
|
338
338
|
|
|
339
339
|
* **[Treo](https://treo.sh)** - Treo is Lighthouse as a Service. It provides regression testing, geographical regions, custom networks, and integrations with GitHub & Slack. Treo is a paid product with plans for solo-developers and teams.
|
|
340
340
|
|
|
341
|
-
* **[PageVitals](https://pagevitals.com)** - PageVitals combines Lighthouse, CrUX and
|
|
341
|
+
* **[PageVitals](https://pagevitals.com)** - PageVitals combines Lighthouse, CrUX and field testing to monitor the performance of websites. See how your website performs over time and get alerted if it gets too slow. Drill down and find the real cause of any performance issue. PageVitals is a paid product with a free 14-day trial.
|
|
342
342
|
|
|
343
343
|
* **[Alertdesk](https://www.alertdesk.com/)** - Alertdesk is based on Lighthouse and helps you to keep track of your site’s quality & performance. Run daily quality & performance tests from both Mobile and Desktop and dive into the powerful & intuitive reports. You can also monitor your uptime (every minute - 24/7) & domain health. Alertdesk is a paid product with a free 14-day trial.
|
|
344
344
|
|
|
@@ -1,27 +1,25 @@
|
|
|
1
|
-
export type
|
|
2
|
-
export type ArgumentElement = import('intl-messageformat-parser').ArgumentElement;
|
|
1
|
+
export type MessageFormatElement = import('@formatjs/icu-messageformat-parser').MessageFormatElement;
|
|
3
2
|
export const DEFAULT_LOCALE: "en-US";
|
|
4
3
|
/** @param {string[]} pathInLHR */
|
|
5
4
|
export function _formatPathAsString(pathInLHR: string[]): string;
|
|
6
5
|
/**
|
|
7
|
-
* Function to retrieve all
|
|
8
|
-
*
|
|
9
|
-
* differs from
|
|
6
|
+
* Function to retrieve all elements from an ICU message AST that are associated
|
|
7
|
+
* with a named input, like '{varName}' or '{varName, number, bytes}'. This
|
|
8
|
+
* differs from literal message types which are just arbitrary text.
|
|
10
9
|
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
10
|
+
* This function recursively inspects plural elements for nested elements,
|
|
11
|
+
* but since the output is a Map they are deduplicated.
|
|
12
|
+
* e.g. "=1{hello {icu}} =other{hello {icu}}" will produce one element in the output,
|
|
13
|
+
* with "icu" as its key.
|
|
13
14
|
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
* the variable "icu" would appear twice if it wasn't de duplicated. And they cannot
|
|
17
|
-
* be stored in a set because they are not equal since their locations are different,
|
|
18
|
-
* thus they are stored via a Map keyed on the "id" which is the ICU varName.
|
|
15
|
+
* TODO: don't do that deduplication because messages within a plural message could be number
|
|
16
|
+
* messages with different styles.
|
|
19
17
|
*
|
|
20
|
-
* @param {Array<
|
|
21
|
-
* @param {Map<string,
|
|
22
|
-
* @return {Map<string,
|
|
18
|
+
* @param {Array<MessageFormatElement>} icuElements
|
|
19
|
+
* @param {Map<string, MessageFormatElement>} [customElements]
|
|
20
|
+
* @return {Map<string, MessageFormatElement>}
|
|
23
21
|
*/
|
|
24
|
-
export function collectAllCustomElementsFromICU(icuElements: Array<
|
|
22
|
+
export function collectAllCustomElementsFromICU(icuElements: Array<MessageFormatElement>, customElements?: Map<string, import("@formatjs/icu-messageformat-parser").MessageFormatElement> | undefined): Map<string, MessageFormatElement>;
|
|
25
23
|
/**
|
|
26
24
|
* Returns whether `icuMessageOrNot`` is an `LH.IcuMessage` instance.
|
|
27
25
|
* @param {unknown} icuMessageOrNot
|
|
@@ -103,4 +101,11 @@ export function getAvailableLocales(): Array<LH.Locale>;
|
|
|
103
101
|
* @return {Array<string>}
|
|
104
102
|
*/
|
|
105
103
|
export function getCanonicalLocales(): Array<string>;
|
|
104
|
+
/**
|
|
105
|
+
* Our strings were made when \ was the escape character, but now it is '. To avoid churn,
|
|
106
|
+
* let's convert to the new style in memory.
|
|
107
|
+
* @param {string} message
|
|
108
|
+
* @return {string}
|
|
109
|
+
*/
|
|
110
|
+
export function escapeIcuMessage(message: string): string;
|
|
106
111
|
//# sourceMappingURL=format.d.ts.map
|
|
@@ -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
|