From faee95c0ec3fe7c68c8ed9e7d942405c2222fa2b Mon Sep 17 00:00:00 2001 From: Paul Irish Date: Thu, 13 May 2021 12:58:31 -0700 Subject: [PATCH 1/5] squashed commit of 'mapping-atomic' branch. --- .../test/cli/__snapshots__/index-test.js.snap | 54 +++++++++++ lighthouse-core/config/default-config.js | 20 +++-- lighthouse-core/config/metrics-to-audits.js | 55 ++++++++++++ .../renderer/performance-category-renderer.js | 89 ++++++++++++++++--- lighthouse-core/report/html/report-styles.css | 37 ++++++++ .../test/config/default-config-test.js | 15 ++++ lighthouse-core/test/results/sample_v2.json | 70 +++++++++++++-- proto/lighthouse-result.proto | 8 +- types/config.d.ts | 2 + types/lhr.d.ts | 4 + 10 files changed, 323 insertions(+), 31 deletions(-) create mode 100644 lighthouse-core/config/metrics-to-audits.js diff --git a/lighthouse-cli/test/cli/__snapshots__/index-test.js.snap b/lighthouse-cli/test/cli/__snapshots__/index-test.js.snap index 2a0c8acd10a7..cc9bc18f322e 100644 --- a/lighthouse-cli/test/cli/__snapshots__/index-test.js.snap +++ b/lighthouse-cli/test/cli/__snapshots__/index-test.js.snap @@ -832,36 +832,89 @@ Object { "performance": Object { "auditRefs": Array [ Object { + "acronym": "FCP", "group": "metrics", "id": "first-contentful-paint", + "relevantAudits": Array [ + "server-response-time", + "render-blocking-resources", + "redirects", + "critical-request-chains", + "uses-text-compression", + "uses-rel-preconnect", + "uses-rel-preload", + "font-display", + "unminified-javascript", + "unminified-css", + "unused-css-rules", + ], "weight": 15, }, Object { + "acronym": "SI", "group": "metrics", "id": "speed-index", "weight": 15, }, Object { + "acronym": "LCP", "group": "metrics", "id": "largest-contentful-paint", + "relevantAudits": Array [ + "server-response-time", + "render-blocking-resources", + "redirects", + "critical-request-chains", + "uses-text-compression", + "uses-rel-preconnect", + "uses-rel-preload", + "font-display", + "unminified-javascript", + "unminified-css", + "unused-css-rules", + "largest-contentful-paint-element", + "preload-lcp-image", + "unused-javascript", + "efficient-animated-content", + "total-byte-weight", + ], "weight": 25, }, Object { + "acronym": "TTI", "group": "metrics", "id": "interactive", "weight": 15, }, Object { + "acronym": "TBT", "group": "metrics", "id": "total-blocking-time", + "relevantAudits": Array [ + "long-tasks", + "third-party-summary", + "third-party-facades", + "bootup-time", + "mainthread-work-breakdown", + "dom-size", + "duplicated-javascript", + "legacy-javascript", + ], "weight": 25, }, Object { + "acronym": "CLS", "group": "metrics", "id": "cumulative-layout-shift", + "relevantAudits": Array [ + "layout-shift-elements", + "non-composited-animations", + "unsized-images", + ], "weight": 5, }, Object { + "acronym": "FCI", "id": "first-cpu-idle", "weight": 0, }, @@ -870,6 +923,7 @@ Object { "weight": 0, }, Object { + "acronym": "FMP", "id": "first-meaningful-paint", "weight": 0, }, diff --git a/lighthouse-core/config/default-config.js b/lighthouse-core/config/default-config.js index 0ed6a9a214d8..d859948b7157 100644 --- a/lighthouse-core/config/default-config.js +++ b/lighthouse-core/config/default-config.js @@ -9,6 +9,7 @@ const constants = require('./constants.js'); const i18n = require('../lib/i18n/i18n.js'); +const m2a = require('./metrics-to-audits.js'); const UIStrings = { /** Title of the Performance category of audits. Equivalent to 'Web performance', this term is inclusive of all web page speed and loading optimization topics. Also used as a label of a score gauge; try to limit to 20 characters. */ @@ -420,16 +421,17 @@ const defaultConfig = { 'performance': { title: str_(UIStrings.performanceCategoryTitle), auditRefs: [ - {id: 'first-contentful-paint', weight: 15, group: 'metrics'}, - {id: 'speed-index', weight: 15, group: 'metrics'}, - {id: 'largest-contentful-paint', weight: 25, group: 'metrics'}, - {id: 'interactive', weight: 15, group: 'metrics'}, - {id: 'total-blocking-time', weight: 25, group: 'metrics'}, - {id: 'cumulative-layout-shift', weight: 5, group: 'metrics'}, - // intentionally left out of metrics group so they won't be displayed - {id: 'first-cpu-idle', weight: 0}, + {id: 'first-contentful-paint', weight: 15, group: 'metrics', acronym: 'FCP', relevantAudits: m2a.fcpRelevantAudits}, + {id: 'speed-index', weight: 15, group: 'metrics', acronym: 'SI'}, + {id: 'largest-contentful-paint', weight: 25, group: 'metrics', acronym: 'LCP', relevantAudits: m2a.lcpRelevantAudits}, + {id: 'interactive', weight: 15, group: 'metrics', acronym: 'TTI'}, + {id: 'total-blocking-time', weight: 25, group: 'metrics', acronym: 'TBT', relevantAudits: m2a.tbtRelevantAudits}, + {id: 'cumulative-layout-shift', weight: 5, group: 'metrics', acronym: 'CLS', relevantAudits: m2a.clsRelevantAudits}, + + // These are our "invisible" metrics. Not displayed, but still in the LHR + {id: 'first-cpu-idle', weight: 0, acronym: 'FCI'}, {id: 'max-potential-fid', weight: 0}, - {id: 'first-meaningful-paint', weight: 0}, + {id: 'first-meaningful-paint', weight: 0, acronym: 'FMP'}, {id: 'estimated-input-latency', weight: 0}, {id: 'render-blocking-resources', weight: 0, group: 'load-opportunities'}, diff --git a/lighthouse-core/config/metrics-to-audits.js b/lighthouse-core/config/metrics-to-audits.js new file mode 100644 index 000000000000..6b613e9a5b91 --- /dev/null +++ b/lighthouse-core/config/metrics-to-audits.js @@ -0,0 +1,55 @@ +/** + * @license Copyright 2021 The Lighthouse Authors. All Rights Reserved. + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. + */ +'use strict'; + +// go/lh-audit-metric-mapping +const fcpRelevantAudits = [ + 'server-response-time', + 'render-blocking-resources', + 'redirects', + 'critical-request-chains', + 'uses-text-compression', + 'uses-rel-preconnect', + 'uses-rel-preload', + 'font-display', + 'unminified-javascript', + 'unminified-css', + 'unused-css-rules', +]; + +const lcpRelevantAudits = [ + ...fcpRelevantAudits, + 'largest-contentful-paint-element', + 'preload-lcp-image', + 'unused-javascript', + 'efficient-animated-content', + 'total-byte-weight', +]; + +const tbtRelevantAudits = [ + 'long-tasks', + 'third-party-summary', + 'third-party-facades', + 'bootup-time', + 'mainthread-work-breakdown', + 'dom-size', + 'duplicated-javascript', + 'legacy-javascript', +]; + +const clsRelevantAudits = [ + 'layout-shift-elements', + 'non-composited-animations', + 'unsized-images', + // 'preload-fonts', // actually in BP, rather than perf +]; + +module.exports = { + fcpRelevantAudits, + lcpRelevantAudits, + tbtRelevantAudits, + clsRelevantAudits, +}; diff --git a/lighthouse-core/report/html/renderer/performance-category-renderer.js b/lighthouse-core/report/html/renderer/performance-category-renderer.js index 44dc59f5963d..3f0a6e27ae41 100644 --- a/lighthouse-core/report/html/renderer/performance-category-renderer.js +++ b/lighthouse-core/report/html/renderer/performance-category-renderer.js @@ -118,18 +118,6 @@ class PerformanceCategoryRenderer extends CategoryRenderer { if (fci) v5andv6metrics.push(fci); if (fmp) v5andv6metrics.push(fmp); - /** @type {Record} */ - const acronymMapping = { - 'cumulative-layout-shift': 'CLS', - 'first-contentful-paint': 'FCP', - 'first-cpu-idle': 'FCI', - 'first-meaningful-paint': 'FMP', - 'interactive': 'TTI', - 'largest-contentful-paint': 'LCP', - 'speed-index': 'SI', - 'total-blocking-time': 'TBT', - }; - /** * Clamp figure to 2 decimal places * @param {number} val @@ -147,7 +135,7 @@ class PerformanceCategoryRenderer extends CategoryRenderer { } else { value = 'null'; } - return [acronymMapping[audit.id] || audit.id, value]; + return [audit.acronym || audit.id, value]; }); const paramPairs = [...metricPairs]; @@ -225,6 +213,13 @@ class PerformanceCategoryRenderer extends CategoryRenderer { .filter(audit => audit.group === 'load-opportunities' && !Util.showAsPassed(audit.result)) .sort((auditA, auditB) => this._getWastedMs(auditB) - this._getWastedMs(auditA)); + + const filterableMetrics = metricAudits.filter(a => !!a.relevantAudits); + // TODO: only add if there are opportunities & diagnostics rendered. + if (filterableMetrics.length) { + this.renderMetricAuditFilter(filterableMetrics, element); + } + if (opportunityAudits.length) { // Scale the sparklines relative to savings, minimum 2s to not overstate small savings const minimumScale = 2000; @@ -299,6 +294,74 @@ class PerformanceCategoryRenderer extends CategoryRenderer { return element; } + + /** + * Render the control to filter the audits by metric. The filtering is done at runtime by CSS only + * @param {LH.ReportResult.AuditRef[]} filterableMetrics + * @param {HTMLDivElement} categoryEl + */ + renderMetricAuditFilter(filterableMetrics, categoryEl) { + // thx https://codepen.io/surjithctly/pen/weEJvX + const metricFilterEl = this.dom.createElement('div', 'lh-metricfilter'); + const textEl = this.dom.createChildOf(metricFilterEl, 'span', 'lh-metricfilter__text'); + textEl.textContent = 'Show audits relevant to: '; + const labelSelectors = []; + const auditSelectors = []; + + const filterChoices = /** @type {LH.ReportResult.AuditRef[]} */ ([ + ({acronym: 'All'}), + ...filterableMetrics + ]); + for (const metric of filterChoices) { + // The radio elements are appended into `categoryEl` to allow the sweet ~ selectors to work + const elemId = `metric-${metric.acronym}`; + const radioEl = this.dom.createChildOf(categoryEl, 'input', 'lh-metricfilter__radio', { + type: 'radio', + name: 'metricsfilter', + id: elemId, + hidden: 'true', + }); + const labelEl = this.dom.createChildOf(metricFilterEl, 'label', 'lh-metricfilter__label', { + for: elemId, + title: metric.result && metric.result.title, + }); + labelEl.textContent = metric.acronym || metric.id; + if (metric.acronym === 'All') { + radioEl.checked = true; + } + // Dynamically write some CSS, for the CSS-only filtering + labelSelectors.push(`.lh-metricfilter__radio#${elemId}:checked ~ .lh-metricfilter > .lh-metricfilter__label[for="${elemId}"]`); // eslint-disable-line max-len + if (metric.relevantAudits) { + /* Generate some CSS selectors like this: + #metric-CLS:checked ~ .lh-audit-group > #layout-shift-elements, + #metric-CLS:checked ~ .lh-audit-group > #non-composited-animations, + #metric-CLS:checked ~ .lh-audit-group > #unsized-images + */ + auditSelectors.push(metric.relevantAudits.map(auditId => `#${elemId}:checked ~ .lh-audit-group > #${auditId}`).join(',\n')); // eslint-disable-line max-len + } + } + + const styleEl = this.dom.createChildOf(metricFilterEl, 'style'); + // eslint-disable-next-line max-len + styleEl.textContent = ` +${labelSelectors.join(',\n')} { + background: var(--color-blue-A700); + color: var(--color-white); +} +/* If selecting non-All, hide all audits (and also the group header/description… */ +.lh-metricfilter__radio:checked:not(#metric-All) ~ .lh-audit-group .lh-audit, +.lh-metricfilter__radio:checked:not(#metric-All) ~ .lh-audit-group .lh-audit-group__description, +.lh-metricfilter__radio:checked:not(#metric-All) ~ .lh-audit-group .lh-audit-group__itemcount { + display: none; +} +/* …And then display:block the relevant ones */ +${auditSelectors.join(',\n')} { + display: block; +} +/*# sourceURL=metricfilter.css */ + `; + categoryEl.append(metricFilterEl); + } } if (typeof module !== 'undefined' && module.exports) { diff --git a/lighthouse-core/report/html/report-styles.css b/lighthouse-core/report/html/report-styles.css index 18107a56fdbb..7a7e1d1a8084 100644 --- a/lighthouse-core/report/html/report-styles.css +++ b/lighthouse-core/report/html/report-styles.css @@ -40,6 +40,7 @@ --color-blue-A700: #2962FF; --color-cyan-500: #00BCD4; --color-gray-100: #F5F5F5; + --color-gray-300: #CFCFCF; --color-gray-200: #E0E0E0; --color-gray-400: #BDBDBD; --color-gray-50: #FAFAFA; @@ -171,6 +172,7 @@ .lh-vars.dark { /* Pallete */ --color-gray-200: var(--color-gray-800); + --color-gray-300: #616161; --color-gray-400: var(--color-gray-600); --color-gray-700: var(--color-gray-400); --color-gray-50: #757575; @@ -567,6 +569,41 @@ display: block; } + +.lh-metricfilter { + text-align: right; + margin-top: var(--default-padding); +} + +.lh-metricfilter__label { + border: solid 1px var(--color-gray-400); + align-items: center; + justify-content: center; + padding: 2px 5px; + width: 50%; + height: 28px; + cursor: pointer; + font-size: 90%; +} + +.lh-metricfilter__label:first-of-type { + border-top-left-radius: 5px; + border-bottom-left-radius: 5px; +} +.lh-metricfilter__label:last-of-type { + border-top-right-radius: 5px; + border-bottom-right-radius: 5px; +} + +/* Give the 'All' choice a more muted display */ +.lh-metricfilter__radio#metric-All:checked ~ .lh-metricfilter > .lh-metricfilter__label[for="metric-All"] { + background-color: var(--color-blue-200) !important; + color: black !important; +} + + +/* checked styles are generated by JS */ + .lh-audit__header:hover { background-color: var(--color-hover); } diff --git a/lighthouse-core/test/config/default-config-test.js b/lighthouse-core/test/config/default-config-test.js index 89a372672af4..169310670e02 100644 --- a/lighthouse-core/test/config/default-config-test.js +++ b/lighthouse-core/test/config/default-config-test.js @@ -41,4 +41,19 @@ describe('Default Config', () => { `${auditResult.id} has an undefined overallSavingsMs`); }); }); + + it('relevantAudits map to existing perf audit', () => { + const metricsWithRelevantAudits = defaultConfig.categories.performance.auditRefs.filter(a => + a.relevantAudits); + const allPerfAuditIds = defaultConfig.categories.performance.auditRefs.map(a => a.id); + + for (const metric of metricsWithRelevantAudits) { + assert.ok(Array.isArray(metric.relevantAudits) && metric.relevantAudits.length); + + for (const auditid of metric.relevantAudits) { + const errMsg = `(${auditid}) is relevant audit for (${metric.id}), but no audit found.`; + assert.ok(allPerfAuditIds.includes(auditid), errMsg); + } + } + }); }); diff --git a/lighthouse-core/test/results/sample_v2.json b/lighthouse-core/test/results/sample_v2.json index ae4ee1fe35d3..ab539a1c21b1 100644 --- a/lighthouse-core/test/results/sample_v2.json +++ b/lighthouse-core/test/results/sample_v2.json @@ -4876,36 +4876,89 @@ { "id": "first-contentful-paint", "weight": 15, - "group": "metrics" + "group": "metrics", + "acronym": "FCP", + "relevantAudits": [ + "server-response-time", + "render-blocking-resources", + "redirects", + "critical-request-chains", + "uses-text-compression", + "uses-rel-preconnect", + "uses-rel-preload", + "font-display", + "unminified-javascript", + "unminified-css", + "unused-css-rules" + ] }, { "id": "speed-index", "weight": 15, - "group": "metrics" + "group": "metrics", + "acronym": "SI" }, { "id": "largest-contentful-paint", "weight": 25, - "group": "metrics" + "group": "metrics", + "acronym": "LCP", + "relevantAudits": [ + "server-response-time", + "render-blocking-resources", + "redirects", + "critical-request-chains", + "uses-text-compression", + "uses-rel-preconnect", + "uses-rel-preload", + "font-display", + "unminified-javascript", + "unminified-css", + "unused-css-rules", + "largest-contentful-paint-element", + "preload-lcp-image", + "unused-javascript", + "efficient-animated-content", + "total-byte-weight" + ] }, { "id": "interactive", "weight": 15, - "group": "metrics" + "group": "metrics", + "acronym": "TTI" }, { "id": "total-blocking-time", "weight": 25, - "group": "metrics" + "group": "metrics", + "acronym": "TBT", + "relevantAudits": [ + "long-tasks", + "third-party-summary", + "third-party-facades", + "bootup-time", + "mainthread-work-breakdown", + "dom-size", + "duplicated-javascript", + "legacy-javascript" + ] }, { "id": "cumulative-layout-shift", "weight": 5, - "group": "metrics" + "group": "metrics", + "acronym": "CLS", + "relevantAudits": [ + "layout-shift-elements", + "non-composited-animations", + "unsized-images" + ] }, { "id": "first-cpu-idle", - "weight": 0 + "weight": 0, + "acronym": "FCI" }, { "id": "max-potential-fid", @@ -4913,7 +4966,8 @@ }, { "id": "first-meaningful-paint", - "weight": 0 + "weight": 0, + "acronym": "FMP" }, { "id": "estimated-input-latency", diff --git a/proto/lighthouse-result.proto b/proto/lighthouse-result.proto index 3388ec3e1cf4..9cedf7f4b988 100644 --- a/proto/lighthouse-result.proto +++ b/proto/lighthouse-result.proto @@ -233,8 +233,14 @@ message LhrCategory { // The weight of the audit's score in the overall category score. google.protobuf.DoubleValue weight = 2; - // The category group that the audit belongs to + // The category group that the audit belongs to. string group = 3; + + // The conventional acronym for the audit/metric. + string acronym = 4; + + // Any audit IDs closely relevant to this one. + repeated string relevant_audits = 5; } // References to all the audit members and their weight in this category. diff --git a/types/config.d.ts b/types/config.d.ts index 6d7f7c99f197..2bc9212e5511 100644 --- a/types/config.d.ts +++ b/types/config.d.ts @@ -146,6 +146,8 @@ declare global { id: string; weight: number; group?: string; + relevantAudits?: string[]; + acronym?: string; } export interface Settings extends Required { diff --git a/types/lhr.d.ts b/types/lhr.d.ts index e01548a0a83f..0f3736cbefc6 100644 --- a/types/lhr.d.ts +++ b/types/lhr.d.ts @@ -87,6 +87,10 @@ declare global { weight: number; /** Optional grouping within the category. Matches the key of a Result.Group. */ group?: string; + /** Any audit IDs closely relevant to this one. */ + relevantAudits?: string[]; + /** The conventional acronym for the audit/metric. */ + acronym?: string; } export interface ReportGroup { From 3bb719a7e5f3f2aa2c44065a62ae5dea51280dfb Mon Sep 17 00:00:00 2001 From: Paul Irish Date: Thu, 13 May 2021 13:50:54 -0700 Subject: [PATCH 2/5] lint --- .../report/html/renderer/performance-category-renderer.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lighthouse-core/report/html/renderer/performance-category-renderer.js b/lighthouse-core/report/html/renderer/performance-category-renderer.js index 3f0a6e27ae41..60ce9cb41dd9 100644 --- a/lighthouse-core/report/html/renderer/performance-category-renderer.js +++ b/lighthouse-core/report/html/renderer/performance-category-renderer.js @@ -310,7 +310,7 @@ class PerformanceCategoryRenderer extends CategoryRenderer { const filterChoices = /** @type {LH.ReportResult.AuditRef[]} */ ([ ({acronym: 'All'}), - ...filterableMetrics + ...filterableMetrics, ]); for (const metric of filterChoices) { // The radio elements are appended into `categoryEl` to allow the sweet ~ selectors to work From ed3d2c95bdd41c6cccccd5312d2da99a60115b25 Mon Sep 17 00:00:00 2001 From: Paul Irish Date: Fri, 14 May 2021 13:49:43 -0700 Subject: [PATCH 3/5] order swap --- types/config.d.ts | 2 +- types/lhr.d.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/types/config.d.ts b/types/config.d.ts index 2bc9212e5511..6814557f4a5f 100644 --- a/types/config.d.ts +++ b/types/config.d.ts @@ -146,8 +146,8 @@ declare global { id: string; weight: number; group?: string; - relevantAudits?: string[]; acronym?: string; + relevantAudits?: string[]; } export interface Settings extends Required { diff --git a/types/lhr.d.ts b/types/lhr.d.ts index 0f3736cbefc6..5d6e609448a0 100644 --- a/types/lhr.d.ts +++ b/types/lhr.d.ts @@ -87,10 +87,10 @@ declare global { weight: number; /** Optional grouping within the category. Matches the key of a Result.Group. */ group?: string; - /** Any audit IDs closely relevant to this one. */ - relevantAudits?: string[]; /** The conventional acronym for the audit/metric. */ acronym?: string; + /** Any audit IDs closely relevant to this one. */ + relevantAudits?: string[]; } export interface ReportGroup { From cd7a6385ffe108eab125967c6f5f66694e5df5be Mon Sep 17 00:00:00 2001 From: Paul Irish Date: Mon, 17 May 2021 13:08:23 -0700 Subject: [PATCH 4/5] disable buggy regex caching in intl polyfill --- lighthouse-core/lib/i18n/i18n.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lighthouse-core/lib/i18n/i18n.js b/lighthouse-core/lib/i18n/i18n.js index f4d3081e6a11..15613120f4f9 100644 --- a/lighthouse-core/lib/i18n/i18n.js +++ b/lighthouse-core/lib/i18n/i18n.js @@ -41,6 +41,10 @@ const MESSAGE_I18N_ID_REGEX = / | [^\s]+$/; Intl.NumberFormat = IntlPolyfill.NumberFormat; Intl.DateTimeFormat = IntlPolyfill.DateTimeFormat; } + // Deal with buggy regex caching. https://github.com/andyearnshaw/Intl.js/issues/308 + if (IntlPolyfill.__disableRegExpRestore) { + IntlPolyfill.__disableRegExpRestore() + } })(); const UIStrings = { From b3d72b0eec61a3ab9d62f1de88c5841e1bf1800c Mon Sep 17 00:00:00 2001 From: Paul Irish Date: Mon, 17 May 2021 13:18:26 -0700 Subject: [PATCH 5/5] semi --- lighthouse-core/lib/i18n/i18n.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lighthouse-core/lib/i18n/i18n.js b/lighthouse-core/lib/i18n/i18n.js index 15613120f4f9..bfeed2b67e42 100644 --- a/lighthouse-core/lib/i18n/i18n.js +++ b/lighthouse-core/lib/i18n/i18n.js @@ -43,7 +43,7 @@ const MESSAGE_I18N_ID_REGEX = / | [^\s]+$/; } // Deal with buggy regex caching. https://github.com/andyearnshaw/Intl.js/issues/308 if (IntlPolyfill.__disableRegExpRestore) { - IntlPolyfill.__disableRegExpRestore() + IntlPolyfill.__disableRegExpRestore(); } })();