UNPKG

lighthouse

Version:

Automated auditing, performance metrics, and best practices for the web.

159 lines (129 loc) 4.42 kB
/** * @license * Copyright 2021 Google LLC * SPDX-License-Identifier: Apache-2.0 */ import {createContext} from 'preact'; import {useContext, useEffect, useLayoutEffect, useMemo, useRef, useState} from 'preact/hooks'; import type {UIStringsType} from './i18n/ui-strings'; const FlowResultContext = createContext<LH.FlowResult|undefined>(undefined); const OptionsContext = createContext<LH.FlowReportOptions>({}); function getHashParam(param: string): string|null { const params = new URLSearchParams(location.hash.replace('#', '?')); return params.get(param); } function classNames(...args: Array<string|undefined|Record<string, boolean>>): string { const classes = []; for (const arg of args) { if (!arg) continue; if (typeof arg === 'string') { classes.push(arg); continue; } const applicableClasses = Object.entries(arg) .filter(([_, shouldApply]) => shouldApply) .map(([className]) => className); classes.push(...applicableClasses); } return classes.join(' '); } function getScreenDimensions(reportResult: LH.Result) { const {width, height} = reportResult.configSettings.screenEmulation; return {width, height}; } function getFilmstripFrames( reportResult: LH.Result ): Array<{data: string}> | undefined { const filmstripAudit = reportResult.audits['screenshot-thumbnails']; if (!filmstripAudit) return undefined; const frameItems = filmstripAudit.details && filmstripAudit.details.type === 'filmstrip' && filmstripAudit.details.items; return frameItems || undefined; } function getModeDescription(mode: LH.Result.GatherMode, strings: UIStringsType) { switch (mode) { case 'navigation': return strings.navigationDescription; case 'timespan': return strings.timespanDescription; case 'snapshot': return strings.snapshotDescription; } } function useFlowResult(): LH.FlowResult { const flowResult = useContext(FlowResultContext); if (!flowResult) throw Error('useFlowResult must be called in the FlowResultContext'); return flowResult; } function useHashParams(...params: string[]) { const [paramValues, setParamValues] = useState(params.map(getHashParam)); // Use two-way-binding on the URL hash. // Triggers a re-render if any param changes. useEffect(() => { function hashListener() { const newParamValues = params.map(getHashParam); if (newParamValues.every((value, i) => value === paramValues[i])) return; setParamValues(newParamValues); } window.addEventListener('hashchange', hashListener); return () => window.removeEventListener('hashchange', hashListener); }, [paramValues]); return paramValues; } function useHashState(): LH.HashState|null { const flowResult = useFlowResult(); const [indexString, anchor] = useHashParams('index', 'anchor'); // Memoize result so a new object is not created on every call. return useMemo(() => { if (!indexString) return null; const index = Number(indexString); if (!Number.isFinite(index)) { // eslint-disable-next-line no-console console.warn(`Invalid hash index: ${indexString}`); return null; } const step = flowResult.steps[index]; if (!step) { // eslint-disable-next-line no-console console.warn(`No flow step at index ${index}`); return null; } return {currentLhr: step.lhr, index, anchor}; }, [indexString, flowResult, anchor]); } /** * Creates a DOM subtree from non-preact code (e.g. LH report renderer). * @param renderCallback Callback that renders a DOM subtree. * @param inputs Changes to these values will trigger a re-render of the DOM subtree. * @return Reference to the element that will contain the DOM subtree. */ function useExternalRenderer<T extends Element>( renderCallback: () => Node, inputs?: ReadonlyArray<unknown> ) { const ref = useRef<T>(null); useLayoutEffect(() => { if (!ref.current) return; const root = renderCallback(); ref.current.append(root); return () => { if (ref.current?.contains(root)) ref.current.removeChild(root); }; }, inputs); return ref; } function useOptions() { return useContext(OptionsContext); } export { FlowResultContext, OptionsContext, classNames, getScreenDimensions, getFilmstripFrames, getModeDescription, useFlowResult, useHashParams, useHashState, useExternalRenderer, useOptions, };