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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "lighthouse",
3
3
  "type": "module",
4
- "version": "11.1.0-dev.20231004",
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": "^4.4.0",
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 real-user monitoring data 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.
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 MessageElement = import('intl-messageformat-parser').Element;
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 'argumentElement's from an ICU message. An argumentElement
8
- * is an ICU element with an argument in it, like '{varName}' or '{varName, number, bytes}'. This
9
- * differs from 'messageElement's which are just arbitrary text in a message.
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
- * Notes:
12
- * This function will recursively inspect plural elements for nested argumentElements.
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
- * We need to find all the elements from the plural format sections, but
15
- * they need to be deduplicated. I.e. "=1{hello {icu}} =other{hello {icu}}"
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<MessageElement>} icuElements
21
- * @param {Map<string, ArgumentElement>} [seenElementsById]
22
- * @return {Map<string, ArgumentElement>}
18
+ * @param {Array<MessageFormatElement>} icuElements
19
+ * @param {Map<string, MessageFormatElement>} [customElements]
20
+ * @return {Map<string, MessageFormatElement>}
23
21
  */
24
- export function collectAllCustomElementsFromICU(icuElements: Array<MessageElement>, seenElementsById?: Map<string, import("intl-messageformat-parser").ArgumentElement> | undefined): Map<string, ArgumentElement>;
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('intl-messageformat-parser').Element} MessageElement */
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 'argumentElement's from an ICU message. An argumentElement
61
- * is an ICU element with an argument in it, like '{varName}' or '{varName, number, bytes}'. This
62
- * differs from 'messageElement's which are just arbitrary text in a message.
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
- * Notes:
65
- * This function will recursively inspect plural elements for nested argumentElements.
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
- * We need to find all the elements from the plural format sections, but
68
- * they need to be deduplicated. I.e. "=1{hello {icu}} =other{hello {icu}}"
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<MessageElement>} icuElements
74
- * @param {Map<string, ArgumentElement>} [seenElementsById]
75
- * @return {Map<string, ArgumentElement>}
85
+ * @param {Array<MessageFormatElement>} icuElements
86
+ * @param {Map<string, MessageFormatElement>} [customElements]
87
+ * @return {Map<string, MessageFormatElement>}
76
88
  */
77
- function collectAllCustomElementsFromICU(icuElements, seenElementsById = new Map()) {
89
+ function collectAllCustomElementsFromICU(icuElements, customElements = new Map()) {
78
90
  for (const el of icuElements) {
79
- // We are only interested in elements that need ICU formatting (argumentElements)
80
- if (el.type !== 'argumentElement') continue;
91
+ if (el.type === TYPE.literal || el.type === TYPE.pound) continue;
81
92
 
82
- seenElementsById.set(el.id, el);
93
+ customElements.set(el.value, el);
83
94
 
84
95
  // Plurals need to be inspected recursively
85
- if (!el.format || el.format.type !== 'pluralFormat') continue;
86
- // Look at all options of the plural (=1{} =other{}...)
87
- for (const option of el.format.options) {
88
- // Run collections on each option's elements
89
- collectAllCustomElementsFromICU(option.value.elements, seenElementsById);
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 seenElementsById;
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 elementMap = collectAllCustomElementsFromICU(messageFormatter.getAst().elements);
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 {id, format} of argumentElements) {
123
+ for (const [id, element] of customElements) {
113
124
  // Throw an error if a message's value isn't provided
114
- if (id && (id in values) === false) {
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 (!format || format.type !== 'numberFormat') {
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 (format.style === 'milliseconds') {
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 (format.style === 'seconds' && id === 'timeInMs') {
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 (format.style === 'bytes') {
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
- const formatter = new IntlMessageFormat(message, localeForMessageFormat, formats);
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
- return formatter.format(valuesForMessageFormat);
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 unknown[] | Record<string, unknown>;
22
+ export function isObjectOrArrayOfUnknownValues(val: unknown): val is Record<string, unknown> | unknown[];
23
23
  //# sourceMappingURL=type-verifiers.d.ts.map