lighthouse 9.0.0-dev.20211123 → 9.1.0-dev.20211126
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/changelog.md +70 -0
- package/dist/report/bundle.esm.js +16 -2
- package/dist/report/flow.js +34 -23
- package/dist/report/standalone.js +2 -2
- package/flow-report/api.ts +18 -0
- package/flow-report/{standalone-flow.tsx → clients/standalone.ts} +4 -5
- package/flow-report/src/app.tsx +20 -12
- package/flow-report/src/sidebar/flow.tsx +1 -3
- package/flow-report/src/sidebar/sidebar.tsx +1 -3
- package/flow-report/src/topbar.tsx +22 -13
- package/flow-report/src/util.ts +7 -0
- package/flow-report/src/wrappers/report.tsx +1 -0
- package/flow-report/src/wrappers/styles.tsx +16 -0
- package/flow-report/test/topbar-test.tsx +22 -6
- package/flow-report/types/flow-report.d.ts +4 -0
- package/lighthouse-core/lib/stack-packs.js +12 -8
- package/package.json +2 -2
- package/report/generator/file-namer.js +12 -1
- package/report/renderer/api.js +15 -1
- package/report/renderer/report-renderer.js +4 -1
- package/report/types/report-renderer.d.ts +2 -0
- package/shared/localization/locales/bg.json +17 -17
- package/shared/localization/locales/da.json +16 -16
- package/shared/localization/locales/en-GB.json +19 -19
- package/shared/localization/locales/en-US.json +30 -0
- package/shared/localization/locales/en-XL.json +30 -0
- package/shared/localization/locales/es-419.json +17 -17
- package/shared/localization/locales/fi.json +16 -16
- package/shared/localization/locales/fil.json +33 -33
- package/shared/localization/locales/fr.json +16 -16
- package/shared/localization/locales/he.json +16 -16
- package/shared/localization/locales/hr.json +16 -16
- package/shared/localization/locales/hu.json +16 -16
- package/shared/localization/locales/id.json +16 -16
- package/shared/localization/locales/it.json +37 -37
- package/shared/localization/locales/ja.json +16 -16
- package/shared/localization/locales/ko.json +16 -16
- package/shared/localization/locales/lt.json +17 -17
- package/shared/localization/locales/nl.json +16 -16
- package/shared/localization/locales/no.json +17 -17
- package/shared/localization/locales/pl.json +16 -16
- package/shared/localization/locales/pt-PT.json +37 -37
- package/shared/localization/locales/pt.json +16 -16
- package/shared/localization/locales/ru.json +16 -16
- package/shared/localization/locales/sr-Latn.json +16 -16
- package/shared/localization/locales/sr.json +16 -16
- package/shared/localization/locales/sv.json +17 -17
- package/shared/localization/locales/uk.json +17 -17
|
@@ -145,7 +145,7 @@ class s{constructor(e,t){this._document=e,this._lighthouseChannel="unknown",this
|
|
|
145
145
|
*
|
|
146
146
|
* Dummy text for ensuring report robustness: <\/script> pre$`post %%LIGHTHOUSE_JSON%%
|
|
147
147
|
* (this is handled by terser)
|
|
148
|
-
*/class N{constructor(e){this._dom=e,this._opts={}}renderReport(e,t,n){if(!this._dom.rootEl&&t){console.warn("Please adopt the new report API in renderer/api.js.");const e=t.closest(".lh-root");e?this._dom.rootEl=e:(t.classList.add("lh-root","lh-vars"),this._dom.rootEl=t)}else this._dom.rootEl&&t&&(this._dom.rootEl=t);n&&(this._opts=n),this._dom.setLighthouseChannel(e.configSettings.channel||"unknown");const r=l.prepareReportResult(e);return this._dom.rootEl.textContent="",this._dom.rootEl.appendChild(this._renderReport(r)),this._dom.rootEl}_renderReportTopbar(e){const t=this._dom.createComponent("topbar"),n=this._dom.find("a.lh-topbar__url",t);return n.textContent=e.finalUrl,n.title=e.finalUrl,this._dom.safelySetHref(n,e.finalUrl),t}_renderReportHeader(){const e=this._dom.createComponent("heading"),t=this._dom.createComponent("scoresWrapper");return this._dom.find(".lh-scores-wrapper-placeholder",e).replaceWith(t),e}_renderReportFooter(e){const t=this._dom.createComponent("footer");return this._renderMetaBlock(e,t),this._dom.find(".lh-footer__version_issue",t).textContent=l.i18n.strings.footerIssue,this._dom.find(".lh-footer__version",t).textContent=e.lighthouseVersion,t}_renderMetaBlock(e,t){const n=l.getEmulationDescriptions(e.configSettings||{}),r=e.userAgent.match(/(\w*Chrome\/[\d.]+)/),o=Array.isArray(r)?r[1].replace("/"," ").replace("Chrome","Chromium"):"Chromium",i=e.configSettings.channel,a=e.environment.benchmarkIndex.toFixed(0),s=e.environment.credits?.["axe-core"],c=[["date",`Captured at ${l.i18n.formatDateTime(e.fetchTime)}`],["devices",`${n.deviceEmulation} with Lighthouse ${e.lighthouseVersion}`,`${l.i18n.strings.runtimeSettingsBenchmark}: ${a}\n${l.i18n.strings.runtimeSettingsCPUThrottling}: ${n.cpuThrottling}`+(s?`\n${l.i18n.strings.runtimeSettingsAxeVersion}: ${s}`:"")],["samples-one",l.i18n.strings.runtimeSingleLoad,l.i18n.strings.runtimeSingleLoadTooltip],["stopwatch",l.i18n.strings.runtimeAnalysisWindow],["networkspeed",`${n.summary}`,`${l.i18n.strings.runtimeSettingsNetworkThrottling}: ${n.networkThrottling}`],["chrome",`Using ${o}`+(i?` with ${i}`:""),`${l.i18n.strings.runtimeSettingsUANetwork}: "${e.environment.networkUserAgent}"`]],d=this._dom.find(".lh-meta__items",t);for(const[e,t,n]of c){const r=this._dom.createChildOf(d,"li","lh-meta__item");if(r.textContent=t,n){r.classList.add("lh-tooltip-boundary");this._dom.createChildOf(r,"div","lh-tooltip").textContent=n}r.classList.add("lh-report-icon",`lh-report-icon--${e}`)}}_renderReportWarnings(e){if(!e.runWarnings||0===e.runWarnings.length)return this._dom.createElement("div");const t=this._dom.createComponent("warningsToplevel");this._dom.find(".lh-warnings__msg",t).textContent=l.i18n.strings.toplevelWarningsMessage;const n=this._dom.find("ul",t);for(const t of e.runWarnings){n.appendChild(this._dom.createElement("li")).appendChild(this._dom.convertMarkdownLinkSnippets(t))}return t}_renderScoreGauges(e,t,n){const r=[],o=[],i=[];for(const a of Object.values(e.categories)){const s=n[a.id]||t,c=s.renderCategoryScore(a,e.categoryGroups||{},{gatherMode:e.gatherMode}),d=this._dom.find("a.lh-gauge__wrapper, a.lh-fraction__wrapper",c);d&&(this._dom.safelySetHref(d,`#${a.id}`),d.addEventListener("click",(e=>{if(!d.matches('[href^="#"]'))return;const t=d.getAttribute("href"),n=this._dom.rootEl;if(!t||!n)return;const r=this._dom.find(t,n);e.preventDefault(),r.scrollIntoView()})),this._opts.onPageAnchorRendered?.(d)),l.isPluginCategory(a.id)?i.push(c):s.renderCategoryScore===t.renderCategoryScore?r.push(c):o.push(c)}return[...r,...o,...i]}_renderReport(e){const t=new L(e.configSettings.locale,{...l.UIStrings,...e.i18n.rendererFormattedStrings});l.i18n=t,l.reportJson=e;const n=e.audits["full-page-screenshot"]&&e.audits["full-page-screenshot"].details&&"full-page-screenshot"===e.audits["full-page-screenshot"].details.type?e.audits["full-page-screenshot"].details:void 0,r=new z(this._dom,{fullPageScreenshot:n}),o=new c(this._dom,r),i={performance:new M(this._dom,r),pwa:new F(this._dom,r)},a=this._dom.createElement("div");a.appendChild(this._renderReportHeader());const s=this._dom.createElement("div","lh-container"),d=this._dom.createElement("div","lh-report");let h;d.appendChild(this._renderReportWarnings(e));1===Object.keys(e.categories).length?a.classList.add("lh-header--solo-category"):h=this._dom.createElement("div","lh-scores-header");const p=this._dom.createElement("div");if(p.classList.add("lh-scorescale-wrap"),p.append(this._dom.createComponent("scorescale")),h){const t=this._dom.find(".lh-scores-container",a);h.append(...this._renderScoreGauges(e,o,i)),t.appendChild(h),t.appendChild(p);const n=this._dom.createElement("div","lh-sticky-header");n.append(...this._renderScoreGauges(e,o,i)),s.appendChild(n)}const u=d.appendChild(this._dom.createElement("div","lh-categories")),g={gatherMode:e.gatherMode};for(const t of Object.values(e.categories)){const n=i[t.id]||o;n.dom.createChildOf(u,"div","lh-category-wrapper").appendChild(n.render(t,e.categoryGroups,g))}o.injectFinalScreenshot(u,e.audits,p);const m=this._dom.createFragment();return m.append(this._dom.createComponent("styles")),this._opts.omitTopbar||m.appendChild(this._renderReportTopbar(e)),m.appendChild(s),s.appendChild(a),s.appendChild(d),d.appendChild(this._renderReportFooter(e)),n&&A.installFullPageScreenshot(this._dom.rootEl,n.screenshot),m}}
|
|
148
|
+
*/class N{constructor(e){this._dom=e,this._opts={}}renderReport(e,t,n){if(!this._dom.rootEl&&t){console.warn("Please adopt the new report API in renderer/api.js.");const e=t.closest(".lh-root");e?this._dom.rootEl=e:(t.classList.add("lh-root","lh-vars"),this._dom.rootEl=t)}else this._dom.rootEl&&t&&(this._dom.rootEl=t);n&&(this._opts=n),this._dom.setLighthouseChannel(e.configSettings.channel||"unknown");const r=l.prepareReportResult(e);return this._dom.rootEl.textContent="",this._dom.rootEl.appendChild(this._renderReport(r)),this._dom.rootEl}_renderReportTopbar(e){const t=this._dom.createComponent("topbar"),n=this._dom.find("a.lh-topbar__url",t);return n.textContent=e.finalUrl,n.title=e.finalUrl,this._dom.safelySetHref(n,e.finalUrl),t}_renderReportHeader(){const e=this._dom.createComponent("heading"),t=this._dom.createComponent("scoresWrapper");return this._dom.find(".lh-scores-wrapper-placeholder",e).replaceWith(t),e}_renderReportFooter(e){const t=this._dom.createComponent("footer");return this._renderMetaBlock(e,t),this._dom.find(".lh-footer__version_issue",t).textContent=l.i18n.strings.footerIssue,this._dom.find(".lh-footer__version",t).textContent=e.lighthouseVersion,t}_renderMetaBlock(e,t){const n=l.getEmulationDescriptions(e.configSettings||{}),r=e.userAgent.match(/(\w*Chrome\/[\d.]+)/),o=Array.isArray(r)?r[1].replace("/"," ").replace("Chrome","Chromium"):"Chromium",i=e.configSettings.channel,a=e.environment.benchmarkIndex.toFixed(0),s=e.environment.credits?.["axe-core"],c=[["date",`Captured at ${l.i18n.formatDateTime(e.fetchTime)}`],["devices",`${n.deviceEmulation} with Lighthouse ${e.lighthouseVersion}`,`${l.i18n.strings.runtimeSettingsBenchmark}: ${a}\n${l.i18n.strings.runtimeSettingsCPUThrottling}: ${n.cpuThrottling}`+(s?`\n${l.i18n.strings.runtimeSettingsAxeVersion}: ${s}`:"")],["samples-one",l.i18n.strings.runtimeSingleLoad,l.i18n.strings.runtimeSingleLoadTooltip],["stopwatch",l.i18n.strings.runtimeAnalysisWindow],["networkspeed",`${n.summary}`,`${l.i18n.strings.runtimeSettingsNetworkThrottling}: ${n.networkThrottling}`],["chrome",`Using ${o}`+(i?` with ${i}`:""),`${l.i18n.strings.runtimeSettingsUANetwork}: "${e.environment.networkUserAgent}"`]],d=this._dom.find(".lh-meta__items",t);for(const[e,t,n]of c){const r=this._dom.createChildOf(d,"li","lh-meta__item");if(r.textContent=t,n){r.classList.add("lh-tooltip-boundary");this._dom.createChildOf(r,"div","lh-tooltip").textContent=n}r.classList.add("lh-report-icon",`lh-report-icon--${e}`)}}_renderReportWarnings(e){if(!e.runWarnings||0===e.runWarnings.length)return this._dom.createElement("div");const t=this._dom.createComponent("warningsToplevel");this._dom.find(".lh-warnings__msg",t).textContent=l.i18n.strings.toplevelWarningsMessage;const n=this._dom.find("ul",t);for(const t of e.runWarnings){n.appendChild(this._dom.createElement("li")).appendChild(this._dom.convertMarkdownLinkSnippets(t))}return t}_renderScoreGauges(e,t,n){const r=[],o=[],i=[];for(const a of Object.values(e.categories)){const s=n[a.id]||t,c=s.renderCategoryScore(a,e.categoryGroups||{},{gatherMode:e.gatherMode}),d=this._dom.find("a.lh-gauge__wrapper, a.lh-fraction__wrapper",c);d&&(this._dom.safelySetHref(d,`#${a.id}`),d.addEventListener("click",(e=>{if(!d.matches('[href^="#"]'))return;const t=d.getAttribute("href"),n=this._dom.rootEl;if(!t||!n)return;const r=this._dom.find(t,n);e.preventDefault(),r.scrollIntoView()})),this._opts.onPageAnchorRendered?.(d)),l.isPluginCategory(a.id)?i.push(c):s.renderCategoryScore===t.renderCategoryScore?r.push(c):o.push(c)}return[...r,...o,...i]}_renderReport(e){const t=new L(e.configSettings.locale,{...l.UIStrings,...e.i18n.rendererFormattedStrings});l.i18n=t,l.reportJson=e;const n=e.audits["full-page-screenshot"]&&e.audits["full-page-screenshot"].details&&"full-page-screenshot"===e.audits["full-page-screenshot"].details.type?e.audits["full-page-screenshot"].details:void 0,r=new z(this._dom,{fullPageScreenshot:n}),o=new c(this._dom,r),i={performance:new M(this._dom,r),pwa:new F(this._dom,r)},a=this._dom.createElement("div");a.appendChild(this._renderReportHeader());const s=this._dom.createElement("div","lh-container"),d=this._dom.createElement("div","lh-report");let h;d.appendChild(this._renderReportWarnings(e));1===Object.keys(e.categories).length?a.classList.add("lh-header--solo-category"):h=this._dom.createElement("div","lh-scores-header");const p=this._dom.createElement("div");if(p.classList.add("lh-scorescale-wrap"),p.append(this._dom.createComponent("scorescale")),h){const t=this._dom.find(".lh-scores-container",a);h.append(...this._renderScoreGauges(e,o,i)),t.appendChild(h),t.appendChild(p);const n=this._dom.createElement("div","lh-sticky-header");n.append(...this._renderScoreGauges(e,o,i)),s.appendChild(n)}const u=d.appendChild(this._dom.createElement("div","lh-categories")),g={gatherMode:e.gatherMode};for(const t of Object.values(e.categories)){const n=i[t.id]||o;n.dom.createChildOf(u,"div","lh-category-wrapper").appendChild(n.render(t,e.categoryGroups,g))}o.injectFinalScreenshot(u,e.audits,p);const m=this._dom.createFragment();return this._opts.omitGlobalStyles||m.append(this._dom.createComponent("styles")),this._opts.omitTopbar||m.appendChild(this._renderReportTopbar(e)),m.appendChild(s),s.appendChild(a),s.appendChild(d),d.appendChild(this._renderReportFooter(e)),n&&A.installFullPageScreenshot(this._dom.rootEl,n.screenshot),m}}
|
|
149
149
|
/**
|
|
150
150
|
* @license Copyright 2021 The Lighthouse Authors. All Rights Reserved.
|
|
151
151
|
* 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
|
|
@@ -187,7 +187,7 @@ class O{constructor(e){this._dom=e,this._toggleEl,this._menuEl,this.onDocumentKe
|
|
|
187
187
|
* @license Copyright 2017 The Lighthouse Authors. All Rights Reserved.
|
|
188
188
|
* 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
|
|
189
189
|
* 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.
|
|
190
|
-
*/function V(e,t){const n=t?new Date(t):new Date,r=n.toLocaleTimeString("en-US",{hour12:!1}),o=n.toLocaleDateString("en-US",{year:"numeric",month:"2-digit",day:"2-digit"}).split("/");o.unshift(o.pop());return`${e}_${o.join("-")}_${r}`.replace(/[/?<>\\:*|"]/g,"-")}var G={getLhrFilenamePrefix:function(e){return V(new URL(e.finalUrl).hostname,e.fetchTime)},getFilenamePrefix:V};
|
|
190
|
+
*/function V(e,t){const n=t?new Date(t):new Date,r=n.toLocaleTimeString("en-US",{hour12:!1}),o=n.toLocaleDateString("en-US",{year:"numeric",month:"2-digit",day:"2-digit"}).split("/");o.unshift(o.pop());return`${e}_${o.join("-")}_${r}`.replace(/[/?<>\\:*|"]/g,"-")}var G={getLhrFilenamePrefix:function(e){return V(new URL(e.finalUrl).hostname,e.fetchTime)},getFilenamePrefix:V,getFlowResultFilenamePrefix:function(e){const t=e.steps[0].lhr;return V(e.name.replace(/\s/g,"-"),t.fetchTime)}};
|
|
191
191
|
/**
|
|
192
192
|
* @license
|
|
193
193
|
* Copyright 2017 The Lighthouse Authors. All Rights Reserved.
|
|
@@ -0,0 +1,18 @@
|
|
|
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, h} from 'preact';
|
|
8
|
+
|
|
9
|
+
import {App} from './src/app';
|
|
10
|
+
|
|
11
|
+
export function renderFlowReport(
|
|
12
|
+
flowResult: LH.FlowResult,
|
|
13
|
+
root: HTMLElement,
|
|
14
|
+
options?: LH.FlowReportOptions
|
|
15
|
+
) {
|
|
16
|
+
root.classList.add('flow-vars', 'lh-vars', 'lh-root');
|
|
17
|
+
render(h(App, {flowResult, options}), root);
|
|
18
|
+
}
|
|
@@ -9,15 +9,14 @@
|
|
|
9
9
|
* The renderer code is bundled and injected into the report HTML along with the JSON report.
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
|
-
import {
|
|
13
|
-
|
|
14
|
-
import {App} from './src/app';
|
|
12
|
+
import {renderFlowReport} from '../api';
|
|
15
13
|
|
|
16
14
|
function __initLighthouseFlowReport__() {
|
|
17
15
|
const container = document.body.querySelector('main');
|
|
18
16
|
if (!container) throw Error('Container element not found');
|
|
19
|
-
|
|
20
|
-
|
|
17
|
+
renderFlowReport(window.__LIGHTHOUSE_FLOW_JSON__, container, {
|
|
18
|
+
getReportHtml: () => document.documentElement.outerHTML,
|
|
19
|
+
});
|
|
21
20
|
}
|
|
22
21
|
|
|
23
22
|
window.__initLighthouseFlowReport__ = __initLighthouseFlowReport__;
|
package/flow-report/src/app.tsx
CHANGED
|
@@ -5,15 +5,16 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import {FunctionComponent} from 'preact';
|
|
8
|
-
import {useLayoutEffect, useRef, useState} from 'preact/hooks';
|
|
8
|
+
import {useLayoutEffect, useMemo, useRef, useState} from 'preact/hooks';
|
|
9
9
|
|
|
10
10
|
import {Sidebar} from './sidebar/sidebar';
|
|
11
11
|
import {Summary} from './summary/summary';
|
|
12
|
-
import {classNames, FlowResultContext, useHashState} from './util';
|
|
12
|
+
import {classNames, FlowResultContext, OptionsContext, useHashState} from './util';
|
|
13
13
|
import {Report} from './wrappers/report';
|
|
14
14
|
import {Topbar} from './topbar';
|
|
15
15
|
import {Header} from './header';
|
|
16
16
|
import {I18nProvider} from './i18n/i18n';
|
|
17
|
+
import {Styles} from './wrappers/styles';
|
|
17
18
|
|
|
18
19
|
function getAnchorElement(hashState: LH.FlowResult.HashState|null) {
|
|
19
20
|
if (!hashState || !hashState.anchor) return null;
|
|
@@ -47,17 +48,24 @@ const Content: FunctionComponent = () => {
|
|
|
47
48
|
);
|
|
48
49
|
};
|
|
49
50
|
|
|
50
|
-
export const App: FunctionComponent<{
|
|
51
|
+
export const App: FunctionComponent<{
|
|
52
|
+
flowResult: LH.FlowResult,
|
|
53
|
+
options?: LH.FlowReportOptions
|
|
54
|
+
}> = ({flowResult, options}) => {
|
|
51
55
|
const [collapsed, setCollapsed] = useState(false);
|
|
56
|
+
const optionsValue = useMemo(() => options || {}, [options]);
|
|
52
57
|
return (
|
|
53
|
-
<
|
|
54
|
-
<
|
|
55
|
-
<
|
|
56
|
-
<
|
|
57
|
-
<
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
58
|
+
<OptionsContext.Provider value={optionsValue}>
|
|
59
|
+
<FlowResultContext.Provider value={flowResult}>
|
|
60
|
+
<I18nProvider>
|
|
61
|
+
<Styles/>
|
|
62
|
+
<div className={classNames('App', {'App--collapsed': collapsed})} data-testid="App">
|
|
63
|
+
<Topbar onMenuClick={() => setCollapsed(c => !c)} />
|
|
64
|
+
<Sidebar/>
|
|
65
|
+
<Content/>
|
|
66
|
+
</div>
|
|
67
|
+
</I18nProvider>
|
|
68
|
+
</FlowResultContext.Provider>
|
|
69
|
+
</OptionsContext.Provider>
|
|
62
70
|
);
|
|
63
71
|
};
|
|
@@ -48,8 +48,6 @@ export const SidebarFlow: FunctionComponent = () => {
|
|
|
48
48
|
{
|
|
49
49
|
flowResult.steps.map((step, index) => {
|
|
50
50
|
const {lhr, name} = step;
|
|
51
|
-
const url = new URL(location.href);
|
|
52
|
-
url.hash = `#index=${index}`;
|
|
53
51
|
return <>
|
|
54
52
|
{
|
|
55
53
|
lhr.gatherMode === 'navigation' && index !== 0 ?
|
|
@@ -59,7 +57,7 @@ export const SidebarFlow: FunctionComponent = () => {
|
|
|
59
57
|
<SidebarFlowStep
|
|
60
58
|
key={lhr.fetchTime}
|
|
61
59
|
mode={lhr.gatherMode}
|
|
62
|
-
href={
|
|
60
|
+
href={`#index=${index}`}
|
|
63
61
|
label={name}
|
|
64
62
|
isCurrent={index === (hashState && hashState.index)}
|
|
65
63
|
/>
|
|
@@ -17,11 +17,9 @@ const SidebarSummary: FunctionComponent = () => {
|
|
|
17
17
|
const hashState = useHashState();
|
|
18
18
|
const strings = useLocalizedStrings();
|
|
19
19
|
|
|
20
|
-
const url = new URL(location.href);
|
|
21
|
-
url.hash = '#';
|
|
22
20
|
return (
|
|
23
21
|
<a
|
|
24
|
-
href=
|
|
22
|
+
href="#"
|
|
25
23
|
className={classNames('SidebarSummary', {'Sidebar--current': hashState === null})}
|
|
26
24
|
data-testid="SidebarSummary"
|
|
27
25
|
>
|
|
@@ -8,20 +8,15 @@ import {FunctionComponent, JSX} from 'preact';
|
|
|
8
8
|
import {useState} from 'preact/hooks';
|
|
9
9
|
|
|
10
10
|
import {HelpDialog} from './help-dialog';
|
|
11
|
-
import {
|
|
11
|
+
import {getFlowResultFilenamePrefix} from '../../report/generator/file-namer';
|
|
12
12
|
import {useLocalizedStrings} from './i18n/i18n';
|
|
13
13
|
import {HamburgerIcon, InfoIcon} from './icons';
|
|
14
|
-
import {useFlowResult} from './util';
|
|
14
|
+
import {useFlowResult, useOptions} from './util';
|
|
15
15
|
import {saveFile} from '../../report/renderer/api';
|
|
16
16
|
|
|
17
|
-
function saveHtml(flowResult: LH.FlowResult) {
|
|
18
|
-
const htmlStr = document.documentElement.outerHTML;
|
|
17
|
+
function saveHtml(flowResult: LH.FlowResult, htmlStr: string) {
|
|
19
18
|
const blob = new Blob([htmlStr], {type: 'text/html'});
|
|
20
|
-
|
|
21
|
-
const lhr = flowResult.steps[0].lhr;
|
|
22
|
-
const name = flowResult.name.replace(/\s/g, '-');
|
|
23
|
-
const filename = getFilenamePrefix(name, lhr.fetchTime);
|
|
24
|
-
|
|
19
|
+
const filename = getFlowResultFilenamePrefix(flowResult);
|
|
25
20
|
saveFile(blob, filename);
|
|
26
21
|
}
|
|
27
22
|
|
|
@@ -78,6 +73,7 @@ export const Topbar: FunctionComponent<{onMenuClick: JSX.MouseEventHandler<HTMLB
|
|
|
78
73
|
const flowResult = useFlowResult();
|
|
79
74
|
const strings = useLocalizedStrings();
|
|
80
75
|
const [showHelpDialog, setShowHelpDialog] = useState(false);
|
|
76
|
+
const {getReportHtml, saveAsGist} = useOptions();
|
|
81
77
|
|
|
82
78
|
return (
|
|
83
79
|
<div className="Topbar">
|
|
@@ -88,10 +84,23 @@ export const Topbar: FunctionComponent<{onMenuClick: JSX.MouseEventHandler<HTMLB
|
|
|
88
84
|
<Logo/>
|
|
89
85
|
</div>
|
|
90
86
|
<div className="Topbar__title">{strings.title}</div>
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
87
|
+
{
|
|
88
|
+
getReportHtml &&
|
|
89
|
+
<TopbarButton
|
|
90
|
+
onClick={() => {
|
|
91
|
+
const htmlStr = getReportHtml(flowResult);
|
|
92
|
+
saveHtml(flowResult, htmlStr);
|
|
93
|
+
}}
|
|
94
|
+
label="Button that saves the report as HTML"
|
|
95
|
+
>{strings.save}</TopbarButton>
|
|
96
|
+
}
|
|
97
|
+
{
|
|
98
|
+
saveAsGist &&
|
|
99
|
+
<TopbarButton
|
|
100
|
+
onClick={() => saveAsGist(flowResult)}
|
|
101
|
+
label="Button that saves the report to a gist"
|
|
102
|
+
>{strings.dropdownSaveGist}</TopbarButton>
|
|
103
|
+
}
|
|
95
104
|
<div style={{flexGrow: 1}} />
|
|
96
105
|
<TopbarButton
|
|
97
106
|
onClick={() => setShowHelpDialog(previous => !previous)}
|
package/flow-report/src/util.ts
CHANGED
|
@@ -10,6 +10,7 @@ import {useContext, useEffect, useLayoutEffect, useMemo, useRef, useState} from
|
|
|
10
10
|
import type {UIStringsType} from './i18n/ui-strings';
|
|
11
11
|
|
|
12
12
|
const FlowResultContext = createContext<LH.FlowResult|undefined>(undefined);
|
|
13
|
+
const OptionsContext = createContext<LH.FlowReportOptions>({});
|
|
13
14
|
|
|
14
15
|
function getHashParam(param: string): string|null {
|
|
15
16
|
const params = new URLSearchParams(location.hash.replace('#', '?'));
|
|
@@ -147,8 +148,13 @@ function useExternalRenderer<T extends Element>(
|
|
|
147
148
|
return ref;
|
|
148
149
|
}
|
|
149
150
|
|
|
151
|
+
function useOptions() {
|
|
152
|
+
return useContext(OptionsContext);
|
|
153
|
+
}
|
|
154
|
+
|
|
150
155
|
export {
|
|
151
156
|
FlowResultContext,
|
|
157
|
+
OptionsContext,
|
|
152
158
|
classNames,
|
|
153
159
|
getScreenDimensions,
|
|
154
160
|
getFullPageScreenshot,
|
|
@@ -158,4 +164,5 @@ export {
|
|
|
158
164
|
useHashParams,
|
|
159
165
|
useHashState,
|
|
160
166
|
useExternalRenderer,
|
|
167
|
+
useOptions,
|
|
161
168
|
};
|
|
@@ -35,6 +35,7 @@ export const Report: FunctionComponent<{hashState: LH.FlowResult.HashState}> =
|
|
|
35
35
|
return renderReport(hashState.currentLhr, {
|
|
36
36
|
disableAutoDarkModeAndFireworks: true,
|
|
37
37
|
omitTopbar: true,
|
|
38
|
+
omitGlobalStyles: true,
|
|
38
39
|
onPageAnchorRendered: link => convertAnchor(link, hashState.index),
|
|
39
40
|
});
|
|
40
41
|
}, [hashState]);
|
|
@@ -0,0 +1,16 @@
|
|
|
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 {FunctionComponent} from 'preact';
|
|
8
|
+
|
|
9
|
+
import {createStylesElement} from '../../../report/renderer/api';
|
|
10
|
+
import {useExternalRenderer} from '../util';
|
|
11
|
+
|
|
12
|
+
export const Styles: FunctionComponent = () => {
|
|
13
|
+
const ref = useExternalRenderer<HTMLDivElement>(createStylesElement);
|
|
14
|
+
|
|
15
|
+
return <div ref={ref}/>;
|
|
16
|
+
};
|
|
@@ -8,7 +8,7 @@ import {jest} from '@jest/globals';
|
|
|
8
8
|
import {FunctionComponent} from 'preact';
|
|
9
9
|
import {act, render} from '@testing-library/preact';
|
|
10
10
|
|
|
11
|
-
import {FlowResultContext} from '../src/util';
|
|
11
|
+
import {FlowResultContext, OptionsContext} from '../src/util';
|
|
12
12
|
import {I18nProvider} from '../src/i18n/i18n';
|
|
13
13
|
|
|
14
14
|
const mockSaveFile = jest.fn();
|
|
@@ -31,19 +31,24 @@ const flowResult = {
|
|
|
31
31
|
} as any;
|
|
32
32
|
|
|
33
33
|
let wrapper: FunctionComponent;
|
|
34
|
+
let options: LH.FlowReportOptions;
|
|
34
35
|
|
|
35
36
|
beforeEach(() => {
|
|
36
37
|
mockSaveFile.mockReset();
|
|
38
|
+
options = {};
|
|
37
39
|
wrapper = ({children}) => (
|
|
38
|
-
<
|
|
39
|
-
<
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
40
|
+
<OptionsContext.Provider value={options}>
|
|
41
|
+
<FlowResultContext.Provider value={flowResult}>
|
|
42
|
+
<I18nProvider>
|
|
43
|
+
{children}
|
|
44
|
+
</I18nProvider>
|
|
45
|
+
</FlowResultContext.Provider>
|
|
46
|
+
</OptionsContext.Provider>
|
|
43
47
|
);
|
|
44
48
|
});
|
|
45
49
|
|
|
46
50
|
it('save button opens save dialog for HTML file', async () => {
|
|
51
|
+
options = {getReportHtml: () => '<html></html>'};
|
|
47
52
|
const root = render(<Topbar onMenuClick={() => {}}/>, {wrapper});
|
|
48
53
|
|
|
49
54
|
const saveButton = root.getByText('Save');
|
|
@@ -55,6 +60,17 @@ it('save button opens save dialog for HTML file', async () => {
|
|
|
55
60
|
);
|
|
56
61
|
});
|
|
57
62
|
|
|
63
|
+
it('provides save as gist option if defined', async () => {
|
|
64
|
+
const saveAsGist = jest.fn();
|
|
65
|
+
options = {saveAsGist};
|
|
66
|
+
const root = render(<Topbar onMenuClick={() => {}}/>, {wrapper});
|
|
67
|
+
|
|
68
|
+
const saveButton = root.getByText('Save as Gist');
|
|
69
|
+
saveButton.click();
|
|
70
|
+
|
|
71
|
+
expect(saveAsGist).toHaveBeenCalledWith(flowResult);
|
|
72
|
+
});
|
|
73
|
+
|
|
58
74
|
it('toggles help dialog', async () => {
|
|
59
75
|
const root = render(<Topbar onMenuClick={() => {}}/>, {wrapper});
|
|
60
76
|
|
|
@@ -20,6 +20,10 @@ declare global {
|
|
|
20
20
|
// Expose global types in LH namespace.
|
|
21
21
|
module LH {
|
|
22
22
|
export type ConfigSettings = Settings.ConfigSettings;
|
|
23
|
+
export interface FlowReportOptions {
|
|
24
|
+
getReportHtml?: (flowResult: FlowResult_) => string;
|
|
25
|
+
saveAsGist?: (flowResult: FlowResult_) => void;
|
|
26
|
+
}
|
|
23
27
|
|
|
24
28
|
export import FlowResult = FlowResult_;
|
|
25
29
|
}
|
|
@@ -23,14 +23,6 @@ const stackPacksToInclude = [
|
|
|
23
23
|
packId: 'drupal',
|
|
24
24
|
requiredStacks: ['js:drupal'],
|
|
25
25
|
},
|
|
26
|
-
{
|
|
27
|
-
packId: 'react',
|
|
28
|
-
requiredStacks: ['js:react'],
|
|
29
|
-
},
|
|
30
|
-
{
|
|
31
|
-
packId: 'angular',
|
|
32
|
-
requiredStacks: ['js:@angular/core'],
|
|
33
|
-
},
|
|
34
26
|
{
|
|
35
27
|
packId: 'amp',
|
|
36
28
|
requiredStacks: ['js:amp'],
|
|
@@ -47,6 +39,18 @@ const stackPacksToInclude = [
|
|
|
47
39
|
packId: 'joomla',
|
|
48
40
|
requiredStacks: ['js:joomla'],
|
|
49
41
|
},
|
|
42
|
+
{
|
|
43
|
+
packId: 'next.js',
|
|
44
|
+
requiredStacks: ['js:next'],
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
packId: 'angular',
|
|
48
|
+
requiredStacks: ['js:@angular/core'],
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
packId: 'react',
|
|
52
|
+
requiredStacks: ['js:react'],
|
|
53
|
+
},
|
|
50
54
|
];
|
|
51
55
|
|
|
52
56
|
/**
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lighthouse",
|
|
3
|
-
"version": "9.
|
|
3
|
+
"version": "9.1.0-dev.20211126",
|
|
4
4
|
"description": "Automated auditing, performance metrics, and best practices for the web.",
|
|
5
5
|
"main": "./lighthouse-core/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -196,7 +196,7 @@
|
|
|
196
196
|
"jpeg-js": "^0.4.1",
|
|
197
197
|
"js-library-detector": "^6.4.0",
|
|
198
198
|
"lighthouse-logger": "^1.3.0",
|
|
199
|
-
"lighthouse-stack-packs": "^1.
|
|
199
|
+
"lighthouse-stack-packs": "^1.6.0",
|
|
200
200
|
"lodash.clonedeep": "^4.5.0",
|
|
201
201
|
"lodash.get": "^4.4.2",
|
|
202
202
|
"lodash.isequal": "^4.5.0",
|
|
@@ -43,4 +43,15 @@ function getLhrFilenamePrefix(lhr) {
|
|
|
43
43
|
return getFilenamePrefix(hostname, lhr.fetchTime);
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
-
|
|
46
|
+
/**
|
|
47
|
+
* Generate a filenamePrefix of name_YYYY-MM-DD_HH-MM-SS.
|
|
48
|
+
* @param {{name: string, steps: Array<{lhr: {fetchTime: string}}>}} flowResult
|
|
49
|
+
* @return {string}
|
|
50
|
+
*/
|
|
51
|
+
function getFlowResultFilenamePrefix(flowResult) {
|
|
52
|
+
const lhr = flowResult.steps[0].lhr;
|
|
53
|
+
const name = flowResult.name.replace(/\s/g, '-');
|
|
54
|
+
return getFilenamePrefix(name, lhr.fetchTime);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
module.exports = {getLhrFilenamePrefix, getFilenamePrefix, getFlowResultFilenamePrefix};
|
package/report/renderer/api.js
CHANGED
|
@@ -63,4 +63,18 @@ function convertMarkdownCodeSnippets(markdownText) {
|
|
|
63
63
|
return dom.convertMarkdownCodeSnippets(markdownText);
|
|
64
64
|
}
|
|
65
65
|
|
|
66
|
-
|
|
66
|
+
/**
|
|
67
|
+
* @return {DocumentFragment}
|
|
68
|
+
*/
|
|
69
|
+
function createStylesElement() {
|
|
70
|
+
const dom = new DOM(document, document.documentElement);
|
|
71
|
+
return dom.createComponent('styles');
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export {
|
|
75
|
+
renderReport,
|
|
76
|
+
renderCategoryScore,
|
|
77
|
+
saveFile,
|
|
78
|
+
convertMarkdownCodeSnippets,
|
|
79
|
+
createStylesElement,
|
|
80
|
+
};
|
|
@@ -29,6 +29,7 @@ import {PerformanceCategoryRenderer} from './performance-category-renderer.js';
|
|
|
29
29
|
import {PwaCategoryRenderer} from './pwa-category-renderer.js';
|
|
30
30
|
import {Util} from './util.js';
|
|
31
31
|
|
|
32
|
+
|
|
32
33
|
export class ReportRenderer {
|
|
33
34
|
/**
|
|
34
35
|
* @param {DOM} dom
|
|
@@ -322,7 +323,9 @@ export class ReportRenderer {
|
|
|
322
323
|
categoryRenderer.injectFinalScreenshot(categories, report.audits, scoreScale);
|
|
323
324
|
|
|
324
325
|
const reportFragment = this._dom.createFragment();
|
|
325
|
-
|
|
326
|
+
if (!this._opts.omitGlobalStyles) {
|
|
327
|
+
reportFragment.append(this._dom.createComponent('styles'));
|
|
328
|
+
}
|
|
326
329
|
|
|
327
330
|
if (!this._opts.omitTopbar) {
|
|
328
331
|
reportFragment.appendChild(this._renderReportTopbar(report));
|
|
@@ -17,6 +17,8 @@ declare module Renderer {
|
|
|
17
17
|
|
|
18
18
|
/** Disable the topbar UI component */
|
|
19
19
|
omitTopbar?: boolean;
|
|
20
|
+
/** Prevent injection of report styles. Set to true if these styles are already included by the environment. */
|
|
21
|
+
omitGlobalStyles?: boolean;
|
|
20
22
|
/**
|
|
21
23
|
* Convert report anchor links to a different format.
|
|
22
24
|
* Flow report uses this to convert `#seo` to `#index=0&anchor=seo`.
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"message": "Всички отчети"
|
|
4
4
|
},
|
|
5
5
|
"flow-report/src/i18n/ui-strings.js | categories": {
|
|
6
|
-
"message": "
|
|
6
|
+
"message": "Категории"
|
|
7
7
|
},
|
|
8
8
|
"flow-report/src/i18n/ui-strings.js | categoryAccessibility": {
|
|
9
9
|
"message": "Достъпност"
|
|
@@ -24,40 +24,40 @@
|
|
|
24
24
|
"message": "Настолни компютри"
|
|
25
25
|
},
|
|
26
26
|
"flow-report/src/i18n/ui-strings.js | helpDialogTitle": {
|
|
27
|
-
"message": "
|
|
27
|
+
"message": "Тълкуване на отчета на Lighthouse за навигацията"
|
|
28
28
|
},
|
|
29
29
|
"flow-report/src/i18n/ui-strings.js | helpLabel": {
|
|
30
|
-
"message": "
|
|
30
|
+
"message": "Информация за навигацията"
|
|
31
31
|
},
|
|
32
32
|
"flow-report/src/i18n/ui-strings.js | helpUseCaseInstructionNavigation": {
|
|
33
|
-
"message": "
|
|
33
|
+
"message": "Използване на отчетите за навигирането за..."
|
|
34
34
|
},
|
|
35
35
|
"flow-report/src/i18n/ui-strings.js | helpUseCaseInstructionSnapshot": {
|
|
36
|
-
"message": "
|
|
36
|
+
"message": "Използвайте отчетите за моментната снимка за..."
|
|
37
37
|
},
|
|
38
38
|
"flow-report/src/i18n/ui-strings.js | helpUseCaseInstructionTimespan": {
|
|
39
|
-
"message": "
|
|
39
|
+
"message": "Използване на отчетите за периода от време за..."
|
|
40
40
|
},
|
|
41
41
|
"flow-report/src/i18n/ui-strings.js | helpUseCaseNavigation1": {
|
|
42
|
-
"message": "
|
|
42
|
+
"message": "Получаване на резултат за ефективността от Lighthouse."
|
|
43
43
|
},
|
|
44
44
|
"flow-report/src/i18n/ui-strings.js | helpUseCaseNavigation2": {
|
|
45
|
-
"message": "
|
|
45
|
+
"message": "Измерване на показатели за ефективността при зареждане на страниците, като например изобразяване на най-голямото съдържание (LCP) и индекс на скоростта."
|
|
46
46
|
},
|
|
47
47
|
"flow-report/src/i18n/ui-strings.js | helpUseCaseNavigation3": {
|
|
48
|
-
"message": "
|
|
48
|
+
"message": "Тестване на възможностите на прогресивни уеб приложения."
|
|
49
49
|
},
|
|
50
50
|
"flow-report/src/i18n/ui-strings.js | helpUseCaseSnapshot1": {
|
|
51
|
-
"message": "
|
|
51
|
+
"message": "Намиране на проблеми с достъпността в приложения от една страница и сложни формуляри."
|
|
52
52
|
},
|
|
53
53
|
"flow-report/src/i18n/ui-strings.js | helpUseCaseSnapshot2": {
|
|
54
|
-
"message": "
|
|
54
|
+
"message": "Анализ на най-добрите практики, свързани с взаимодействията с менюта и елементи на ПИ."
|
|
55
55
|
},
|
|
56
56
|
"flow-report/src/i18n/ui-strings.js | helpUseCaseTimespan1": {
|
|
57
|
-
"message": "
|
|
57
|
+
"message": "Измерване на структурните промени и времето за изпълнение на JavaScript за поредица от взаимодействия."
|
|
58
58
|
},
|
|
59
59
|
"flow-report/src/i18n/ui-strings.js | helpUseCaseTimespan2": {
|
|
60
|
-
"message": "
|
|
60
|
+
"message": "Откриване на възможности за подобряване на ефективността на продължително отворените страници и приложенията от една страница."
|
|
61
61
|
},
|
|
62
62
|
"flow-report/src/i18n/ui-strings.js | highestImpact": {
|
|
63
63
|
"message": "С най-голямо въздействие"
|
|
@@ -72,7 +72,7 @@
|
|
|
72
72
|
"message": "Зареждане на страницата"
|
|
73
73
|
},
|
|
74
74
|
"flow-report/src/i18n/ui-strings.js | navigationLongDescription": {
|
|
75
|
-
"message": "
|
|
75
|
+
"message": "Отчетите за навигацията анализират зареждането на отделни страници, точно както първоначалните отчети на Lighthouse."
|
|
76
76
|
},
|
|
77
77
|
"flow-report/src/i18n/ui-strings.js | navigationReport": {
|
|
78
78
|
"message": "Отчет за навигирането"
|
|
@@ -99,13 +99,13 @@
|
|
|
99
99
|
"message": "Добра"
|
|
100
100
|
},
|
|
101
101
|
"flow-report/src/i18n/ui-strings.js | save": {
|
|
102
|
-
"message": "
|
|
102
|
+
"message": "Запазване"
|
|
103
103
|
},
|
|
104
104
|
"flow-report/src/i18n/ui-strings.js | snapshotDescription": {
|
|
105
105
|
"message": "Моментно състояние на страницата"
|
|
106
106
|
},
|
|
107
107
|
"flow-report/src/i18n/ui-strings.js | snapshotLongDescription": {
|
|
108
|
-
"message": "
|
|
108
|
+
"message": "Отчетите за моментната снимка анализират страницата в определено състояние, обикновено след потребителски взаимодействия."
|
|
109
109
|
},
|
|
110
110
|
"flow-report/src/i18n/ui-strings.js | snapshotReport": {
|
|
111
111
|
"message": "Отчет за моментното състояние"
|
|
@@ -120,7 +120,7 @@
|
|
|
120
120
|
"message": "Потребителски взаимодействия"
|
|
121
121
|
},
|
|
122
122
|
"flow-report/src/i18n/ui-strings.js | timespanLongDescription": {
|
|
123
|
-
"message": "
|
|
123
|
+
"message": "Отчетите за периода от време анализират произволен времеви интервал, който обикновено съдържа потребителски взаимодействия."
|
|
124
124
|
},
|
|
125
125
|
"flow-report/src/i18n/ui-strings.js | timespanReport": {
|
|
126
126
|
"message": "Отчет за периода от време"
|