lighthouse 13.5.0 → 13.6.0-dev.20261028
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/.agents/skills/update-ard-port/SKILL.md +171 -0
- package/.agents/skills/update-trace-engine/SKILL.md +154 -0
- package/cli/bin.d.ts +2 -2
- package/cli/bin.js +5 -4
- package/cli/cli-flags.d.ts +16 -1
- package/cli/cli-flags.js +53 -2
- package/cli/printer.js +5 -8
- package/cli/run.d.ts +5 -5
- package/cli/run.js +97 -47
- package/cli/sentry-prompt.js +3 -4
- package/cli/test/smokehouse/__snapshots__/report-assert-test.js.snap +1 -1
- package/cli/test/smokehouse/config/exclusions.js +3 -1
- package/cli/test/smokehouse/frontends/smokehouse-bin.js +5 -3
- package/cli/test/smokehouse/report-assert-test.js +65 -0
- package/cli/test/smokehouse/report-assert.js +24 -39
- package/cli/test/smokehouse/smokehouse-test.d.ts +2 -0
- package/cli/test/smokehouse/smokehouse-test.js +120 -0
- package/cli/test/smokehouse/smokehouse.js +37 -15
- package/cli/test/smokehouse/version-check-test.js +5 -0
- package/cli/test/smokehouse/version-check.d.ts +1 -1
- package/cli/test/smokehouse/version-check.js +1 -1
- package/commitlint.config.js +1 -1
- package/core/audits/accessibility/accesskeys.js +1 -1
- package/core/audits/accessibility/aria-allowed-attr.js +1 -1
- package/core/audits/accessibility/aria-allowed-role.js +1 -1
- package/core/audits/accessibility/aria-command-name.js +1 -1
- package/core/audits/accessibility/aria-conditional-attr.js +1 -1
- package/core/audits/accessibility/aria-deprecated-role.js +1 -1
- package/core/audits/accessibility/aria-dialog-name.js +1 -1
- package/core/audits/accessibility/aria-hidden-body.js +1 -1
- package/core/audits/accessibility/aria-hidden-focus.js +1 -1
- package/core/audits/accessibility/aria-input-field-name.js +1 -1
- package/core/audits/accessibility/aria-meter-name.js +1 -1
- package/core/audits/accessibility/aria-progressbar-name.js +1 -1
- package/core/audits/accessibility/aria-prohibited-attr.js +1 -1
- package/core/audits/accessibility/aria-required-attr.js +1 -1
- package/core/audits/accessibility/aria-required-children.js +1 -1
- package/core/audits/accessibility/aria-required-parent.js +1 -1
- package/core/audits/accessibility/aria-roles.js +1 -1
- package/core/audits/accessibility/aria-text.js +1 -1
- package/core/audits/accessibility/aria-toggle-field-name.js +1 -1
- package/core/audits/accessibility/aria-tooltip-name.js +1 -1
- package/core/audits/accessibility/aria-treeitem-name.js +1 -1
- package/core/audits/accessibility/aria-valid-attr-value.js +1 -1
- package/core/audits/accessibility/aria-valid-attr.js +1 -1
- package/core/audits/accessibility/autocomplete-valid.js +1 -1
- package/core/audits/accessibility/button-name.js +1 -1
- package/core/audits/accessibility/bypass.js +1 -1
- package/core/audits/accessibility/color-contrast.js +1 -1
- package/core/audits/accessibility/definition-list.js +1 -1
- package/core/audits/accessibility/dlitem.js +1 -1
- package/core/audits/accessibility/document-title.js +1 -1
- package/core/audits/accessibility/duplicate-id-aria.js +1 -1
- package/core/audits/accessibility/empty-heading.js +1 -1
- package/core/audits/accessibility/form-field-multiple-labels.js +1 -1
- package/core/audits/accessibility/frame-title.js +1 -1
- package/core/audits/accessibility/heading-order.js +1 -1
- package/core/audits/accessibility/html-has-lang.js +1 -1
- package/core/audits/accessibility/html-lang-valid.js +1 -1
- package/core/audits/accessibility/html-xml-lang-mismatch.js +1 -1
- package/core/audits/accessibility/identical-links-same-purpose.js +1 -1
- package/core/audits/accessibility/image-alt.js +1 -1
- package/core/audits/accessibility/image-redundant-alt.js +1 -1
- package/core/audits/accessibility/input-button-name.js +1 -1
- package/core/audits/accessibility/input-image-alt.js +1 -1
- package/core/audits/accessibility/label-content-name-mismatch.js +1 -1
- package/core/audits/accessibility/label.js +1 -1
- package/core/audits/accessibility/landmark-one-main.js +1 -1
- package/core/audits/accessibility/link-in-text-block.js +1 -1
- package/core/audits/accessibility/link-name.js +1 -1
- package/core/audits/accessibility/list.js +1 -1
- package/core/audits/accessibility/listitem.js +1 -1
- package/core/audits/accessibility/meta-refresh.js +1 -1
- package/core/audits/accessibility/meta-viewport.js +1 -1
- package/core/audits/accessibility/object-alt.js +1 -1
- package/core/audits/accessibility/presentation-role-conflict.js +1 -1
- package/core/audits/accessibility/select-name.js +1 -1
- package/core/audits/accessibility/skip-link.js +1 -1
- package/core/audits/accessibility/svg-img-alt.js +1 -1
- package/core/audits/accessibility/tabindex.js +1 -1
- package/core/audits/accessibility/table-duplicate-name.js +1 -1
- package/core/audits/accessibility/table-fake-caption.js +1 -1
- package/core/audits/accessibility/target-size.js +1 -1
- package/core/audits/accessibility/td-has-header.js +1 -1
- package/core/audits/accessibility/td-headers-attr.js +1 -1
- package/core/audits/accessibility/th-has-data-cells.js +1 -1
- package/core/audits/accessibility/valid-lang.js +1 -1
- package/core/audits/accessibility/video-caption.js +1 -1
- package/core/audits/agentic/ard-schema.d.ts +13 -0
- package/core/audits/agentic/ard-schema.js +81 -33
- package/core/audits/agentic/llms-txt.js +5 -2
- package/core/audits/baseline.d.ts +29 -7
- package/core/audits/baseline.js +19 -6
- package/core/audits/byte-efficiency/byte-efficiency-audit.js +2 -2
- package/core/audits/insights/insight-audit.d.ts +9 -0
- package/core/audits/insights/insight-audit.js +35 -28
- package/core/audits/long-tasks.js +2 -1
- package/core/audits/predictive-perf.js +2 -2
- package/core/audits/screenshot-thumbnails.d.ts +0 -13
- package/core/audits/screenshot-thumbnails.js +0 -45
- package/core/audits/script-treemap-data.js +26 -6
- package/core/audits/seo/robots-txt.js +3 -8
- package/core/audits/unsized-images.d.ts +0 -7
- package/core/audits/unsized-images.js +7 -26
- package/core/computed/lcp-image-record.d.ts +2 -23
- package/core/computed/lcp-image-record.js +3 -67
- package/core/computed/lcp-network-record.d.ts +24 -0
- package/core/computed/lcp-network-record.js +74 -0
- package/core/computed/metrics/lantern-metric.js +1 -1
- package/core/computed/metrics/lcp-breakdown.js +6 -6
- package/core/computed/metrics/time-to-first-byte.js +5 -3
- package/core/computed/navigation-insights.js +3 -2
- package/core/computed/page-dependency-graph.js +55 -5
- package/core/computed/speedline.js +20 -5
- package/core/computed/trace-engine-result.js +25 -5
- package/core/computed/unused-css.js +15 -2
- package/core/config/config-helpers.d.ts +5 -0
- package/core/config/config-helpers.js +17 -28
- package/core/config/config-plugin.d.ts +7 -0
- package/core/config/config-plugin.js +49 -0
- package/core/config/config.js +10 -2
- package/core/config/constants.d.ts +2 -28
- package/core/config/filters.js +1 -0
- package/core/config/lr-desktop-config.js +13 -0
- package/core/config/lr-mobile-config.js +13 -0
- package/core/gather/base-artifacts.js +7 -1
- package/core/gather/driver/storage.js +3 -2
- package/core/gather/gatherers/agentic/ard.d.ts +59 -4
- package/core/gather/gatherers/agentic/ard.js +218 -56
- package/core/gather/gatherers/full-page-screenshot.js +2 -2
- package/core/gather/gatherers/image-elements.d.ts +7 -0
- package/core/gather/gatherers/image-elements.js +75 -24
- package/core/gather/gatherers/inspector-issues.js +2 -0
- package/core/gather/gatherers/js-usage.js +2 -0
- package/core/gather/gatherers/scripts.d.ts +16 -3
- package/core/gather/gatherers/scripts.js +157 -14
- package/core/gather/gatherers/stylesheets.js +2 -0
- package/core/gather/gatherers/trace-elements.js +2 -1
- package/core/gather/gatherers/trace.d.ts +6 -0
- package/core/gather/gatherers/trace.js +56 -16
- package/core/index.d.ts +2 -0
- package/core/index.js +2 -0
- package/core/lib/arbitrary-equality-map.d.ts +14 -3
- package/core/lib/arbitrary-equality-map.js +113 -6
- package/core/lib/baseline/web-features-data.json +32 -11
- package/core/lib/baseline/web-features-metadata.json +1 -1
- package/core/lib/bf-cache-strings.js +237 -254
- package/core/lib/cdt/Common.d.ts +1 -1
- package/core/lib/cdt/Common.js +2 -2
- package/core/lib/cdt/Platform.d.ts +7 -7
- package/core/lib/cdt/Platform.js +11 -8
- package/core/lib/cdt/SDK.d.ts +5 -1
- package/core/lib/cdt/SDK.js +4 -2
- package/core/lib/cdt/generated/ParsedURL.d.ts +0 -1
- package/core/lib/cdt/generated/ParsedURL.js +3 -9
- package/core/lib/cdt/generated/SourceMap.d.ts +24 -25
- package/core/lib/cdt/generated/SourceMap.js +7 -15
- package/core/lib/deprecations-strings.d.ts +93 -88
- package/core/lib/deprecations-strings.js +20 -14
- package/core/lib/i18n/i18n.d.ts +2 -1
- package/core/lib/i18n/i18n.js +25 -13
- package/core/lib/legacy-javascript/legacy-javascript.d.ts +3 -2
- package/core/lib/legacy-javascript/legacy-javascript.js +235 -111
- package/core/lib/legacy-javascript/polyfill-graph-data.json +94 -89
- package/core/lib/legacy-javascript/polyfill-module-data.json +48 -7
- package/core/lib/minification-estimator.js +10 -10
- package/core/lib/network-request.d.ts +2 -0
- package/core/lib/network-request.js +21 -0
- package/core/lib/page-functions.js +2 -5
- package/core/lib/sentry.d.ts +1 -0
- package/core/lib/sentry.js +3 -0
- package/core/lib/trace-engine.d.ts +1 -0
- package/core/lib/trace-engine.js +2 -0
- package/core/runner.js +9 -3
- package/dist/report/bundle.esm.js +10 -10
- package/dist/report/flow.js +13 -13
- package/dist/report/standalone.js +5 -5
- package/flow-report/src/common.tsx +5 -2
- package/flow-report/src/i18n/i18n.d.ts +2 -0
- package/flow-report/src/summary/summary.tsx +9 -1
- package/flow-report/src/util.d.ts +1 -1
- package/package.json +62 -98
- package/readme.md +13 -18
- package/report/generator/file-namer.d.ts +1 -1
- package/report/generator/file-namer.js +5 -4
- package/report/generator/flow-report-assets.js +1 -3
- package/report/generator/report-assets.js +1 -2
- package/report/renderer/category-renderer.js +2 -2
- package/report/renderer/details-renderer.js +3 -2
- package/report/renderer/dom.d.ts +8 -2
- package/report/renderer/dom.js +29 -1
- package/report/renderer/element-screenshot-renderer.js +3 -0
- package/report/renderer/performance-category-renderer.js +3 -1
- package/report/renderer/report-renderer.js +4 -0
- package/report/renderer/report-utils.d.ts +1 -0
- package/report/renderer/report-utils.js +2 -0
- package/report/renderer/text-encoding.js +2 -2
- package/report/types/augment-dom.d.ts +4 -0
- package/shared/esm-utils.d.ts +0 -8
- package/shared/esm-utils.js +0 -18
- package/shared/localization/format.js +5 -3
- package/shared/localization/locales/ar-XB.json +157 -142
- package/shared/localization/locales/ar.json +157 -142
- package/shared/localization/locales/bg.json +154 -139
- package/shared/localization/locales/ca.json +157 -142
- package/shared/localization/locales/cs.json +148 -133
- package/shared/localization/locales/da.json +153 -138
- package/shared/localization/locales/de.json +152 -137
- package/shared/localization/locales/el.json +153 -138
- package/shared/localization/locales/en-GB.json +160 -145
- package/shared/localization/locales/en-US.json +239 -218
- package/shared/localization/locales/en-XL.json +239 -218
- package/shared/localization/locales/es-419.json +150 -135
- package/shared/localization/locales/es.json +149 -134
- package/shared/localization/locales/fi.json +151 -136
- package/shared/localization/locales/fil.json +154 -139
- package/shared/localization/locales/fr.json +150 -135
- package/shared/localization/locales/he.json +160 -145
- package/shared/localization/locales/hi.json +156 -141
- package/shared/localization/locales/hr.json +154 -139
- package/shared/localization/locales/hu.json +111 -96
- package/shared/localization/locales/id.json +153 -138
- package/shared/localization/locales/it.json +153 -138
- package/shared/localization/locales/ja.json +150 -135
- package/shared/localization/locales/ko.json +42 -27
- package/shared/localization/locales/lt.json +152 -137
- package/shared/localization/locales/lv.json +93 -78
- package/shared/localization/locales/nl.json +149 -134
- package/shared/localization/locales/no.json +152 -137
- package/shared/localization/locales/pl.json +153 -138
- package/shared/localization/locales/pt-PT.json +152 -137
- package/shared/localization/locales/pt.json +157 -142
- package/shared/localization/locales/ro.json +149 -134
- package/shared/localization/locales/ru.json +76 -61
- package/shared/localization/locales/sk.json +153 -138
- package/shared/localization/locales/sl.json +149 -134
- package/shared/localization/locales/sr-Latn.json +151 -136
- package/shared/localization/locales/sr.json +151 -136
- package/shared/localization/locales/sv.json +152 -137
- package/shared/localization/locales/ta.json +156 -141
- package/shared/localization/locales/te.json +157 -142
- package/shared/localization/locales/th.json +111 -96
- package/shared/localization/locales/tr.json +153 -138
- package/shared/localization/locales/uk.json +149 -134
- package/shared/localization/locales/vi.json +162 -147
- package/shared/localization/locales/zh-HK.json +150 -135
- package/shared/localization/locales/zh-TW.json +154 -139
- package/shared/localization/locales/zh.json +152 -137
- package/shared/localization/swap-locale.js +10 -5
- package/shared/root.js +1 -3
- package/third-party/ard/README.md +19 -10
- package/third-party/ard/ard-test.js +189 -266
- package/third-party/ard/ard.d.ts +18 -1
- package/third-party/ard/ard.js +282 -45
- package/third-party/ard/schema-validator.d.ts +14 -5
- package/third-party/ard/schema-validator.js +921 -1177
- package/third-party/ard/spec/schemas/ard-entry.schema.json +176 -0
- package/tsconfig.json +0 -2
- package/types/artifacts.d.ts +43 -7
- package/types/config.d.ts +6 -1
- package/types/externs.d.ts +4 -0
- package/types/internal/test.d.ts +1 -0
- package/build-tracker.config.js +0 -16
- package/core/lib/cdt/package.json +0 -4
- package/eslint-local-rules.d.cts +0 -9
- package/eslint.config.mjs +0 -242
- package/third-party/ard/spec/schemas/ai-catalog.schema.json +0 -292
- package/third-party/esbuild-plugins-polyfills/LICENSE +0 -3
- package/third-party/esbuild-plugins-polyfills/README.md +0 -7
- package/third-party/esbuild-plugins-polyfills/esbuild-polyfills.d.ts +0 -9
- package/third-party/esbuild-plugins-polyfills/esbuild-polyfills.js +0 -129
- package/types/internal/lookup-closest-locale.d.ts +0 -13
|
@@ -238,6 +238,21 @@ function getEffectiveSizingRule({attributesStyle, inlineStyle, matchedCSSRules},
|
|
|
238
238
|
return null;
|
|
239
239
|
}
|
|
240
240
|
|
|
241
|
+
/**
|
|
242
|
+
* @param {LH.Artifacts.ImageElement} element
|
|
243
|
+
* @return {boolean}
|
|
244
|
+
*/
|
|
245
|
+
function needsSourceRules(element) {
|
|
246
|
+
if (element.isInShadowDOM || element.isCss) return false;
|
|
247
|
+
if (
|
|
248
|
+
ImageElements.doesHtmlAttrProvideExplicitSize(element.attributeWidth) &&
|
|
249
|
+
ImageElements.doesHtmlAttrProvideExplicitSize(element.attributeHeight)
|
|
250
|
+
) {
|
|
251
|
+
return false;
|
|
252
|
+
}
|
|
253
|
+
return true;
|
|
254
|
+
}
|
|
255
|
+
|
|
241
256
|
/**
|
|
242
257
|
* @param {LH.Artifacts.ImageElement} element
|
|
243
258
|
* @return {number}
|
|
@@ -255,6 +270,28 @@ class ImageElements extends BaseGatherer {
|
|
|
255
270
|
supportedModes: ['snapshot', 'timespan', 'navigation'],
|
|
256
271
|
};
|
|
257
272
|
|
|
273
|
+
/**
|
|
274
|
+
* An img size attribute prevents layout shifts if it is a non-negative integer (incl zero!).
|
|
275
|
+
* @url https://html.spec.whatwg.org/multipage/embedded-content-other.html#dimension-attributes
|
|
276
|
+
* @param {string | null} attrValue
|
|
277
|
+
* @return {boolean}
|
|
278
|
+
*/
|
|
279
|
+
static doesHtmlAttrProvideExplicitSize(attrValue) {
|
|
280
|
+
if (!attrValue) return false;
|
|
281
|
+
|
|
282
|
+
// First, superweird edge case of using the positive-sign. The spec _sorta_ says it's valid...
|
|
283
|
+
// https://html.spec.whatwg.org/multipage/common-microsyntaxes.html#rules-for-parsing-integers
|
|
284
|
+
// > Otherwise, if the character is … (+): Advance position to the next character.
|
|
285
|
+
// > (The "+" is ignored, but it is not conforming.)
|
|
286
|
+
// lol. Irrelevant though b/c Chrome (at least) doesn't ignore. It rejects this as a non-conforming value.
|
|
287
|
+
if (attrValue.startsWith('+')) return false;
|
|
288
|
+
|
|
289
|
+
// parseInt isn't exactly the same as the html's spec for parsing integers, but it's close enough
|
|
290
|
+
// https://tc39.es/ecma262/#sec-parseint-string-radix
|
|
291
|
+
const int = parseInt(attrValue, 10);
|
|
292
|
+
return int >= 0;
|
|
293
|
+
}
|
|
294
|
+
|
|
258
295
|
constructor() {
|
|
259
296
|
super();
|
|
260
297
|
/** @type {Map<string, {naturalWidth: number, naturalHeight: number}>} */
|
|
@@ -322,24 +359,32 @@ class ImageElements extends BaseGatherer {
|
|
|
322
359
|
async collectExtraDetails(driver, elements) {
|
|
323
360
|
// Don't do more than 5s of this expensive devtools protocol work. See #11289
|
|
324
361
|
let reachedGatheringBudget = false;
|
|
325
|
-
setTimeout(_ => (reachedGatheringBudget = true), 5000);
|
|
362
|
+
const timeoutId = setTimeout(_ => (reachedGatheringBudget = true), 5000);
|
|
326
363
|
let skippedCount = 0;
|
|
327
364
|
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
365
|
+
try {
|
|
366
|
+
for (const element of elements) {
|
|
367
|
+
if (reachedGatheringBudget) {
|
|
368
|
+
skippedCount++;
|
|
369
|
+
continue;
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
// Need source rules to determine if sized via CSS (for unsized-images).
|
|
373
|
+
if (needsSourceRules(element)) {
|
|
374
|
+
await this.fetchSourceRules(
|
|
375
|
+
driver.defaultSession,
|
|
376
|
+
element.node.devtoolsNodePath,
|
|
377
|
+
element
|
|
378
|
+
);
|
|
379
|
+
}
|
|
380
|
+
// Images within `picture` behave strangely and natural size information isn't accurate,
|
|
381
|
+
// CSS images have no natural size information at all. Try to get the actual size if we can.
|
|
382
|
+
if (element.isPicture || element.isCss || element.srcset) {
|
|
383
|
+
await this.fetchElementWithSizeInformation(driver, element);
|
|
384
|
+
}
|
|
342
385
|
}
|
|
386
|
+
} finally {
|
|
387
|
+
clearTimeout(timeoutId);
|
|
343
388
|
}
|
|
344
389
|
|
|
345
390
|
if (reachedGatheringBudget) {
|
|
@@ -369,11 +414,14 @@ class ImageElements extends BaseGatherer {
|
|
|
369
414
|
],
|
|
370
415
|
});
|
|
371
416
|
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
417
|
+
const shouldFetchSourceRules = elements.some(needsSourceRules);
|
|
418
|
+
if (shouldFetchSourceRules) {
|
|
419
|
+
await Promise.all([
|
|
420
|
+
session.sendCommand('DOM.enable'),
|
|
421
|
+
session.sendCommand('CSS.enable'),
|
|
422
|
+
session.sendCommand('DOM.getDocument', {depth: -1, pierce: true}),
|
|
423
|
+
]);
|
|
424
|
+
}
|
|
377
425
|
|
|
378
426
|
// Spend our extra details budget on highest impact images.
|
|
379
427
|
// Our best approximation of impact without network records is to use pixel area.
|
|
@@ -381,11 +429,14 @@ class ImageElements extends BaseGatherer {
|
|
|
381
429
|
|
|
382
430
|
await this.collectExtraDetails(context.driver, elements);
|
|
383
431
|
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
432
|
+
if (shouldFetchSourceRules) {
|
|
433
|
+
await Promise.all([
|
|
434
|
+
session.sendCommand('DOM.disable'),
|
|
435
|
+
session.sendCommand('CSS.disable'),
|
|
436
|
+
]);
|
|
437
|
+
}
|
|
388
438
|
|
|
439
|
+
this._naturalSizeCache.clear();
|
|
389
440
|
return elements;
|
|
390
441
|
}
|
|
391
442
|
}
|
|
@@ -1,13 +1,22 @@
|
|
|
1
1
|
export default Scripts;
|
|
2
|
+
export type TraceScript = {
|
|
3
|
+
scriptId: string;
|
|
4
|
+
sourceText?: string | undefined;
|
|
5
|
+
chunks?: string[] | undefined;
|
|
6
|
+
url?: string | undefined;
|
|
7
|
+
sourceUrl?: string | undefined;
|
|
8
|
+
executionContextId?: number | undefined;
|
|
9
|
+
frame?: string | undefined;
|
|
10
|
+
};
|
|
2
11
|
/**
|
|
3
12
|
* @fileoverview Gets JavaScript file contents.
|
|
4
13
|
*/
|
|
5
14
|
declare class Scripts extends BaseGatherer {
|
|
6
15
|
static symbol: symbol;
|
|
16
|
+
/** @type {LH.Gatherer.GathererMeta<'Trace'>} */
|
|
17
|
+
meta: LH.Gatherer.GathererMeta<"Trace">;
|
|
7
18
|
/** @type {LH.Crdp.Debugger.ScriptParsedEvent[]} */
|
|
8
19
|
_scriptParsedEvents: LH.Crdp.Debugger.ScriptParsedEvent[];
|
|
9
|
-
/** @type {Array<string | undefined>} */
|
|
10
|
-
_scriptContents: Array<string | undefined>;
|
|
11
20
|
/**
|
|
12
21
|
* @param {LH.Crdp.Debugger.ScriptParsedEvent} params
|
|
13
22
|
*/
|
|
@@ -20,7 +29,11 @@ declare class Scripts extends BaseGatherer {
|
|
|
20
29
|
* @param {LH.Gatherer.Context} context
|
|
21
30
|
*/
|
|
22
31
|
stopInstrumentation(context: LH.Gatherer.Context): Promise<void>;
|
|
23
|
-
|
|
32
|
+
/**
|
|
33
|
+
* @param {LH.Gatherer.Context<'Trace'>} context
|
|
34
|
+
* @return {Promise<LH.Artifacts['Scripts']>}
|
|
35
|
+
*/
|
|
36
|
+
getArtifact(context: LH.Gatherer.Context<"Trace">): Promise<LH.Artifacts["Scripts"]>;
|
|
24
37
|
}
|
|
25
38
|
import BaseGatherer from '../base-gatherer.js';
|
|
26
39
|
//# sourceMappingURL=scripts.d.ts.map
|
|
@@ -5,19 +5,31 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import BaseGatherer from '../base-gatherer.js';
|
|
8
|
+
import Trace from './trace.js';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* @typedef {Object} TraceScript
|
|
12
|
+
* @property {string} scriptId
|
|
13
|
+
* @property {string} [sourceText]
|
|
14
|
+
* @property {string[]} [chunks]
|
|
15
|
+
* @property {string} [url]
|
|
16
|
+
* @property {string} [sourceUrl]
|
|
17
|
+
* @property {number} [executionContextId]
|
|
18
|
+
* @property {string} [frame]
|
|
19
|
+
*/
|
|
8
20
|
|
|
9
21
|
/**
|
|
10
22
|
* @template T, U
|
|
11
23
|
* @param {Array<T>} values
|
|
12
|
-
* @param {(value: T) => Promise<U>} promiseMapper
|
|
24
|
+
* @param {(value: T, index: number) => Promise<U>} promiseMapper
|
|
13
25
|
* @param {boolean} runInSeries
|
|
14
26
|
* @return {Promise<Array<U>>}
|
|
15
27
|
*/
|
|
16
28
|
async function runInSeriesOrParallel(values, promiseMapper, runInSeries) {
|
|
17
29
|
if (runInSeries) {
|
|
18
30
|
const results = [];
|
|
19
|
-
for (
|
|
20
|
-
const result = await promiseMapper(
|
|
31
|
+
for (let i = 0; i < values.length; i++) {
|
|
32
|
+
const result = await promiseMapper(values[i], i);
|
|
21
33
|
results.push(result);
|
|
22
34
|
}
|
|
23
35
|
return results;
|
|
@@ -27,6 +39,121 @@ async function runInSeriesOrParallel(values, promiseMapper, runInSeries) {
|
|
|
27
39
|
}
|
|
28
40
|
}
|
|
29
41
|
|
|
42
|
+
/**
|
|
43
|
+
* Extracts script source text and disambiguating metadata from trace rundown events,
|
|
44
|
+
* grouped by `scriptId`.
|
|
45
|
+
* @param {LH.TraceEvent[]} traceEvents
|
|
46
|
+
* @return {Map<string, TraceScript[]>}
|
|
47
|
+
*/
|
|
48
|
+
function collectTraceScripts(traceEvents) {
|
|
49
|
+
/** @type {Map<string, TraceScript>} */
|
|
50
|
+
const byIsolateAndScriptId = new Map();
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* @param {NonNullable<LH.TraceEvent['args']['data']>} data
|
|
54
|
+
* @return {TraceScript}
|
|
55
|
+
*/
|
|
56
|
+
const getOrCreateRecord = (data) => {
|
|
57
|
+
const scriptId = String(data.scriptId);
|
|
58
|
+
const key = `${data.isolate ?? ''}.${scriptId}`;
|
|
59
|
+
let record = byIsolateAndScriptId.get(key);
|
|
60
|
+
if (!record) {
|
|
61
|
+
record = {scriptId};
|
|
62
|
+
byIsolateAndScriptId.set(key, record);
|
|
63
|
+
}
|
|
64
|
+
return record;
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
for (const event of traceEvents) {
|
|
68
|
+
const data = event.args?.data;
|
|
69
|
+
if (!data || data.scriptId === undefined) continue;
|
|
70
|
+
|
|
71
|
+
if (event.cat === 'disabled-by-default-devtools.target-rundown') {
|
|
72
|
+
if (typeof data.frame === 'string') {
|
|
73
|
+
getOrCreateRecord(data).frame = data.frame;
|
|
74
|
+
}
|
|
75
|
+
} else if (event.cat === 'disabled-by-default-devtools.v8-source-rundown' &&
|
|
76
|
+
event.name === 'ScriptCatchup') {
|
|
77
|
+
const record = getOrCreateRecord(data);
|
|
78
|
+
if (typeof data.url === 'string') record.url = data.url;
|
|
79
|
+
if (typeof data.sourceUrl === 'string') record.sourceUrl = data.sourceUrl;
|
|
80
|
+
if (typeof data.executionContextId === 'number') {
|
|
81
|
+
record.executionContextId = data.executionContextId;
|
|
82
|
+
}
|
|
83
|
+
} else if (event.cat === 'disabled-by-default-devtools.v8-source-rundown-sources' &&
|
|
84
|
+
typeof data.sourceText === 'string') {
|
|
85
|
+
const record = getOrCreateRecord(data);
|
|
86
|
+
if (event.name === 'ScriptCatchup') {
|
|
87
|
+
record.sourceText = data.sourceText;
|
|
88
|
+
} else if (event.name === 'LargeScriptCatchup') {
|
|
89
|
+
if (!record.chunks || data.splitIndex === 0) record.chunks = [];
|
|
90
|
+
record.chunks.push(data.sourceText);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/** @type {Map<string, TraceScript[]>} */
|
|
96
|
+
const byScriptId = new Map();
|
|
97
|
+
for (const record of byIsolateAndScriptId.values()) {
|
|
98
|
+
if (record.chunks) record.sourceText = record.chunks.join('');
|
|
99
|
+
if (record.sourceText === undefined) continue;
|
|
100
|
+
|
|
101
|
+
let list = byScriptId.get(record.scriptId);
|
|
102
|
+
if (!list) {
|
|
103
|
+
list = [];
|
|
104
|
+
byScriptId.set(record.scriptId, list);
|
|
105
|
+
}
|
|
106
|
+
list.push(record);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return byScriptId;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* @param {TraceScript} traceScript
|
|
114
|
+
* @param {LH.Crdp.Debugger.ScriptParsedEvent} script
|
|
115
|
+
* @return {boolean}
|
|
116
|
+
*/
|
|
117
|
+
function matchesScriptParsedEvent(traceScript, script) {
|
|
118
|
+
const frameId = script.executionContextAuxData?.frameId;
|
|
119
|
+
if (frameId && traceScript.frame !== undefined && traceScript.frame !== frameId) {
|
|
120
|
+
return false;
|
|
121
|
+
}
|
|
122
|
+
const scriptUrl = script.embedderName || script.url;
|
|
123
|
+
if (traceScript.url !== undefined &&
|
|
124
|
+
traceScript.url !== scriptUrl &&
|
|
125
|
+
traceScript.sourceUrl !== script.url) {
|
|
126
|
+
return false;
|
|
127
|
+
}
|
|
128
|
+
if (script.executionContextId !== undefined &&
|
|
129
|
+
traceScript.executionContextId !== undefined &&
|
|
130
|
+
traceScript.executionContextId !== script.executionContextId) {
|
|
131
|
+
return false;
|
|
132
|
+
}
|
|
133
|
+
return true;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* @param {LH.Crdp.Debugger.ScriptParsedEvent} script
|
|
138
|
+
* @param {Map<string, TraceScript[]>} traceScriptsByScriptId
|
|
139
|
+
* @return {string|undefined}
|
|
140
|
+
*/
|
|
141
|
+
function findTraceScriptSource(script, traceScriptsByScriptId) {
|
|
142
|
+
const candidates = traceScriptsByScriptId.get(script.scriptId);
|
|
143
|
+
if (!candidates) return undefined;
|
|
144
|
+
|
|
145
|
+
const matches = candidates.filter((candidate) => matchesScriptParsedEvent(candidate, script));
|
|
146
|
+
if (matches.length === 1) return matches[0].sourceText;
|
|
147
|
+
|
|
148
|
+
const frameId = script.executionContextAuxData?.frameId;
|
|
149
|
+
if (matches.length > 1 && frameId) {
|
|
150
|
+
const frameMatches = matches.filter((candidate) => candidate.frame === frameId);
|
|
151
|
+
if (frameMatches.length === 1) return frameMatches[0].sourceText;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
return undefined;
|
|
155
|
+
}
|
|
156
|
+
|
|
30
157
|
/**
|
|
31
158
|
* Returns true if the script was created via our own calls
|
|
32
159
|
* to Runtime.evaluate.
|
|
@@ -50,18 +177,16 @@ function isLighthouseRuntimeEvaluateScript(script) {
|
|
|
50
177
|
class Scripts extends BaseGatherer {
|
|
51
178
|
static symbol = Symbol('Scripts');
|
|
52
179
|
|
|
53
|
-
/** @type {LH.Gatherer.GathererMeta} */
|
|
180
|
+
/** @type {LH.Gatherer.GathererMeta<'Trace'>} */
|
|
54
181
|
meta = {
|
|
55
182
|
symbol: Scripts.symbol,
|
|
56
183
|
supportedModes: ['timespan', 'navigation'],
|
|
184
|
+
dependencies: {Trace: Trace.symbol},
|
|
57
185
|
};
|
|
58
186
|
|
|
59
187
|
/** @type {LH.Crdp.Debugger.ScriptParsedEvent[]} */
|
|
60
188
|
_scriptParsedEvents = [];
|
|
61
189
|
|
|
62
|
-
/** @type {Array<string | undefined>} */
|
|
63
|
-
_scriptContents = [];
|
|
64
|
-
|
|
65
190
|
constructor() {
|
|
66
191
|
super();
|
|
67
192
|
this.onScriptParsed = this.onScriptParsed.bind(this);
|
|
@@ -89,26 +214,42 @@ class Scripts extends BaseGatherer {
|
|
|
89
214
|
* @param {LH.Gatherer.Context} context
|
|
90
215
|
*/
|
|
91
216
|
async stopInstrumentation(context) {
|
|
217
|
+
const session = context.driver.defaultSession;
|
|
218
|
+
session.off('Debugger.scriptParsed', this.onScriptParsed);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* @param {LH.Gatherer.Context<'Trace'>} context
|
|
223
|
+
* @return {Promise<LH.Artifacts['Scripts']>}
|
|
224
|
+
*/
|
|
225
|
+
async getArtifact(context) {
|
|
92
226
|
const session = context.driver.defaultSession;
|
|
93
227
|
const formFactor = context.baseArtifacts.HostFormFactor;
|
|
228
|
+
const traceScriptsByScriptId = collectTraceScripts(context.dependencies.Trace.traceEvents);
|
|
229
|
+
const traceSources = this._scriptParsedEvents.map(
|
|
230
|
+
(script) => findTraceScriptSource(script, traceScriptsByScriptId)
|
|
231
|
+
);
|
|
94
232
|
|
|
95
|
-
|
|
233
|
+
if (traceSources.some((source) => source === undefined)) {
|
|
234
|
+
// `disableAsyncStacks()` in `prepare.js` runs after `stopInstrumentation` and calls
|
|
235
|
+
// `Debugger.disable`, so re-enable `Debugger` if we need to call `Debugger.getScriptSource`.
|
|
236
|
+
await session.sendCommand('Debugger.enable');
|
|
237
|
+
}
|
|
96
238
|
|
|
97
239
|
// If run on a mobile device, be sensitive to memory limitations and only
|
|
98
240
|
// request one at a time.
|
|
99
|
-
|
|
241
|
+
const scriptContents = await runInSeriesOrParallel(
|
|
100
242
|
this._scriptParsedEvents,
|
|
101
|
-
(
|
|
102
|
-
return
|
|
243
|
+
(script, i) => {
|
|
244
|
+
if (traceSources[i] !== undefined) return Promise.resolve(traceSources[i]);
|
|
245
|
+
return session.sendCommand('Debugger.getScriptSource', {scriptId: script.scriptId})
|
|
103
246
|
.then((resp) => resp.scriptSource)
|
|
104
247
|
.catch(() => undefined);
|
|
105
248
|
},
|
|
106
249
|
formFactor === 'mobile' /* runInSeries */
|
|
107
250
|
);
|
|
108
251
|
await session.sendCommand('Debugger.disable');
|
|
109
|
-
}
|
|
110
252
|
|
|
111
|
-
async getArtifact() {
|
|
112
253
|
/** @type {LH.Artifacts['Scripts']} */
|
|
113
254
|
const scripts = this._scriptParsedEvents.map((event, i) => {
|
|
114
255
|
// 'embedderName' and 'url' are confusingly named, so we rewrite them here.
|
|
@@ -123,10 +264,12 @@ class Scripts extends BaseGatherer {
|
|
|
123
264
|
// embedderName is optional on the protocol because backends like Node may not set it.
|
|
124
265
|
// For our purposes, it is always set. But just in case it isn't... fallback to the url.
|
|
125
266
|
url: event.embedderName || event.url,
|
|
126
|
-
content:
|
|
267
|
+
content: scriptContents[i],
|
|
127
268
|
};
|
|
128
269
|
});
|
|
129
270
|
|
|
271
|
+
this._scriptParsedEvents = [];
|
|
272
|
+
|
|
130
273
|
return scripts;
|
|
131
274
|
}
|
|
132
275
|
}
|
|
@@ -82,6 +82,8 @@ class Stylesheets extends BaseGatherer {
|
|
|
82
82
|
|
|
83
83
|
// Ensure we finish fetching all stylesheet contents before disabling the CSS domain
|
|
84
84
|
const sheets = await Promise.all(this._sheetPromises.values());
|
|
85
|
+
this._sheetPromises.clear();
|
|
86
|
+
this._session = undefined;
|
|
85
87
|
|
|
86
88
|
await session.sendCommand('CSS.disable');
|
|
87
89
|
await session.sendCommand('DOM.disable');
|
|
@@ -21,6 +21,7 @@ import {ProcessedTrace} from '../../computed/processed-trace.js';
|
|
|
21
21
|
import {Responsiveness} from '../../computed/metrics/responsiveness.js';
|
|
22
22
|
import {CumulativeLayoutShift} from '../../computed/metrics/cumulative-layout-shift.js';
|
|
23
23
|
import {TraceEngineResult} from '../../computed/trace-engine-result.js';
|
|
24
|
+
import {Helpers} from '../../lib/trace-engine.js';
|
|
24
25
|
import SourceMaps from './source-maps.js';
|
|
25
26
|
|
|
26
27
|
/** @typedef {{nodeId: number, animations?: {name?: string, failureReasonsMask?: number, unsupportedProperties?: string[]}[], type?: string}} TraceElementData */
|
|
@@ -80,7 +81,7 @@ class TraceElements extends BaseGatherer {
|
|
|
80
81
|
}
|
|
81
82
|
|
|
82
83
|
if (navigationId) {
|
|
83
|
-
if (insightSet.navigation
|
|
84
|
+
if (Helpers.Trace.navigationIDForEvent(insightSet.navigation) !== navigationId) {
|
|
84
85
|
return [];
|
|
85
86
|
}
|
|
86
87
|
} else {
|
|
@@ -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
|
|
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
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
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
|
-
|
|
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
|
|
package/core/index.d.ts
CHANGED
|
@@ -68,6 +68,8 @@ export function auditFlowArtifacts(flowArtifacts: LH.UserFlow.FlowArtifacts, con
|
|
|
68
68
|
}>;
|
|
69
69
|
export function getAuditList(): string[];
|
|
70
70
|
export const traceCategories: string[];
|
|
71
|
+
import * as i18n from './lib/i18n/i18n.js';
|
|
71
72
|
import * as LH from '../types/lh.js';
|
|
72
73
|
import { UserFlow } from './user-flow.js';
|
|
74
|
+
export { i18n };
|
|
73
75
|
//# sourceMappingURL=index.d.ts.map
|
package/core/index.js
CHANGED
|
@@ -11,6 +11,7 @@ import {ReportGenerator} from '../report/generator/report-generator.js';
|
|
|
11
11
|
import {startTimespanGather} from './gather/timespan-runner.js';
|
|
12
12
|
import {snapshotGather} from './gather/snapshot-runner.js';
|
|
13
13
|
import {navigationGather} from './gather/navigation-runner.js';
|
|
14
|
+
import * as i18n from './lib/i18n/i18n.js';
|
|
14
15
|
import * as LH from '../types/lh.js';
|
|
15
16
|
|
|
16
17
|
/*
|
|
@@ -131,4 +132,5 @@ export {
|
|
|
131
132
|
auditFlowArtifacts,
|
|
132
133
|
getAuditList,
|
|
133
134
|
traceCategories,
|
|
135
|
+
i18n,
|
|
134
136
|
};
|
|
@@ -5,9 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
export class ArbitraryEqualityMap {
|
|
7
7
|
/**
|
|
8
|
-
* Determines whether two objects are deeply equal.
|
|
9
|
-
* easy usage by consumers.
|
|
10
|
-
* See https://lodash.com/docs/4.17.5#isEqual.
|
|
8
|
+
* Determines whether two objects are deeply equal.
|
|
11
9
|
* @param {*} objA
|
|
12
10
|
* @param {*} objB
|
|
13
11
|
* @return {boolean}
|
|
@@ -44,4 +42,17 @@ export class ArbitraryEqualityMap {
|
|
|
44
42
|
*/
|
|
45
43
|
_findIndexOf(key: any): number;
|
|
46
44
|
}
|
|
45
|
+
/**
|
|
46
|
+
* @license
|
|
47
|
+
* Copyright 2018 Google LLC
|
|
48
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
49
|
+
*/
|
|
50
|
+
/**
|
|
51
|
+
* @param {*} a
|
|
52
|
+
* @param {*} b
|
|
53
|
+
* @param {WeakMap<object, WeakSet<object>>=} seen
|
|
54
|
+
* @return {boolean}
|
|
55
|
+
*/
|
|
56
|
+
declare function deepEquals(a: any, b: any, seen?: WeakMap<object, WeakSet<object>> | undefined): boolean;
|
|
57
|
+
export { deepEquals as isEqual };
|
|
47
58
|
//# sourceMappingURL=arbitrary-equality-map.d.ts.map
|