lighthouse 8.6.0-dev.20211024 → 8.6.0-dev.20211028
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 +11 -11
- package/dist/report/standalone.js +2 -2
- package/flow-report/assets/styles.css +57 -1
- package/flow-report/src/common.tsx +8 -8
- package/flow-report/src/header.tsx +7 -8
- package/flow-report/src/i18n/ui-strings.js +2 -0
- package/flow-report/src/summary/category.tsx +73 -4
- package/flow-report/src/summary/summary.tsx +2 -1
- package/flow-report/src/util.ts +4 -4
- package/flow-report/src/wrappers/markdown.tsx +27 -0
- package/flow-report/src/wrappers/report.tsx +12 -0
- package/flow-report/test/common-test.tsx +6 -6
- package/flow-report/test/summary/category-test.tsx +111 -19
- package/flow-report/test/wrappers/markdown-test.tsx +29 -0
- package/jest.config.js +2 -2
- package/lighthouse-core/audits/byte-efficiency/modern-image-formats.js +4 -2
- package/lighthouse-core/audits/byte-efficiency/offscreen-images.js +11 -10
- package/lighthouse-core/audits/byte-efficiency/uses-optimized-images.js +5 -3
- package/lighthouse-core/audits/byte-efficiency/uses-responsive-images-snapshot.js +2 -1
- package/lighthouse-core/audits/byte-efficiency/uses-responsive-images.js +2 -1
- package/lighthouse-core/audits/image-aspect-ratio.js +4 -3
- package/lighthouse-core/audits/image-size-responsive.js +3 -2
- package/lighthouse-core/audits/preload-lcp-image.js +7 -5
- package/lighthouse-core/audits/script-treemap-data.js +1 -1
- package/lighthouse-core/audits/unsized-images.js +1 -2
- package/package.json +7 -7
- package/report/README.md +1 -1
- package/report/generator/README.md +1 -1
- package/report/renderer/category-renderer.js +7 -0
- package/report/renderer/pwa-category-renderer.js +0 -10
- package/report/renderer/report-renderer.js +1 -1
- package/report/test/renderer/category-renderer-test.js +41 -0
- package/report/test/renderer/pwa-category-renderer-test.js +4 -4
- package/report/test/renderer/text-encoding-test.js +1 -1
- package/shared/localization/format.js +5 -9
- package/shared/localization/locales/ar-XB.json +8 -8
- package/shared/localization/locales/ar.json +8 -8
- package/shared/localization/locales/bg.json +8 -8
- package/shared/localization/locales/ca.json +8 -8
- package/shared/localization/locales/cs.json +8 -8
- package/shared/localization/locales/da.json +8 -8
- package/shared/localization/locales/de.json +8 -8
- package/shared/localization/locales/el.json +8 -8
- package/shared/localization/locales/en-GB.json +8 -8
- package/shared/localization/locales/en-US.json +27 -24
- package/shared/localization/locales/en-XA.json +8 -8
- package/shared/localization/locales/en-XL.json +27 -24
- package/shared/localization/locales/es-419.json +8 -8
- package/shared/localization/locales/es.json +8 -8
- package/shared/localization/locales/fi.json +8 -8
- package/shared/localization/locales/fil.json +8 -8
- package/shared/localization/locales/fr.json +8 -8
- package/shared/localization/locales/he.json +8 -8
- package/shared/localization/locales/hi.json +8 -8
- package/shared/localization/locales/hr.json +8 -8
- package/shared/localization/locales/hu.json +8 -8
- package/shared/localization/locales/id.json +8 -8
- package/shared/localization/locales/it.json +8 -8
- package/shared/localization/locales/ja.json +8 -8
- package/shared/localization/locales/ko.json +8 -8
- package/shared/localization/locales/lt.json +8 -8
- package/shared/localization/locales/lv.json +8 -8
- package/shared/localization/locales/nl.json +8 -8
- package/shared/localization/locales/no.json +8 -8
- package/shared/localization/locales/pl.json +8 -8
- package/shared/localization/locales/pt-PT.json +8 -8
- package/shared/localization/locales/pt.json +8 -8
- package/shared/localization/locales/ro.json +8 -8
- package/shared/localization/locales/ru.json +8 -8
- package/shared/localization/locales/sk.json +8 -8
- package/shared/localization/locales/sl.json +8 -8
- package/shared/localization/locales/sr-Latn.json +8 -8
- package/shared/localization/locales/sr.json +8 -8
- package/shared/localization/locales/sv.json +8 -8
- package/shared/localization/locales/ta.json +8 -8
- package/shared/localization/locales/te.json +8 -8
- package/shared/localization/locales/th.json +8 -8
- package/shared/localization/locales/tr.json +8 -8
- package/shared/localization/locales/uk.json +8 -8
- package/shared/localization/locales/vi.json +8 -8
- package/shared/localization/locales/zh-HK.json +8 -8
- package/shared/localization/locales/zh-TW.json +8 -8
- package/shared/localization/locales/zh.json +8 -8
- package/tsconfig-all.json +2 -2
- package/types/lhr/audit-details.d.ts +5 -5
|
@@ -11,6 +11,7 @@ import {SummaryTooltip} from '../../src/summary/category';
|
|
|
11
11
|
import {flowResult} from '../sample-flow';
|
|
12
12
|
import {I18nProvider} from '../../src/i18n/i18n';
|
|
13
13
|
import {FlowResultContext} from '../../src/util';
|
|
14
|
+
import {ReportRendererProvider} from '../../src/wrappers/report-renderer';
|
|
14
15
|
|
|
15
16
|
let wrapper: FunctionComponent;
|
|
16
17
|
|
|
@@ -18,9 +19,11 @@ beforeEach(() => {
|
|
|
18
19
|
// Include sample flowResult for locale in I18nProvider.
|
|
19
20
|
wrapper = ({children}) => (
|
|
20
21
|
<FlowResultContext.Provider value={flowResult}>
|
|
21
|
-
<
|
|
22
|
-
|
|
23
|
-
|
|
22
|
+
<ReportRendererProvider>
|
|
23
|
+
<I18nProvider>
|
|
24
|
+
{children}
|
|
25
|
+
</I18nProvider>
|
|
26
|
+
</ReportRendererProvider>
|
|
24
27
|
</FlowResultContext.Provider>
|
|
25
28
|
);
|
|
26
29
|
});
|
|
@@ -31,14 +34,16 @@ describe('SummaryTooltip', () => {
|
|
|
31
34
|
id: 'performance',
|
|
32
35
|
score: 1,
|
|
33
36
|
auditRefs: [
|
|
34
|
-
|
|
35
|
-
{result: {score: 1, scoreDisplayMode: 'binary'}, weight: 1, group: 'diagnostics'},
|
|
36
|
-
{result: {score:
|
|
37
|
+
/* eslint-disable max-len */
|
|
38
|
+
{result: {score: 1, scoreDisplayMode: 'binary', title: 'Audit 1'}, weight: 1, group: 'diagnostics'},
|
|
39
|
+
{result: {score: 1, scoreDisplayMode: 'binary', title: 'Audit 2'}, weight: 1, group: 'diagnostics'},
|
|
40
|
+
{result: {score: 0, scoreDisplayMode: 'binary', title: 'Audit 3'}, weight: 1, group: 'diagnostics'},
|
|
41
|
+
/* eslint-enable max-len */
|
|
37
42
|
],
|
|
38
43
|
};
|
|
39
44
|
|
|
40
45
|
const root = render(
|
|
41
|
-
<SummaryTooltip category={category} gatherMode="snapshot"/>,
|
|
46
|
+
<SummaryTooltip category={category} gatherMode="snapshot" url="https://example.com"/>,
|
|
42
47
|
{wrapper}
|
|
43
48
|
);
|
|
44
49
|
|
|
@@ -46,6 +51,7 @@ describe('SummaryTooltip', () => {
|
|
|
46
51
|
expect(() => root.getByText(/^[0-9]+$/)).toThrow();
|
|
47
52
|
expect(root.getByText('2 audits passed')).toBeTruthy();
|
|
48
53
|
expect(root.getByText('3 passable audits')).toBeTruthy();
|
|
54
|
+
expect(root.getByText('https://example.com'));
|
|
49
55
|
});
|
|
50
56
|
|
|
51
57
|
it('renders tooltip without rating', async () => {
|
|
@@ -53,14 +59,16 @@ describe('SummaryTooltip', () => {
|
|
|
53
59
|
id: 'performance',
|
|
54
60
|
score: 1,
|
|
55
61
|
auditRefs: [
|
|
56
|
-
|
|
57
|
-
{result: {score: 1, scoreDisplayMode: 'binary'}, weight: 0, group: 'diagnostics'},
|
|
58
|
-
{result: {score:
|
|
62
|
+
/* eslint-disable max-len */
|
|
63
|
+
{result: {score: 1, scoreDisplayMode: 'binary', title: 'Audit 1'}, weight: 0, group: 'diagnostics'},
|
|
64
|
+
{result: {score: 1, scoreDisplayMode: 'binary', title: 'Audit 2'}, weight: 0, group: 'diagnostics'},
|
|
65
|
+
{result: {score: 0, scoreDisplayMode: 'binary', title: 'Audit 3'}, weight: 0, group: 'diagnostics'},
|
|
66
|
+
/* eslint-enable max-len */
|
|
59
67
|
],
|
|
60
68
|
};
|
|
61
69
|
|
|
62
70
|
const root = render(
|
|
63
|
-
<SummaryTooltip category={category} gatherMode="snapshot"/>,
|
|
71
|
+
<SummaryTooltip category={category} gatherMode="snapshot" url="https://example.com"/>,
|
|
64
72
|
{wrapper}
|
|
65
73
|
);
|
|
66
74
|
|
|
@@ -68,6 +76,7 @@ describe('SummaryTooltip', () => {
|
|
|
68
76
|
expect(() => root.getByText(/^[0-9]+$/)).toThrow();
|
|
69
77
|
expect(root.getByText('2 audits passed')).toBeTruthy();
|
|
70
78
|
expect(root.getByText('3 passable audits')).toBeTruthy();
|
|
79
|
+
expect(root.getByText('https://example.com'));
|
|
71
80
|
});
|
|
72
81
|
|
|
73
82
|
it('renders scored category tooltip with score', async () => {
|
|
@@ -75,14 +84,16 @@ describe('SummaryTooltip', () => {
|
|
|
75
84
|
id: 'performance',
|
|
76
85
|
score: 1,
|
|
77
86
|
auditRefs: [
|
|
78
|
-
|
|
79
|
-
{result: {score: 1, scoreDisplayMode: 'binary'}, weight: 1, group: 'diagnostics'},
|
|
80
|
-
{result: {score:
|
|
87
|
+
/* eslint-disable max-len */
|
|
88
|
+
{result: {score: 1, scoreDisplayMode: 'binary', title: 'Audit 1'}, weight: 1, group: 'diagnostics'},
|
|
89
|
+
{result: {score: 1, scoreDisplayMode: 'binary', title: 'Audit 2'}, weight: 1, group: 'diagnostics'},
|
|
90
|
+
{result: {score: 0, scoreDisplayMode: 'binary', title: 'Audit 3'}, weight: 1, group: 'diagnostics'},
|
|
91
|
+
/* eslint-enable max-len */
|
|
81
92
|
],
|
|
82
93
|
};
|
|
83
94
|
|
|
84
95
|
const root = render(
|
|
85
|
-
<SummaryTooltip category={category} gatherMode="navigation"/>,
|
|
96
|
+
<SummaryTooltip category={category} gatherMode="navigation" url="https://example.com"/>,
|
|
86
97
|
{wrapper}
|
|
87
98
|
);
|
|
88
99
|
|
|
@@ -90,6 +101,7 @@ describe('SummaryTooltip', () => {
|
|
|
90
101
|
expect(root.getByText('100')).toBeTruthy();
|
|
91
102
|
expect(root.getByText('2 audits passed')).toBeTruthy();
|
|
92
103
|
expect(root.getByText('3 passable audits')).toBeTruthy();
|
|
104
|
+
expect(root.getByText('https://example.com'));
|
|
93
105
|
});
|
|
94
106
|
|
|
95
107
|
it('renders informative audit count if any', async () => {
|
|
@@ -97,14 +109,16 @@ describe('SummaryTooltip', () => {
|
|
|
97
109
|
id: 'performance',
|
|
98
110
|
score: 1,
|
|
99
111
|
auditRefs: [
|
|
100
|
-
|
|
101
|
-
{result: {score: 1, scoreDisplayMode: 'binary'}, weight: 1, group: 'diagnostics'},
|
|
102
|
-
{result: {score:
|
|
112
|
+
/* eslint-disable max-len */
|
|
113
|
+
{result: {score: 1, scoreDisplayMode: 'binary', title: 'Audit 1'}, weight: 1, group: 'diagnostics'},
|
|
114
|
+
{result: {score: 1, scoreDisplayMode: 'binary', title: 'Audit 2'}, weight: 1, group: 'diagnostics'},
|
|
115
|
+
{result: {score: null, scoreDisplayMode: 'informative', title: 'Audit 3'}, weight: 1, group: 'diagnostics'},
|
|
116
|
+
/* eslint-enable max-len */
|
|
103
117
|
],
|
|
104
118
|
};
|
|
105
119
|
|
|
106
120
|
const root = render(
|
|
107
|
-
<SummaryTooltip category={category} gatherMode="navigation"/>,
|
|
121
|
+
<SummaryTooltip category={category} gatherMode="navigation" url="https://example.com"/>,
|
|
108
122
|
{wrapper}
|
|
109
123
|
);
|
|
110
124
|
|
|
@@ -113,5 +127,83 @@ describe('SummaryTooltip', () => {
|
|
|
113
127
|
expect(root.getByText('2 audits passed')).toBeTruthy();
|
|
114
128
|
expect(root.getByText('2 passable audits')).toBeTruthy();
|
|
115
129
|
expect(root.getByText('1 informative audit')).toBeTruthy();
|
|
130
|
+
expect(root.getByText('https://example.com'));
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
it('renders highest impact audits', async () => {
|
|
134
|
+
const category: any = {
|
|
135
|
+
id: 'seo',
|
|
136
|
+
score: 1,
|
|
137
|
+
auditRefs: [
|
|
138
|
+
/* eslint-disable max-len */
|
|
139
|
+
{result: {score: 0, scoreDisplayMode: 'binary', title: 'Audit 1'}, weight: 1, group: 'group'},
|
|
140
|
+
{result: {score: 0, scoreDisplayMode: 'binary', title: 'Audit 2'}, weight: 2, group: 'group'},
|
|
141
|
+
{result: {score: 0, scoreDisplayMode: 'binary', title: 'Audit 3'}, weight: 3, group: 'group'},
|
|
142
|
+
/* eslint-enable max-len */
|
|
143
|
+
],
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
const root = render(
|
|
147
|
+
<SummaryTooltip category={category} gatherMode="navigation" url="https://example.com"/>,
|
|
148
|
+
{wrapper}
|
|
149
|
+
);
|
|
150
|
+
|
|
151
|
+
const audits = root.getAllByText(/^Audit [0-9]$/);
|
|
152
|
+
|
|
153
|
+
expect(root.getByText('Highest impact')).toBeTruthy();
|
|
154
|
+
expect(audits.map(a => a.textContent)).toEqual([
|
|
155
|
+
'Audit 3',
|
|
156
|
+
'Audit 2',
|
|
157
|
+
]);
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
it('renders highest impact audits in performance', async () => {
|
|
161
|
+
const category: any = {
|
|
162
|
+
id: 'performance',
|
|
163
|
+
score: 0.75,
|
|
164
|
+
auditRefs: [
|
|
165
|
+
/* eslint-disable max-len */
|
|
166
|
+
{result: {score: 0.75, scoreDisplayMode: 'numeric', title: 'Metric 1'}, weight: 1, group: 'metrics'},
|
|
167
|
+
{result: {score: 0, scoreDisplayMode: 'numeric', title: 'Audit 1', details: {type: 'opportunity', overallSavingsMs: 500}}, weight: 0, group: 'opportunities'},
|
|
168
|
+
{result: {score: 0, scoreDisplayMode: 'numeric', title: 'Audit 2', details: {type: 'opportunity', overallSavingsMs: 1000}}, weight: 0, group: 'opportunities'},
|
|
169
|
+
{result: {score: 0, scoreDisplayMode: 'numeric', title: 'Audit 3', details: {type: 'opportunity', overallSavingsMs: 100}}, weight: 0, group: 'opportunities'},
|
|
170
|
+
/* eslint-enable max-len */
|
|
171
|
+
],
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
const root = render(
|
|
175
|
+
<SummaryTooltip category={category} gatherMode="navigation" url="https://example.com"/>,
|
|
176
|
+
{wrapper}
|
|
177
|
+
);
|
|
178
|
+
|
|
179
|
+
const audits = root.getAllByText(/^(Audit|Metric) [0-9]$/);
|
|
180
|
+
|
|
181
|
+
expect(root.getByText('Highest impact')).toBeTruthy();
|
|
182
|
+
expect(audits.map(a => a.textContent)).toEqual([
|
|
183
|
+
'Audit 2',
|
|
184
|
+
'Audit 1',
|
|
185
|
+
]);
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
it('hides highest impact if nothing to show', async () => {
|
|
189
|
+
const category: any = {
|
|
190
|
+
id: 'performance',
|
|
191
|
+
score: 1,
|
|
192
|
+
auditRefs: [
|
|
193
|
+
/* eslint-disable max-len */
|
|
194
|
+
{result: {score: 1, scoreDisplayMode: 'binary', title: 'Audit 1'}, weight: 1, group: 'diagnostics'},
|
|
195
|
+
{result: {score: 0, scoreDisplayMode: 'binary', title: 'Audit 2'}, weight: 1},
|
|
196
|
+
{result: {score: null, scoreDisplayMode: 'informative', title: 'Audit 3'}, weight: 1, group: 'diagnostics'},
|
|
197
|
+
/* eslint-enable max-len */
|
|
198
|
+
],
|
|
199
|
+
};
|
|
200
|
+
|
|
201
|
+
const root = render(
|
|
202
|
+
<SummaryTooltip category={category} gatherMode="navigation" url="https://example.com"/>,
|
|
203
|
+
{wrapper}
|
|
204
|
+
);
|
|
205
|
+
|
|
206
|
+
expect(() => root.getByText('Highest impact')).toThrow();
|
|
207
|
+
expect(() => root.getByText(/^Audit [0-9]$/)).toThrow();
|
|
116
208
|
});
|
|
117
209
|
});
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license Copyright 2021 The Lighthouse Authors. All Rights Reserved.
|
|
3
|
+
* 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
|
|
4
|
+
* 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.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import {render} from '@testing-library/preact';
|
|
8
|
+
import {FunctionComponent} from 'preact';
|
|
9
|
+
|
|
10
|
+
import {Markdown} from '../../src/wrappers/markdown';
|
|
11
|
+
import {ReportRendererProvider} from '../../src/wrappers/report-renderer';
|
|
12
|
+
|
|
13
|
+
let wrapper: FunctionComponent;
|
|
14
|
+
|
|
15
|
+
beforeEach(() => {
|
|
16
|
+
wrapper = ({children}) => (
|
|
17
|
+
<ReportRendererProvider>
|
|
18
|
+
{children}
|
|
19
|
+
</ReportRendererProvider>
|
|
20
|
+
);
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
describe('Markdown', () => {
|
|
24
|
+
it('renders markdown text', () => {
|
|
25
|
+
const root = render(<Markdown text="Some `fancy` text"/>, {wrapper});
|
|
26
|
+
const text = root.getByText(/^Some.*text$/);
|
|
27
|
+
expect(text.innerHTML).toEqual('Some <code>fancy</code> text');
|
|
28
|
+
});
|
|
29
|
+
});
|
package/jest.config.js
CHANGED
|
@@ -13,8 +13,8 @@ module.exports = {
|
|
|
13
13
|
'**/lighthouse-cli/**/*-test.js',
|
|
14
14
|
'**/report/**/*-test.js',
|
|
15
15
|
'**/lighthouse-core/test/fraggle-rock/**/*-test-pptr.js',
|
|
16
|
-
'**/
|
|
17
|
-
'**/
|
|
16
|
+
'**/treemap/**/*-test.js',
|
|
17
|
+
'**/viewer/**/*-test.js',
|
|
18
18
|
'**/third-party/**/*-test.js',
|
|
19
19
|
'**/clients/test/**/*-test.js',
|
|
20
20
|
'**/shared/**/*-test.js',
|
|
@@ -103,6 +103,8 @@ class ModernImageFormats extends ByteEfficiencyAudit {
|
|
|
103
103
|
const items = [];
|
|
104
104
|
const warnings = [];
|
|
105
105
|
for (const image of images) {
|
|
106
|
+
const imageElement = imageElementsByURL.get(image.url);
|
|
107
|
+
|
|
106
108
|
if (image.failed) {
|
|
107
109
|
warnings.push(`Unable to decode ${URL.getURLDisplayName(image.url)}`);
|
|
108
110
|
continue;
|
|
@@ -120,7 +122,6 @@ class ModernImageFormats extends ByteEfficiencyAudit {
|
|
|
120
122
|
let fromProtocol = true;
|
|
121
123
|
|
|
122
124
|
if (typeof webpSize === 'undefined') {
|
|
123
|
-
const imageElement = imageElementsByURL.get(image.url);
|
|
124
125
|
if (!imageElement) {
|
|
125
126
|
warnings.push(`Unable to locate resource ${URL.getURLDisplayName(image.url)}`);
|
|
126
127
|
continue;
|
|
@@ -155,6 +156,7 @@ class ModernImageFormats extends ByteEfficiencyAudit {
|
|
|
155
156
|
const isCrossOrigin = !URL.originsMatch(pageURL, image.url);
|
|
156
157
|
|
|
157
158
|
items.push({
|
|
159
|
+
node: imageElement ? ByteEfficiencyAudit.makeNodeItem(imageElement.node) : undefined,
|
|
158
160
|
url,
|
|
159
161
|
fromProtocol,
|
|
160
162
|
isCrossOrigin,
|
|
@@ -166,7 +168,7 @@ class ModernImageFormats extends ByteEfficiencyAudit {
|
|
|
166
168
|
|
|
167
169
|
/** @type {LH.Audit.Details.Opportunity['headings']} */
|
|
168
170
|
const headings = [
|
|
169
|
-
{key: '
|
|
171
|
+
{key: 'node', valueType: 'node', label: ''},
|
|
170
172
|
{key: 'url', valueType: 'url', label: str_(i18n.UIStrings.columnURL)},
|
|
171
173
|
{key: 'totalBytes', valueType: 'bytes', label: str_(i18n.UIStrings.columnResourceSize)},
|
|
172
174
|
{key: 'wastedBytes', valueType: 'bytes', label: str_(i18n.UIStrings.columnWastedBytes)},
|
|
@@ -37,7 +37,7 @@ const IGNORE_THRESHOLD_IN_BYTES = 2048;
|
|
|
37
37
|
const IGNORE_THRESHOLD_IN_PERCENT = 75;
|
|
38
38
|
const IGNORE_THRESHOLD_IN_MS = 50;
|
|
39
39
|
|
|
40
|
-
/** @typedef {{url: string, requestStartTime: number, totalBytes: number, wastedBytes: number, wastedPercent: number}} WasteResult */
|
|
40
|
+
/** @typedef {{node: LH.Audit.Details.NodeValue, url: string, requestStartTime: number, totalBytes: number, wastedBytes: number, wastedPercent: number}} WasteResult */
|
|
41
41
|
|
|
42
42
|
class OffscreenImages extends ByteEfficiencyAudit {
|
|
43
43
|
/**
|
|
@@ -100,6 +100,7 @@ class OffscreenImages extends ByteEfficiencyAudit {
|
|
|
100
100
|
}
|
|
101
101
|
|
|
102
102
|
return {
|
|
103
|
+
node: ByteEfficiencyAudit.makeNodeItem(image.node),
|
|
103
104
|
url,
|
|
104
105
|
requestStartTime: networkRecord.startTime,
|
|
105
106
|
totalBytes,
|
|
@@ -184,26 +185,26 @@ class OffscreenImages extends ByteEfficiencyAudit {
|
|
|
184
185
|
|
|
185
186
|
/** @type {string[]} */
|
|
186
187
|
const warnings = [];
|
|
187
|
-
|
|
188
|
+
/** @type {Map<string, WasteResult>} */
|
|
189
|
+
const resultsMap = new Map();
|
|
190
|
+
for (const image of images) {
|
|
188
191
|
const processed = OffscreenImages.computeWaste(image, viewportDimensions, networkRecords);
|
|
189
192
|
if (processed === null) {
|
|
190
|
-
|
|
193
|
+
continue;
|
|
191
194
|
}
|
|
192
195
|
|
|
193
196
|
if (processed instanceof Error) {
|
|
194
197
|
warnings.push(processed.message);
|
|
195
198
|
Sentry.captureException(processed, {tags: {audit: this.meta.id}, level: 'warning'});
|
|
196
|
-
|
|
199
|
+
continue;
|
|
197
200
|
}
|
|
198
201
|
|
|
199
202
|
// If an image was used more than once, warn only about its least wasteful usage
|
|
200
|
-
const existing =
|
|
203
|
+
const existing = resultsMap.get(processed.url);
|
|
201
204
|
if (!existing || existing.wastedBytes > processed.wastedBytes) {
|
|
202
|
-
|
|
205
|
+
resultsMap.set(processed.url, processed);
|
|
203
206
|
}
|
|
204
|
-
|
|
205
|
-
return results;
|
|
206
|
-
}, /** @type {Map<string, WasteResult>} */ (new Map()));
|
|
207
|
+
}
|
|
207
208
|
|
|
208
209
|
const settings = context.settings;
|
|
209
210
|
|
|
@@ -233,7 +234,7 @@ class OffscreenImages extends ByteEfficiencyAudit {
|
|
|
233
234
|
|
|
234
235
|
/** @type {LH.Audit.Details.Opportunity['headings']} */
|
|
235
236
|
const headings = [
|
|
236
|
-
{key: '
|
|
237
|
+
{key: 'node', valueType: 'node', label: ''},
|
|
237
238
|
{key: 'url', valueType: 'url', label: str_(i18n.UIStrings.columnURL)},
|
|
238
239
|
{key: 'totalBytes', valueType: 'bytes', label: str_(i18n.UIStrings.columnResourceSize)},
|
|
239
240
|
{key: 'wastedBytes', valueType: 'bytes', label: str_(i18n.UIStrings.columnWastedBytes)},
|
|
@@ -76,10 +76,12 @@ class UsesOptimizedImages extends ByteEfficiencyAudit {
|
|
|
76
76
|
const imageElementsByURL = new Map();
|
|
77
77
|
imageElements.forEach(img => imageElementsByURL.set(img.src, img));
|
|
78
78
|
|
|
79
|
-
/** @type {Array<{url: string, fromProtocol: boolean, isCrossOrigin: boolean, totalBytes: number, wastedBytes: number}>} */
|
|
79
|
+
/** @type {Array<{node?: LH.Audit.Details.NodeValue, url: string, fromProtocol: boolean, isCrossOrigin: boolean, totalBytes: number, wastedBytes: number}>} */
|
|
80
80
|
const items = [];
|
|
81
81
|
const warnings = [];
|
|
82
82
|
for (const image of images) {
|
|
83
|
+
const imageElement = imageElementsByURL.get(image.url);
|
|
84
|
+
|
|
83
85
|
if (image.failed) {
|
|
84
86
|
warnings.push(`Unable to decode ${URL.getURLDisplayName(image.url)}`);
|
|
85
87
|
continue;
|
|
@@ -91,7 +93,6 @@ class UsesOptimizedImages extends ByteEfficiencyAudit {
|
|
|
91
93
|
let fromProtocol = true;
|
|
92
94
|
|
|
93
95
|
if (typeof jpegSize === 'undefined') {
|
|
94
|
-
const imageElement = imageElementsByURL.get(image.url);
|
|
95
96
|
if (!imageElement) {
|
|
96
97
|
warnings.push(`Unable to locate resource ${URL.getURLDisplayName(image.url)}`);
|
|
97
98
|
continue;
|
|
@@ -115,6 +116,7 @@ class UsesOptimizedImages extends ByteEfficiencyAudit {
|
|
|
115
116
|
const jpegSavings = UsesOptimizedImages.computeSavings({...image, jpegSize});
|
|
116
117
|
|
|
117
118
|
items.push({
|
|
119
|
+
node: imageElement ? ByteEfficiencyAudit.makeNodeItem(imageElement.node) : undefined,
|
|
118
120
|
url,
|
|
119
121
|
fromProtocol,
|
|
120
122
|
isCrossOrigin,
|
|
@@ -125,7 +127,7 @@ class UsesOptimizedImages extends ByteEfficiencyAudit {
|
|
|
125
127
|
|
|
126
128
|
/** @type {LH.Audit.Details.Opportunity['headings']} */
|
|
127
129
|
const headings = [
|
|
128
|
-
{key: '
|
|
130
|
+
{key: 'node', valueType: 'node', label: ''},
|
|
129
131
|
{key: 'url', valueType: 'url', label: str_(i18n.UIStrings.columnURL)},
|
|
130
132
|
{key: 'totalBytes', valueType: 'bytes', label: str_(i18n.UIStrings.columnResourceSize)},
|
|
131
133
|
{key: 'wastedBytes', valueType: 'bytes', label: str_(i18n.UIStrings.columnWastedBytes)},
|
|
@@ -70,6 +70,7 @@ class UsesResponsiveImagesSnapshot extends Audit {
|
|
|
70
70
|
if (actualPixels - usedPixels > IGNORE_THRESHOLD_IN_PIXELS) score = 0;
|
|
71
71
|
|
|
72
72
|
items.push({
|
|
73
|
+
node: Audit.makeNodeItem(image.node),
|
|
73
74
|
url: URL.elideDataURI(image.src),
|
|
74
75
|
displayedDimensions: `${displayed.width}x${displayed.height}`,
|
|
75
76
|
actualDimensions: `${actual.width}x${actual.height}`,
|
|
@@ -79,7 +80,7 @@ class UsesResponsiveImagesSnapshot extends Audit {
|
|
|
79
80
|
/** @type {LH.Audit.Details.Table['headings']} */
|
|
80
81
|
const headings = [
|
|
81
82
|
/* eslint-disable max-len */
|
|
82
|
-
{key: '
|
|
83
|
+
{key: 'node', itemType: 'node', text: ''},
|
|
83
84
|
{key: 'url', itemType: 'url', text: str_(i18n.UIStrings.columnURL)},
|
|
84
85
|
{key: 'displayedDimensions', itemType: 'text', text: str_(UIStrings.columnDisplayedDimensions)},
|
|
85
86
|
{key: 'actualDimensions', itemType: 'text', text: str_(UIStrings.columnActualDimensions)},
|
|
@@ -104,6 +104,7 @@ class UsesResponsiveImages extends ByteEfficiencyAudit {
|
|
|
104
104
|
const wastedBytes = Math.round(totalBytes * wastedRatio);
|
|
105
105
|
|
|
106
106
|
return {
|
|
107
|
+
node: ByteEfficiencyAudit.makeNodeItem(image.node),
|
|
107
108
|
url,
|
|
108
109
|
totalBytes,
|
|
109
110
|
wastedBytes,
|
|
@@ -158,7 +159,7 @@ class UsesResponsiveImages extends ByteEfficiencyAudit {
|
|
|
158
159
|
|
|
159
160
|
/** @type {LH.Audit.Details.Opportunity['headings']} */
|
|
160
161
|
const headings = [
|
|
161
|
-
{key: '
|
|
162
|
+
{key: 'node', valueType: 'node', label: ''},
|
|
162
163
|
{key: 'url', valueType: 'url', label: str_(i18n.UIStrings.columnURL)},
|
|
163
164
|
{key: 'totalBytes', valueType: 'bytes', label: str_(i18n.UIStrings.columnResourceSize)},
|
|
164
165
|
{key: 'wastedBytes', valueType: 'bytes', label: str_(i18n.UIStrings.columnWastedBytes)},
|
|
@@ -51,7 +51,7 @@ class ImageAspectRatio extends Audit {
|
|
|
51
51
|
|
|
52
52
|
/**
|
|
53
53
|
* @param {WellDefinedImage} image
|
|
54
|
-
* @return {{url: string, displayedAspectRatio: string, actualAspectRatio: string, doRatiosMatch: boolean}}
|
|
54
|
+
* @return {{url: string, node: LH.Audit.Details.NodeValue, displayedAspectRatio: string, actualAspectRatio: string, doRatiosMatch: boolean}}
|
|
55
55
|
*/
|
|
56
56
|
static computeAspectRatios(image) {
|
|
57
57
|
const url = URL.elideDataURI(image.src);
|
|
@@ -63,6 +63,7 @@ class ImageAspectRatio extends Audit {
|
|
|
63
63
|
|
|
64
64
|
return {
|
|
65
65
|
url,
|
|
66
|
+
node: Audit.makeNodeItem(image.node),
|
|
66
67
|
displayedAspectRatio: `${image.displayedWidth} x ${image.displayedHeight}
|
|
67
68
|
(${displayedAspectRatio.toFixed(2)})`,
|
|
68
69
|
actualAspectRatio: `${image.naturalDimensions.width} x ${image.naturalDimensions.height}
|
|
@@ -78,7 +79,7 @@ class ImageAspectRatio extends Audit {
|
|
|
78
79
|
static audit(artifacts) {
|
|
79
80
|
const images = artifacts.ImageElements;
|
|
80
81
|
|
|
81
|
-
/** @type {Array<{url: string, displayedAspectRatio: string, actualAspectRatio: string, doRatiosMatch: boolean}>} */
|
|
82
|
+
/** @type {Array<{url: string, node: LH.Audit.Details.NodeValue, displayedAspectRatio: string, actualAspectRatio: string, doRatiosMatch: boolean}>} */
|
|
82
83
|
const results = [];
|
|
83
84
|
images.filter(image => {
|
|
84
85
|
// - filter out css background images since we don't have a reliable way to tell if it's a
|
|
@@ -105,7 +106,7 @@ class ImageAspectRatio extends Audit {
|
|
|
105
106
|
|
|
106
107
|
/** @type {LH.Audit.Details.Table['headings']} */
|
|
107
108
|
const headings = [
|
|
108
|
-
{key: '
|
|
109
|
+
{key: 'node', itemType: 'node', text: ''},
|
|
109
110
|
{key: 'url', itemType: 'url', text: str_(i18n.UIStrings.columnURL)},
|
|
110
111
|
{key: 'displayedAspectRatio', itemType: 'text', text: str_(UIStrings.columnDisplayed)},
|
|
111
112
|
{key: 'actualAspectRatio', itemType: 'text', text: str_(UIStrings.columnActual)},
|
|
@@ -45,7 +45,7 @@ const LARGE_IMAGE_FACTOR = 0.75;
|
|
|
45
45
|
// considered SMALL.
|
|
46
46
|
const SMALL_IMAGE_THRESHOLD = 64;
|
|
47
47
|
|
|
48
|
-
/** @typedef {{url: string, elidedUrl: string, displayedSize: string, actualSize: string, actualPixels: number, expectedSize: string, expectedPixels: number}} Result */
|
|
48
|
+
/** @typedef {{url: string, node: LH.Audit.Details.NodeValue, elidedUrl: string, displayedSize: string, actualSize: string, actualPixels: number, expectedSize: string, expectedPixels: number}} Result */
|
|
49
49
|
|
|
50
50
|
/**
|
|
51
51
|
* @param {{top: number, bottom: number, left: number, right: number}} imageRect
|
|
@@ -147,6 +147,7 @@ function getResult(image, DPR) {
|
|
|
147
147
|
expectedImageSize(image.displayedWidth, image.displayedHeight, DPR);
|
|
148
148
|
return {
|
|
149
149
|
url: image.src,
|
|
150
|
+
node: Audit.makeNodeItem(image.node),
|
|
150
151
|
elidedUrl: URL.elideDataURI(image.src),
|
|
151
152
|
displayedSize: `${image.displayedWidth} x ${image.displayedHeight}`,
|
|
152
153
|
actualSize: `${image.naturalDimensions.width} x ${image.naturalDimensions.height}`,
|
|
@@ -263,7 +264,7 @@ class ImageSizeResponsive extends Audit {
|
|
|
263
264
|
|
|
264
265
|
/** @type {LH.Audit.Details.Table['headings']} */
|
|
265
266
|
const headings = [
|
|
266
|
-
{key: '
|
|
267
|
+
{key: 'node', itemType: 'node', text: ''},
|
|
267
268
|
{key: 'elidedUrl', itemType: 'url', text: str_(i18n.UIStrings.columnURL)},
|
|
268
269
|
{key: 'displayedSize', itemType: 'text', text: str_(UIStrings.columnDisplayed)},
|
|
269
270
|
{key: 'actualSize', itemType: 'text', text: str_(UIStrings.columnActual)},
|
|
@@ -106,13 +106,14 @@ class PreloadLCPImageAudit extends Audit {
|
|
|
106
106
|
|
|
107
107
|
/**
|
|
108
108
|
* Computes the estimated effect of preloading the LCP image.
|
|
109
|
+
* @param {LH.Artifacts.TraceElement|undefined} lcpElement
|
|
109
110
|
* @param {LH.Gatherer.Simulation.GraphNetworkNode|undefined} lcpNode
|
|
110
111
|
* @param {LH.Gatherer.Simulation.GraphNode} graph
|
|
111
112
|
* @param {LH.Gatherer.Simulation.Simulator} simulator
|
|
112
|
-
* @return {{wastedMs: number, results: Array<{url: string, wastedMs: number}>}}
|
|
113
|
+
* @return {{wastedMs: number, results: Array<{node: LH.Audit.Details.NodeValue, url: string, wastedMs: number}>}}
|
|
113
114
|
*/
|
|
114
|
-
static computeWasteWithGraph(lcpNode, graph, simulator) {
|
|
115
|
-
if (!lcpNode) {
|
|
115
|
+
static computeWasteWithGraph(lcpElement, lcpNode, graph, simulator) {
|
|
116
|
+
if (!lcpElement || !lcpNode) {
|
|
116
117
|
return {
|
|
117
118
|
wastedMs: 0,
|
|
118
119
|
results: [],
|
|
@@ -185,6 +186,7 @@ class PreloadLCPImageAudit extends Audit {
|
|
|
185
186
|
return {
|
|
186
187
|
wastedMs,
|
|
187
188
|
results: [{
|
|
189
|
+
node: Audit.makeNodeItem(lcpElement.node),
|
|
188
190
|
url: lcpNode.record.url,
|
|
189
191
|
wastedMs,
|
|
190
192
|
}],
|
|
@@ -216,11 +218,11 @@ class PreloadLCPImageAudit extends Audit {
|
|
|
216
218
|
const lcpNode = PreloadLCPImageAudit.getLCPNodeToPreload(mainResource, graph, lcpElement, artifacts.ImageElements);
|
|
217
219
|
|
|
218
220
|
const {results, wastedMs} =
|
|
219
|
-
PreloadLCPImageAudit.computeWasteWithGraph(lcpNode, graph, simulator);
|
|
221
|
+
PreloadLCPImageAudit.computeWasteWithGraph(lcpElement, lcpNode, graph, simulator);
|
|
220
222
|
|
|
221
223
|
/** @type {LH.Audit.Details.Opportunity['headings']} */
|
|
222
224
|
const headings = [
|
|
223
|
-
{key: '
|
|
225
|
+
{key: 'node', valueType: 'node', label: ''},
|
|
224
226
|
{key: 'url', valueType: 'url', label: str_(i18n.UIStrings.columnURL)},
|
|
225
227
|
{key: 'wastedMs', valueType: 'timespanMs', label: str_(i18n.UIStrings.columnWastedMs)},
|
|
226
228
|
];
|
|
@@ -147,9 +147,8 @@ class UnsizedImages extends Audit {
|
|
|
147
147
|
|
|
148
148
|
/** @type {LH.Audit.Details.Table['headings']} */
|
|
149
149
|
const headings = [
|
|
150
|
-
{key: '
|
|
150
|
+
{key: 'node', itemType: 'node', text: ''},
|
|
151
151
|
{key: 'url', itemType: 'url', text: str_(i18n.UIStrings.columnURL)},
|
|
152
|
-
{key: 'node', itemType: 'node', text: str_(i18n.UIStrings.columnFailingElem)},
|
|
153
152
|
];
|
|
154
153
|
|
|
155
154
|
return {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lighthouse",
|
|
3
|
-
"version": "8.6.0-dev.
|
|
3
|
+
"version": "8.6.0-dev.20211028",
|
|
4
4
|
"description": "Automated auditing, performance metrics, and best practices for the web.",
|
|
5
5
|
"main": "./lighthouse-core/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
"node": ">=14.15"
|
|
13
13
|
},
|
|
14
14
|
"scripts": {
|
|
15
|
+
"prepack": "yarn build-report --standalone --flow",
|
|
15
16
|
"build-all": "npm-run-posix-or-windows build-all:task",
|
|
16
17
|
"build-all:task": "yarn build-report && yarn build-cdt-lib && yarn build-devtools && (yarn build-extension & yarn build-lr & yarn build-viewer & yarn build-treemap & yarn build-smokehouse-bundle & wait) && yarn build-pack",
|
|
17
18
|
"build-all:task:windows": "yarn build-report && yarn build-cdt-lib && yarn build-extension && yarn build-devtools && yarn build-lr && yarn build-viewer && yarn build-treemap && yarn build-smokehouse-bundle",
|
|
@@ -36,10 +37,10 @@
|
|
|
36
37
|
"start": "yarn build-report --standalone && node ./lighthouse-cli/index.js",
|
|
37
38
|
"jest": "node --experimental-vm-modules ./node_modules/jest/bin/jest.js",
|
|
38
39
|
"test": "yarn diff:sample-json && yarn lint --quiet && yarn unit && yarn type-check",
|
|
39
|
-
"test-bundle": "yarn smoke --runner bundle -j=1 --retries=2
|
|
40
|
+
"test-bundle": "yarn smoke --runner bundle -j=1 --retries=2 a11y dbw",
|
|
40
41
|
"test-clients": "yarn jest \"$PWD/clients/\" && yarn jest --testMatch=\"**/clients/test/**/*-test-pptr.js\"",
|
|
41
|
-
"test-viewer": "yarn unit-viewer && yarn jest --testMatch=\"**/
|
|
42
|
-
"test-treemap": "yarn unit-treemap && yarn jest --testMatch=\"**/
|
|
42
|
+
"test-viewer": "yarn unit-viewer && yarn jest --testMatch=\"**/viewer/**/*-test-pptr.js\"",
|
|
43
|
+
"test-treemap": "yarn unit-treemap && yarn jest --testMatch=\"**/treemap/**/*-test-pptr.js\"",
|
|
43
44
|
"test-lantern": "bash lighthouse-core/scripts/test-lantern.sh",
|
|
44
45
|
"test-legacy-javascript": "bash lighthouse-core/scripts/test-legacy-javascript.sh",
|
|
45
46
|
"test-docs": "yarn --cwd docs/recipes/ test",
|
|
@@ -47,8 +48,8 @@
|
|
|
47
48
|
"unit-core": "yarn jest \"lighthouse-core\"",
|
|
48
49
|
"unit-cli": "yarn jest \"lighthouse-cli/\"",
|
|
49
50
|
"unit-report": "yarn jest \"report/\"",
|
|
50
|
-
"unit-treemap": "yarn jest \"
|
|
51
|
-
"unit-viewer": "yarn jest \"
|
|
51
|
+
"unit-treemap": "yarn jest \"treemap/.*-test.js\"",
|
|
52
|
+
"unit-viewer": "yarn jest \"viewer/.*-test.js\"",
|
|
52
53
|
"unit-flow": "yarn jest \"flow-report/.*-test.[tj]s[x]?\"",
|
|
53
54
|
"unit": "yarn jest",
|
|
54
55
|
"unit:ci": "NODE_OPTIONS=--max-old-space-size=8192 npm run jest --ci .",
|
|
@@ -128,7 +129,6 @@
|
|
|
128
129
|
"@types/yargs-parser": "^15.0.0",
|
|
129
130
|
"@typescript-eslint/eslint-plugin": "^4.30.0",
|
|
130
131
|
"@typescript-eslint/parser": "^4.30.0",
|
|
131
|
-
"@wardpeet/brfs": "2.1.0",
|
|
132
132
|
"acorn": "^8.5.0",
|
|
133
133
|
"angular": "^1.7.4",
|
|
134
134
|
"archiver": "^3.0.0",
|
package/report/README.md
CHANGED
|
@@ -9,7 +9,7 @@ Example standalone HTML report, delivered by the CLI: [`dbwtest-3.0.3.html`](htt
|
|
|
9
9
|
### Report Renderer components
|
|
10
10
|
|
|
11
11
|
1. [`report/generator/report-generator.js`](https://github.com/GoogleChrome/lighthouse/blob/master/report/generator/report-generator.js) is the entry point for generating the HTML from Node. It compiles together the HTML string with everything required within it.
|
|
12
|
-
- It runs natively in Node.js but can run in the browser after a compile step is applied during our bundling pipeline. That compile step uses `
|
|
12
|
+
- It runs natively in Node.js but can run in the browser after a compile step is applied during our bundling pipeline. That compile step uses `inline-fs`, which takes any `fs.readFileSync()` calls and replaces them with the stringified file content.
|
|
13
13
|
1. [`report/renderer`](https://github.com/GoogleChrome/lighthouse/tree/master/report/renderer) are all client-side JS files. They transform an LHR object into a report DOM tree. They are all executed within the browser.
|
|
14
14
|
1. [`report/assets/standalone-template.html`](https://github.com/GoogleChrome/lighthouse/blob/master/lighthouse-core/report/html/report-template.html) is where the client-side build of the DOM report is typically kicked off ([with these four lines](https://github.com/GoogleChrome/lighthouse/blob/eda3a3e2e271249f261655f9504fd542d6acf0f8/lighthouse-core/report/html/report-template.html#L29-L33)) However, see _Current Uses.._ below for more possibilities.
|
|
15
15
|
|
|
@@ -4,6 +4,6 @@
|
|
|
4
4
|
|
|
5
5
|
Lighthouse's report generator is the entry point for creating reports from an **LHR** (Lighthouse Result object). It returns results as HTML, JSON, and CSV.
|
|
6
6
|
|
|
7
|
-
It runs natively in Node.js but can run in the browser after a compile step is applied during our bundling pipeline. That compile step uses `
|
|
7
|
+
It runs natively in Node.js but can run in the browser after a compile step is applied during our bundling pipeline. That compile step uses `inline-fs`, which takes any `fs.readFileSync()` calls and replaces them with the stringified file content.
|
|
8
8
|
|
|
9
9
|
Because it's shared between core and the report, dependencies (both code and types) should be kept minimal.
|
|
@@ -532,6 +532,13 @@ export class CategoryRenderer {
|
|
|
532
532
|
clumps.set(clumpId, clump);
|
|
533
533
|
}
|
|
534
534
|
|
|
535
|
+
// Sort audits by weight.
|
|
536
|
+
for (const auditRefs of clumps.values()) {
|
|
537
|
+
auditRefs.sort((a, b) => {
|
|
538
|
+
return b.weight - a.weight;
|
|
539
|
+
});
|
|
540
|
+
}
|
|
541
|
+
|
|
535
542
|
// Render each clump.
|
|
536
543
|
for (const [clumpId, auditRefs] of clumps) {
|
|
537
544
|
if (auditRefs.length === 0) continue;
|