Next.js website for Rocky Mountain Vending company featuring: - Product catalog with Stripe integration - Service areas and parts pages - Admin dashboard with Clerk authentication - SEO optimized pages with JSON-LD structured data Co-authored-by: Cursor <cursoragent@cursor.com>
194 lines
6.6 KiB
Text
194 lines
6.6 KiB
Text
/**
|
|
* @license
|
|
* Copyright 2017 Google LLC
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
/**
|
|
* @fileoverview This file contains helpers for constructing and rendering the
|
|
* critical request chains network tree.
|
|
*/
|
|
|
|
import {Globals} from './report-globals.js';
|
|
|
|
/** @typedef {import('./dom.js').DOM} DOM */
|
|
/** @typedef {import('./details-renderer.js').DetailsRenderer} DetailsRenderer */
|
|
/**
|
|
* @typedef NetworkSegment
|
|
* @property {LH.Audit.Details.SimpleCriticalRequestNode[string]|LH.Audit.Details.NetworkNode[string]} node
|
|
* @property {boolean} isLastChild
|
|
* @property {boolean} hasChildren
|
|
* @property {boolean[]} treeMarkers
|
|
*/
|
|
|
|
class CriticalRequestChainRenderer {
|
|
/**
|
|
* Helper to create context for each critical-request-chain node based on its
|
|
* parent. Calculates if this node is the last child, whether it has any
|
|
* children itself and what the tree looks like all the way back up to the root,
|
|
* so the tree markers can be drawn correctly.
|
|
* @param {LH.Audit.Details.SimpleCriticalRequestNode|LH.Audit.Details.NetworkNode} parent
|
|
* @param {string} id
|
|
* @param {Array<boolean>=} treeMarkers
|
|
* @param {boolean=} parentIsLastChild
|
|
* @return {NetworkSegment}
|
|
*/
|
|
static createSegment(parent, id, treeMarkers, parentIsLastChild) {
|
|
const node = parent[id];
|
|
const siblings = Object.keys(parent);
|
|
const isLastChild = siblings.indexOf(id) === (siblings.length - 1);
|
|
const hasChildren = !!node.children && Object.keys(node.children).length > 0;
|
|
|
|
// Copy the tree markers so that we don't change by reference.
|
|
const newTreeMarkers = Array.isArray(treeMarkers) ? treeMarkers.slice(0) : [];
|
|
|
|
// Add on the new entry.
|
|
if (typeof parentIsLastChild !== 'undefined') {
|
|
newTreeMarkers.push(!parentIsLastChild);
|
|
}
|
|
|
|
return {
|
|
node,
|
|
isLastChild,
|
|
hasChildren,
|
|
treeMarkers: newTreeMarkers,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Creates the DOM for a tree segment.
|
|
* @param {DOM} dom
|
|
* @param {NetworkSegment} segment
|
|
* @param {DetailsRenderer} detailsRenderer
|
|
* @return {Node}
|
|
*/
|
|
static createChainNode(dom, segment, detailsRenderer) {
|
|
const chainEl = dom.createComponent('crcChain');
|
|
|
|
// This can be either the duration or the chain end time depending on the detail type.
|
|
let nodeTiming;
|
|
let nodeTransferSize;
|
|
let nodeUrl;
|
|
let alwaysShowTiming;
|
|
let highlightLongest;
|
|
|
|
// `segment.node.request` indicates that this is a legacy critical request chain details node.
|
|
// For historical reasons, the legacy CRC will only show details for leaf nodes and will show
|
|
// the leaf node request duration rather than the duration of the entire tree.
|
|
if ('request' in segment.node) {
|
|
nodeTransferSize = segment.node.request.transferSize;
|
|
nodeUrl = segment.node.request.url;
|
|
nodeTiming = (segment.node.request.endTime - segment.node.request.startTime) * 1000;
|
|
alwaysShowTiming = false;
|
|
} else {
|
|
nodeTransferSize = segment.node.transferSize;
|
|
nodeUrl = segment.node.url;
|
|
nodeTiming = segment.node.navStartToEndTime;
|
|
alwaysShowTiming = true;
|
|
highlightLongest = segment.node.isLongest;
|
|
}
|
|
|
|
// Hovering over request shows full URL.
|
|
const nodeEl = dom.find('.lh-crc-node', chainEl);
|
|
nodeEl.setAttribute('title', nodeUrl);
|
|
if (highlightLongest) {
|
|
nodeEl.classList.add('lh-crc-node__longest');
|
|
}
|
|
|
|
const treeMarkeEl = dom.find('.lh-crc-node__tree-marker', chainEl);
|
|
|
|
// Construct lines and add spacers for sub requests.
|
|
segment.treeMarkers.forEach(separator => {
|
|
const classSeparator = separator ?
|
|
'lh-tree-marker lh-vert' :
|
|
'lh-tree-marker';
|
|
treeMarkeEl.append(
|
|
dom.createElement('span', classSeparator),
|
|
dom.createElement('span', 'lh-tree-marker')
|
|
);
|
|
});
|
|
|
|
const classLastChild = segment.isLastChild ?
|
|
'lh-tree-marker lh-up-right' :
|
|
'lh-tree-marker lh-vert-right';
|
|
const classHasChildren = segment.hasChildren ?
|
|
'lh-tree-marker lh-horiz-down' :
|
|
'lh-tree-marker lh-right';
|
|
|
|
treeMarkeEl.append(
|
|
dom.createElement('span', classLastChild),
|
|
dom.createElement('span', 'lh-tree-marker lh-right'),
|
|
dom.createElement('span', classHasChildren)
|
|
);
|
|
|
|
// Fill in url, host, and request size information.
|
|
const linkEl = detailsRenderer.renderTextURL(nodeUrl);
|
|
const treevalEl = dom.find('.lh-crc-node__tree-value', chainEl);
|
|
treevalEl.append(linkEl);
|
|
|
|
|
|
if (!segment.hasChildren || alwaysShowTiming) {
|
|
const span = dom.createElement('span', 'lh-crc-node__chain-duration');
|
|
span.textContent =
|
|
' - ' + Globals.i18n.formatMilliseconds(nodeTiming) + ', ';
|
|
const span2 = dom.createElement('span', 'lh-crc-node__chain-size');
|
|
span2.textContent = Globals.i18n.formatBytesToKiB(nodeTransferSize, 0.01);
|
|
|
|
treevalEl.append(span, span2);
|
|
}
|
|
|
|
return chainEl;
|
|
}
|
|
|
|
/**
|
|
* Recursively builds a tree from segments.
|
|
* @param {DOM} dom
|
|
* @param {NetworkSegment} segment
|
|
* @param {Element} elem Parent element.
|
|
* @param {DetailsRenderer} detailsRenderer
|
|
*/
|
|
static buildTree(dom, segment, elem, detailsRenderer) {
|
|
elem.append(CRCRenderer.createChainNode(dom, segment, detailsRenderer));
|
|
if (segment.node.children) {
|
|
for (const key of Object.keys(segment.node.children)) {
|
|
const childSegment = CRCRenderer.createSegment(segment.node.children, key,
|
|
segment.treeMarkers, segment.isLastChild);
|
|
CRCRenderer.buildTree(dom, childSegment, elem, detailsRenderer);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param {DOM} dom
|
|
* @param {LH.Audit.Details.CriticalRequestChain|LH.Audit.Details.NetworkTree} details
|
|
* @param {DetailsRenderer} detailsRenderer
|
|
* @return {Element}
|
|
*/
|
|
static render(dom, details, detailsRenderer) {
|
|
const tmpl = dom.createComponent('crc');
|
|
const containerEl = dom.find('.lh-crc', tmpl);
|
|
|
|
// Fill in top summary.
|
|
dom.find('.lh-crc-initial-nav', tmpl).textContent = Globals.strings.crcInitialNavigation;
|
|
dom.find('.lh-crc__longest_duration_label', tmpl).textContent =
|
|
Globals.strings.crcLongestDurationLabel;
|
|
dom.find('.lh-crc__longest_duration', tmpl).textContent =
|
|
Globals.i18n.formatMilliseconds(details.longestChain.duration);
|
|
|
|
// Construct visual tree.
|
|
const tree = details.chains;
|
|
for (const key of Object.keys(tree)) {
|
|
const segment = CRCRenderer.createSegment(tree, key);
|
|
CRCRenderer.buildTree(dom, segment, containerEl, detailsRenderer);
|
|
}
|
|
|
|
return dom.find('.lh-crc-container', tmpl);
|
|
}
|
|
}
|
|
|
|
// Alias b/c the name is really long.
|
|
const CRCRenderer = CriticalRequestChainRenderer;
|
|
|
|
export {
|
|
CriticalRequestChainRenderer,
|
|
};
|