/** * HTML page analyzer using htmlparser2's streaming tokenizer. * * Extracts SEO-relevant data from a page's HTML: title, meta description, * headings, images, links, canonical, OG tags, structured data, robots meta, * word count, hreflang. * * Deliberately NOT a DOM parser: the previous cheerio implementation built a * full DOM (~5-10x the HTML's size) per page, and with 25 concurrent parses * on a 128MB isolate that was the audit engine's dominant OOM cause. The * tokenizer keeps only the accumulated text and extracted fields in memory. */ import { Parser } from "htmlparser2"; import { normalizeUrl, isSameOrigin } from "./url-utils"; import type { PageAnalysis, PageLink } from "./types"; const SKIPPED_LINK_PROTOCOLS = /^(javascript:|mailto:|tel:|#)/; /** Subtrees whose text is not visible content. */ const NON_CONTENT_TAGS = new Set(["script", "style", "noscript", "svg"]); const HEADING_LEVELS: Record = { h1: 1, h2: 2, h3: 3, h4: 4, h5: 5, h6: 6, }; const MAX_ANCHOR_CHARS = 200; interface OpenAnchor { href: string; rel: string; text: string[]; } /** * Analyze an HTML string and extract all SEO-relevant data. */ export function analyzeHtml( html: string, pageUrl: string, statusCode: number, responseTimeMs: number, redirectUrl: string | null = null, ): PageAnalysis { let title: string | null = null; let titleDepth = 0; let titleDone = false; // parse5 (the old DOM path) treats