astro
Version:
Astro is a modern site builder with web best practices, performance, and DX front-of-mind.
173 lines (172 loc) • 5.96 kB
JavaScript
import { settings } from "../../settings.js";
import { positionHighlight } from "../utils/highlight.js";
import { closeOnOutsideClick } from "../utils/window.js";
import { rulesCategories } from "./rules/index.js";
import { DevToolbarAuditListItem } from "./ui/audit-list-item.js";
import { DevToolbarAuditListWindow } from "./ui/audit-list-window.js";
import { createAuditUI } from "./ui/audit-ui.js";
const icon = '<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 1 20 16" aria-hidden="true"><path fill="#fff" d="M.6 2A1.1 1.1 0 0 1 1.7.9h16.6a1.1 1.1 0 1 1 0 2.2H1.6A1.1 1.1 0 0 1 .8 2Zm1.1 7.1h6a1.1 1.1 0 0 0 0-2.2h-6a1.1 1.1 0 0 0 0 2.2ZM9.3 13H1.8a1.1 1.1 0 1 0 0 2.2h7.5a1.1 1.1 0 1 0 0-2.2Zm11.3 1.9a1.1 1.1 0 0 1-1.5 0l-1.7-1.7a4.1 4.1 0 1 1 1.6-1.6l1.6 1.7a1.1 1.1 0 0 1 0 1.6Zm-5.3-3.4a1.9 1.9 0 1 0 0-3.8 1.9 1.9 0 0 0 0 3.8Z"/></svg>';
try {
customElements.define("astro-dev-toolbar-audit-window", DevToolbarAuditListWindow);
customElements.define("astro-dev-toolbar-audit-list-item", DevToolbarAuditListItem);
} catch {
}
let showState = false;
var audit_default = {
id: "astro:audit",
name: "Audit",
icon,
async init(canvas, eventTarget) {
let audits = [];
let auditWindow = document.createElement(
"astro-dev-toolbar-audit-window"
);
let hasCreatedUI = false;
canvas.appendChild(auditWindow);
await lint();
let mutationDebounce;
const observer = new MutationObserver(() => {
if (mutationDebounce) {
clearTimeout(mutationDebounce);
}
mutationDebounce = setTimeout(() => {
settings.logger.verboseLog("Rerunning audit lints because the DOM has been updated.");
if ("requestIdleCallback" in window) {
window.requestIdleCallback(
async () => {
lint().then(() => {
if (showState) createAuditsUI();
});
},
{ timeout: 300 }
);
} else {
setTimeout(async () => {
lint().then(() => {
if (showState) createAuditsUI();
});
}, 150);
}
}, 250);
});
setupObserver();
document.addEventListener("astro:before-preparation", () => {
observer.disconnect();
});
document.addEventListener("astro:after-swap", async () => {
lint();
});
document.addEventListener("astro:page-load", async () => {
refreshLintPositions();
setTimeout(() => {
setupObserver();
}, 100);
});
eventTarget.addEventListener("app-toggled", (event) => {
if (event.detail.state === true) {
showState = true;
createAuditsUI();
} else {
showState = false;
}
});
closeOnOutsideClick(eventTarget, () => {
const activeAudits = audits.filter((audit) => audit.card?.hasAttribute("active"));
if (activeAudits.length > 0) {
activeAudits.forEach((audit) => {
audit.card?.toggleAttribute("active", false);
});
return true;
}
return false;
});
async function createAuditsUI() {
if (hasCreatedUI) return;
const fragment = document.createDocumentFragment();
for (const audit of audits) {
const { card, highlight } = createAuditUI(audit, audits);
audit.card = card;
audit.highlight = highlight;
fragment.appendChild(highlight);
}
auditWindow.audits = audits;
canvas.appendChild(fragment);
hasCreatedUI = true;
}
async function lint() {
if (audits.length > 0) {
audits.forEach((audit) => {
audit.highlight?.remove();
audit.card?.remove();
});
audits = [];
hasCreatedUI = false;
}
const selectorCache = /* @__PURE__ */ new Map();
for (const ruleCategory of rulesCategories) {
for (const rule of ruleCategory.rules) {
const elements = selectorCache.get(rule.selector) ?? document.querySelectorAll(rule.selector);
let matches = [];
if (typeof rule.match === "undefined") {
matches = Array.from(elements);
} else {
for (const element of elements) {
try {
if (await rule.match(element)) {
matches.push(element);
}
} catch (e) {
settings.logger.error(`Error while running audit's match function: ${e}`);
}
}
}
for (const element of matches) {
if (audits.some((audit) => audit.auditedElement === element)) continue;
await createAuditProblem(rule, element);
}
}
}
eventTarget.dispatchEvent(
new CustomEvent("toggle-notification", {
detail: {
state: audits.length > 0
}
})
);
}
async function createAuditProblem(rule, originalElement) {
const computedStyle = window.getComputedStyle(originalElement);
const targetedElement = originalElement.children[0] || originalElement;
if (targetedElement.offsetParent === null || computedStyle.display === "none") {
return;
}
if (originalElement.nodeName === "IMG" && !originalElement.complete) {
return;
}
audits.push({
auditedElement: originalElement,
rule,
card: null,
highlight: null
});
}
function refreshLintPositions() {
audits.forEach(({ highlight, auditedElement }) => {
const rect = auditedElement.getBoundingClientRect();
if (highlight) positionHighlight(highlight, rect);
});
}
["scroll", "resize"].forEach((event) => {
window.addEventListener(event, refreshLintPositions);
});
function setupObserver() {
observer.observe(document.body, {
childList: true,
subtree: true
});
}
}
};
export {
audit_default as default
};