@bejamas/bench 0.0.0-stage → 0.1.0
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/README.md +126 -2
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +125 -0
- package/dist/cli.js.map +1 -0
- package/dist/index.d.ts +289 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +3 -0
- package/dist/summary-DD9dfSgQ.js +1510 -0
- package/dist/summary-DD9dfSgQ.js.map +1 -0
- package/package.json +58 -4
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"summary-DD9dfSgQ.js","names":["measureSide","pkg.name","pkg.version"],"sources":["../package.json","../src/artifacts.ts","../src/errors.ts","../src/browser.ts","../src/format.ts","../src/stats.ts","../src/metrics.ts","../src/budgets.ts","../src/text.ts","../src/checks.ts","../src/types.ts","../src/sections.ts","../src/report.ts","../src/sides.ts","../src/stages/assets.ts","../src/stages/lighthouse.ts","../src/stages/problems.ts","../src/stages/quality.ts","../src/stages/visual.ts","../src/targets.ts","../src/run.ts","../src/summary.ts"],"sourcesContent":["","import { mkdirSync, writeFileSync } from \"node:fs\";\nimport { dirname, join, resolve } from \"node:path\";\n\n/** Writes report files under one directory and returns each file's path. */\nexport interface Artifacts {\n dir: string;\n write: (path: string, data: string | Uint8Array) => string;\n}\n\n/**\n * Report files are opt-in. Without a directory there is no writer, so callers\n * use `artifacts?.write(path, encode())`, which also skips the encoding.\n */\nexport function createArtifacts(outDir: string | undefined) {\n if (!outDir) return undefined;\n const dir = resolve(outDir);\n // Fail on an unwritable directory before minutes of measuring.\n mkdirSync(dir, { recursive: true });\n return {\n dir,\n write(path, data) {\n const file = join(dir, path);\n mkdirSync(dirname(file), { recursive: true });\n writeFileSync(file, data);\n return file;\n },\n } satisfies Artifacts;\n}\n","/** An expected failure with a message that is safe to print without a stack. */\nexport class BenchError extends Error {\n override name = \"BenchError\";\n}\n","import { Launcher } from \"chrome-launcher\";\nimport type { Browser, Page } from \"playwright-core\";\nimport { BenchError } from \"./errors\";\n\nexport function resolveChromePath(explicit?: string) {\n const chromePath =\n explicit ?? process.env.CHROME_PATH ?? Launcher.getFirstInstallation();\n if (!chromePath) {\n throw new BenchError(\n \"Google Chrome was not found. Install Chrome, pass --chrome-path or set CHROME_PATH.\",\n );\n }\n return chromePath;\n}\n\nexport async function launchBrowser(chromePath: string): Promise<Browser> {\n const { chromium } = await import(\"playwright-core\");\n return chromium.launch({ executablePath: chromePath, headless: true });\n}\n\n/**\n * Load a page until the network is idle, then scroll through it so lazy images\n * and scroll-triggered content render before anything is measured.\n */\nexport async function loadPage(page: Page, url: string, timeout: number) {\n await page.goto(url, { waitUntil: \"networkidle\", timeout });\n await page.evaluate(async () => {\n const step = Math.max(200, Math.floor(window.innerHeight * 0.8));\n const pause = () => new Promise((resolve) => setTimeout(resolve, 50));\n // Cap the distance so infinite-scroll pages still finish.\n const limit = 100_000;\n for (\n let y = 0;\n y < Math.min(document.documentElement.scrollHeight, limit);\n y += step\n ) {\n window.scrollTo(0, y);\n await pause();\n }\n window.scrollTo(0, 0);\n await document.fonts.ready;\n });\n await page.waitForLoadState(\"networkidle\", { timeout }).catch(() => {});\n}\n","export function formatBytes(bytes: number | null) {\n if (bytes === null) return \"–\";\n if (Math.abs(bytes) < 1024) return `${bytes} B`;\n return `${(bytes / 1024).toFixed(2)} KiB`;\n}\n\nexport function formatMs(value: number | null) {\n if (value === null) return \"–\";\n return value >= 1000\n ? `${(value / 1000).toFixed(2)} s`\n : `${value.toFixed(0)} ms`;\n}\n\nexport function formatDelta(\n original: number | null,\n ported: number | null,\n format: (value: number) => string,\n) {\n if (original === null || ported === null) return \"–\";\n const delta = ported - original;\n if (delta === 0) return \"±0\";\n const sign = delta > 0 ? \"+\" : \"−\";\n const relative =\n original === 0\n ? \"\"\n : ` (${sign}${Math.abs((delta / original) * 100).toFixed(1)}%)`;\n return `${sign}${format(Math.abs(delta))}${relative}`;\n}\n\nexport function truncate(value: string, length: number) {\n return value.length > length ? `${value.slice(0, length - 1)}…` : value;\n}\n","export function round(value: number, digits = 2) {\n const factor = 10 ** digits;\n return Math.round(value * factor) / factor;\n}\n\n/** Median of the defined values, or null when there are none. */\nexport function median(values: readonly (number | null)[]) {\n const sorted = values\n .filter((value): value is number => typeof value === \"number\")\n .sort((a, b) => a - b);\n if (sorted.length === 0) return null;\n const middle = Math.floor(sorted.length / 2);\n return sorted.length % 2 === 0\n ? (sorted[middle - 1]! + sorted[middle]!) / 2\n : sorted[middle]!;\n}\n","import { formatBytes, formatMs } from \"./format\";\nimport { round } from \"./stats\";\nimport type { LighthouseMetric } from \"./types\";\n\nexport type Unit = \"ms\" | \"bytes\" | \"count\" | \"score\" | \"cls\" | \"percent\";\n\nconst FORMATTERS: Record<Unit, (value: number) => string> = {\n ms: formatMs,\n bytes: formatBytes,\n count: String,\n score: (value) => String(round(value, 1)),\n cls: (value) => String(round(value, 3)),\n percent: (value) => `${round(value, 3)}%`,\n};\n\nexport function formatValue(unit: Unit, value: number) {\n return FORMATTERS[unit](value);\n}\n\n/** Suffixes a budget value may use, by unit. */\nexport const UNIT_FACTORS: Partial<Record<Unit, Record<string, number>>> = {\n ms: { ms: 1, s: 1000 },\n bytes: { b: 1, kb: 1024, kib: 1024, mb: 1024 ** 2, mib: 1024 ** 2 },\n};\n\nexport interface LighthouseMetricSpec {\n label: string;\n unit: Unit;\n /** Decimal places kept in the report. */\n digits: number;\n higherIsBetter: boolean;\n}\n\nexport const LIGHTHOUSE: Record<LighthouseMetric, LighthouseMetricSpec> = {\n score: {\n label: \"Performance score\",\n unit: \"score\",\n digits: 2,\n higherIsBetter: true,\n },\n fcp: {\n label: \"First Contentful Paint\",\n unit: \"ms\",\n digits: 2,\n higherIsBetter: false,\n },\n lcp: {\n label: \"Largest Contentful Paint\",\n unit: \"ms\",\n digits: 2,\n higherIsBetter: false,\n },\n tbt: {\n label: \"Total Blocking Time\",\n unit: \"ms\",\n digits: 2,\n higherIsBetter: false,\n },\n cls: {\n label: \"Cumulative Layout Shift\",\n unit: \"cls\",\n digits: 4,\n higherIsBetter: false,\n },\n si: { label: \"Speed Index\", unit: \"ms\", digits: 2, higherIsBetter: false },\n};\n","import { BenchError } from \"./errors\";\nimport { LIGHTHOUSE, UNIT_FACTORS, formatValue, type Unit } from \"./metrics\";\nimport { round } from \"./stats\";\nimport type {\n AssetsResult,\n BudgetResult,\n Comparability,\n LighthouseMetric,\n LighthouseResult,\n Paired,\n SideAssets,\n VisualResult,\n} from \"./types\";\n\nexport type BudgetMetric =\n | LighthouseMetric\n | \"js\"\n | \"css\"\n | \"html\"\n | \"fonts\"\n | \"total\"\n | \"requests\"\n | \"pixels\";\n\nexport interface Budget {\n source: string;\n metric: BudgetMetric;\n op: \">\" | \"<\";\n value: number;\n /** Compare the relative change in percent instead of the absolute change. */\n relative: boolean;\n}\n\nexport interface BudgetInputs {\n assets?: AssetsResult;\n lighthouse?: LighthouseResult;\n visual?: VisualResult;\n comparability: Comparability;\n}\n\n/** Why a budget could not be evaluated. */\ntype SkipReason = string;\n\n/**\n * Most metrics are budgeted by their change from original to ported. Pixel\n * mismatch is already a comparison, so its budget applies to the value itself.\n */\ntype MetricSpec =\n | {\n kind: \"change\";\n unit: Unit;\n read: (inputs: BudgetInputs) => Paired<number> | SkipReason;\n }\n | {\n kind: \"absolute\";\n unit: Unit;\n read: (\n inputs: BudgetInputs,\n ) => { value: number; detail: string } | SkipReason;\n };\n\nconst lighthouseMetric = (metric: LighthouseMetric): MetricSpec => ({\n kind: \"change\",\n unit: LIGHTHOUSE[metric].unit,\n read: ({ lighthouse, comparability }) => {\n if (!lighthouse) return \"the Lighthouse stage did not run\";\n if (!comparability.comparable)\n return \"timings are not comparable between these URLs\";\n const original = lighthouse.original.median[metric];\n const ported = lighthouse.ported.median[metric];\n if (original === null || ported === null)\n return `Lighthouse did not report ${metric}`;\n return { original, ported };\n },\n});\n\nconst assetMetric = (\n unit: Unit,\n read: (side: SideAssets) => number,\n): MetricSpec => ({\n kind: \"change\",\n unit,\n read: ({ assets }) =>\n assets\n ? { original: read(assets.original), ported: read(assets.ported) }\n : \"the assets stage did not run\",\n});\n\nconst METRICS: Record<BudgetMetric, MetricSpec> = {\n score: lighthouseMetric(\"score\"),\n fcp: lighthouseMetric(\"fcp\"),\n lcp: lighthouseMetric(\"lcp\"),\n tbt: lighthouseMetric(\"tbt\"),\n cls: lighthouseMetric(\"cls\"),\n si: lighthouseMetric(\"si\"),\n js: assetMetric(\"bytes\", (side) => side.categories.js.size),\n css: assetMetric(\"bytes\", (side) => side.categories.css.size),\n html: assetMetric(\"bytes\", (side) => side.categories.html.size),\n fonts: assetMetric(\"bytes\", (side) => side.categories.fonts.size),\n total: assetMetric(\"bytes\", (side) => side.coreSize),\n requests: assetMetric(\"count\", (side) => side.requests),\n pixels: {\n kind: \"absolute\",\n unit: \"percent\",\n read: ({ visual }) => {\n const widths = visual?.widths ?? [];\n if (widths.length === 0) return \"the visual stage did not run\";\n const worst = widths.reduce((a, b) =>\n b.pixels.mismatch > a.pixels.mismatch ? b : a,\n );\n return {\n value: worst.pixels.mismatch,\n detail: `${formatValue(\"percent\", worst.pixels.mismatch)} of pixels differ at ${worst.width}px`,\n };\n },\n },\n};\n\nconst isBudgetMetric = (value: string): value is BudgetMetric =>\n Object.hasOwn(METRICS, value);\n\nexport const BUDGET_HELP = `Budgets compare the ported URL with the original. Size and timing budgets\nuse the change (ported − original), absolute or relative with %:\n lcp>10% fail when LCP grows by more than 10%\n js>0 fail when route JavaScript (gzip) grows at all\n total>20kb fail when HTML + CSS + JS + fonts grow by more than 20 KiB\n score<-5 fail when the Lighthouse score drops by more than 5 points\n pixels>1% fail when screenshots differ in more than 1% of pixels (absolute)\nMetrics: ${Object.keys(METRICS).join(\", \")}.`;\n\nexport function parseBudgets(inputs: readonly string[]): Budget[] {\n return inputs\n .flatMap((input) => input.split(\",\"))\n .map((part) => part.trim())\n .filter(Boolean)\n .map(parseBudget);\n}\n\nfunction parseBudget(source: string): Budget {\n const match = source.match(\n /^([a-z]+)\\s*([<>])\\s*(-?\\d+(?:\\.\\d+)?)\\s*(%|[a-z]+)?$/i,\n );\n if (!match)\n throw new BenchError(\n `Invalid budget \"${source}\". Expected <metric><op><value>, e.g. lcp>10%.`,\n );\n const metric = match[1]!.toLowerCase();\n if (!isBudgetMetric(metric)) {\n throw new BenchError(\n `Unknown budget metric \"${metric}\" in \"${source}\". ${Object.keys(METRICS).join(\", \")} are supported.`,\n );\n }\n const spec = METRICS[metric];\n const unit = match[4]?.toLowerCase();\n let value = Number(match[3]);\n if (unit && unit !== \"%\") {\n const factors = UNIT_FACTORS[spec.unit] ?? {};\n if (!Object.hasOwn(factors, unit))\n throw new BenchError(\n `Unit \"${unit}\" does not apply to ${metric} in \"${source}\".`,\n );\n value *= factors[unit]!;\n }\n return {\n source,\n metric,\n op: match[2] as \">\" | \"<\",\n value,\n relative: unit === \"%\" && spec.kind === \"change\",\n };\n}\n\nconst sign = (value: number) => (value > 0 ? \"+\" : value < 0 ? \"−\" : \"±\");\n\nfunction evaluateBudget(budget: Budget, inputs: BudgetInputs): BudgetResult {\n const spec = METRICS[budget.metric];\n const result = (actual: number, detail: string): BudgetResult => ({\n budget: budget.source,\n status: (budget.op === \">\" ? actual > budget.value : actual < budget.value)\n ? \"fail\"\n : \"pass\",\n detail,\n });\n const skip = (reason: SkipReason): BudgetResult => ({\n budget: budget.source,\n status: \"skip\",\n detail: reason,\n });\n\n if (spec.kind === \"absolute\") {\n const reading = spec.read(inputs);\n if (typeof reading === \"string\") return skip(reading);\n return result(reading.value, reading.detail);\n }\n\n const pair = spec.read(inputs);\n if (typeof pair === \"string\") return skip(pair);\n const format = (value: number) => formatValue(spec.unit, value);\n const delta = pair.ported - pair.original;\n const values = `${budget.metric}: ${format(pair.original)} → ${format(pair.ported)}`;\n if (!budget.relative) {\n return result(\n delta,\n `${values} (${sign(delta)}${format(Math.abs(delta))})`,\n );\n }\n const relative =\n pair.original === 0\n ? delta === 0\n ? 0\n : Math.sign(delta) * Infinity\n : (delta / pair.original) * 100;\n return result(\n relative,\n `${values} (${sign(relative)}${round(Math.abs(relative), 1)}%)`,\n );\n}\n\nexport function evaluateBudgets(\n budgets: readonly Budget[],\n inputs: BudgetInputs,\n): BudgetResult[] {\n return budgets.map((budget) => evaluateBudget(budget, inputs));\n}\n","import type { TextDifference } from \"./types\";\n\n/**\n * Locate the first region where the visible text of the two pages differs,\n * with `context` words around it and at most `maxWords` words per excerpt.\n */\nexport function compareText(\n original: string,\n ported: string,\n { context = 12, maxWords = 80 }: { context?: number; maxWords?: number } = {},\n): TextDifference {\n const a = original.split(\" \").filter(Boolean);\n const b = ported.split(\" \").filter(Boolean);\n const result: TextDifference = {\n identical: original === ported,\n originalWords: a.length,\n portedWords: b.length,\n };\n if (result.identical) return result;\n\n let start = 0;\n while (start < a.length && start < b.length && a[start] === b[start]) start++;\n let endA = a.length;\n let endB = b.length;\n while (endA > start && endB > start && a[endA - 1] === b[endB - 1]) {\n endA--;\n endB--;\n }\n const excerpt = (words: string[], end: number) => {\n const from = Math.max(0, start - context);\n const to = Math.min(\n words.length,\n Math.max(end, start) + context,\n from + maxWords,\n );\n return `${from > 0 ? \"… \" : \"\"}${words.slice(from, to).join(\" \")}${to < words.length ? \" …\" : \"\"}`;\n };\n return { ...result, original: excerpt(a, endA), ported: excerpt(b, endB) };\n}\n","import { compareText } from \"./text\";\nimport type {\n Check,\n CheckStatus,\n Comparability,\n FailedRequest,\n QualityResult,\n SideQuality,\n VisualResult,\n} from \"./types\";\n\nconst PIXEL_WARNING = 1;\n\ntype Outcome = [status: CheckStatus, detail: string];\n\nconst check = (\n id: string,\n label: string,\n [status, detail]: Outcome,\n): Check => ({\n id,\n label,\n status,\n detail,\n});\n\nfunction list(values: readonly string[], limit = 3) {\n const shown = values.slice(0, limit).join(\"; \");\n return values.length > limit\n ? `${shown}; and ${values.length - limit} more`\n : shown;\n}\n\n/** Strip the origin so the same path on both servers compares equal. */\nfunction withoutOrigin(value: string, finalUrl: string) {\n const origin = new URL(finalUrl).origin;\n return value.split(origin).join(\"\");\n}\n\n/** Entries the port has and the original doesn't, compared without origins. */\nfunction newEntries<T>(\n original: SideQuality,\n ported: SideQuality,\n read: (side: SideQuality) => readonly T[],\n key: (value: T) => string,\n) {\n const keyOf = (side: SideQuality) => (value: T) =>\n withoutOrigin(key(value), side.finalUrl);\n const seen = new Set(read(original).map(keyOf(original)));\n return read(ported).filter((value) => !seen.has(keyOf(ported)(value)));\n}\n\nconst describeRequest = ({ url, reason }: FailedRequest) =>\n `${url} (${reason})`;\n\n/** One line per host and reason, so a broken CDN reads as one problem. */\nfunction groupFailures(requests: readonly FailedRequest[]) {\n const groups = new Map<string, FailedRequest[]>();\n for (const request of requests) {\n const key = `${new URL(request.url).host} ${request.reason}`;\n groups.set(key, [...(groups.get(key) ?? []), request]);\n }\n return [...groups.values()].map((group) =>\n group.length === 1\n ? describeRequest(group[0]!)\n : `${group.length} requests to ${new URL(group[0]!.url).host} (${group[0]!.reason})`,\n );\n}\n\n/** Every check for the stages that produced results, in report order. */\nexport function reportChecks({\n comparability,\n quality,\n visual,\n}: {\n comparability: Comparability;\n quality?: QualityResult;\n visual?: VisualResult;\n}): Check[] {\n return [\n comparabilityCheck(comparability),\n ...(quality ? qualityChecks(quality) : []),\n ...(visual ? visualChecks(visual) : []),\n ];\n}\n\nexport function comparabilityCheck(comparability: Comparability): Check {\n return check(\n \"comparability\",\n \"Timing comparability\",\n comparability.comparable\n ? [\"pass\", \"Both URLs run in the same kind of environment.\"]\n : [\"warn\", comparability.reasons.join(\" \")],\n );\n}\n\nexport function qualityChecks({\n original,\n ported,\n text,\n}: QualityResult): Check[] {\n const metadata = ([\"title\", \"description\", \"lang\"] as const).filter(\n (key) => original[key] !== ported[key],\n );\n\n const outline = (side: SideQuality) =>\n side.headings.map((heading) => `h${heading.level} ${heading.text}`);\n const originalOutline = outline(original);\n const portedOutline = outline(ported);\n const mismatch = originalOutline.findIndex(\n (heading, index) => heading !== portedOutline[index],\n );\n const headingsMatch =\n originalOutline.length === portedOutline.length && mismatch === -1;\n const firstDifference = mismatch === -1 ? originalOutline.length : mismatch;\n\n const originalViolations = new Map(\n original.violations.map((violation) => [violation.id, violation.nodes]),\n );\n const regressions = ported.violations.filter(\n (violation) =>\n violation.nodes > (originalViolations.get(violation.id) ?? 0),\n );\n\n const newProblems = [\n ...newEntries(\n original,\n ported,\n (side) => side.errors,\n (error) => error,\n ),\n ...groupFailures(\n newEntries(\n original,\n ported,\n (side) => side.failedRequests,\n describeRequest,\n ),\n ),\n ];\n // The report keeps the long excerpt; the check names just the change.\n const difference = compareText(original.text, ported.text, {\n context: 4,\n maxWords: 20,\n });\n const portedProblems = ported.errors.length + ported.failedRequests.length;\n\n return [\n check(\n \"text\",\n \"Visible text\",\n text.identical\n ? [\"pass\", `Identical (${text.portedWords} words).`]\n : [\n \"fail\",\n `Differs: ${text.originalWords} → ${text.portedWords} words. First difference: “${difference.original}” vs “${difference.ported}”.`,\n ],\n ),\n check(\n \"metadata\",\n \"Title, description and lang\",\n metadata.length === 0\n ? [\"pass\", \"Identical.\"]\n : [\n \"fail\",\n list(\n metadata.map(\n (key) =>\n `${key}: ${JSON.stringify(original[key])} → ${JSON.stringify(ported[key])}`,\n ),\n ),\n ],\n ),\n check(\n \"headings\",\n \"Heading outline\",\n headingsMatch\n ? [\"pass\", `Identical (${originalOutline.length} headings).`]\n : [\n \"fail\",\n `${originalOutline.length} → ${portedOutline.length} headings. First difference: “${originalOutline[firstDifference] ?? \"(none)\"}” vs “${portedOutline[firstDifference] ?? \"(none)\"}”.`,\n ],\n ),\n check(\n \"accessibility\",\n \"Accessibility (axe-core)\",\n regressions.length > 0\n ? [\n \"fail\",\n `New or more frequent violations: ${list(regressions.map((violation) => `${violation.id} (${violation.nodes})`))}.`,\n ]\n : ported.violations.length > 0\n ? [\n \"warn\",\n `No regressions, but ${ported.violations.length} violations remain from the original: ${list(ported.violations.map((violation) => violation.id))}.`,\n ]\n : [\"pass\", \"No violations.\"],\n ),\n check(\n \"errors\",\n \"Browser errors and failed requests\",\n newProblems.length > 0\n ? [\"fail\", list(newProblems)]\n : portedProblems > 0\n ? [\"warn\", \"Only errors that the original also has.\"]\n : [\"pass\", \"None.\"],\n ),\n check(\"nested-controls\", \"Nested interactive controls\", [\n ported.nestedInteractiveControls > original.nestedInteractiveControls\n ? \"fail\"\n : \"pass\",\n `${original.nestedInteractiveControls} → ${ported.nestedInteractiveControls}.`,\n ]),\n ];\n}\n\nexport function visualChecks(visual: VisualResult): Check[] {\n return visual.widths.flatMap((result) => {\n const { pixels } = result;\n const height =\n pixels.heightDelta === 0\n ? \"matches\"\n : `changes by ${pixels.heightDelta > 0 ? \"+\" : \"\"}${pixels.heightDelta}px`;\n return [\n check(\n `geometry-${result.width}`,\n `Layout geometry at ${result.width}px`,\n result.differences.length > 0 || result.landmarkDifferences.length > 0\n ? [\n \"fail\",\n `${result.matchedElements > 0 ? `${result.differences.length} of ${result.matchedElements} matched components and ` : \"\"}${result.landmarkDifferences.length} page regions differ by more than ${visual.tolerance}px.`,\n ]\n : result.matchedElements === 0\n ? [\n \"warn\",\n `No data-slot components matched (${result.originalElements} named in the original, ${result.portedElements} in the port); only page regions were compared.`,\n ]\n : [\n \"pass\",\n `${result.matchedElements} matched components and all page regions are within ${visual.tolerance}px.`,\n ],\n ),\n check(\n `pixels-${result.width}`,\n `Screenshot pixels at ${result.width}px`,\n [\n pixels.mismatch > PIXEL_WARNING || pixels.heightDelta !== 0\n ? \"warn\"\n : \"pass\",\n `${pixels.mismatch}% of pixels differ; page height ${height}.`,\n ],\n ),\n ];\n });\n}\n","export type Side = \"original\" | \"ported\";\nexport const SIDES: readonly Side[] = [\"original\", \"ported\"];\n\n/** One value per side. */\nexport type Paired<T> = Record<Side, T>;\n\nexport type StageName = \"assets\" | \"quality\" | \"visual\" | \"lighthouse\";\nexport const STAGES: readonly StageName[] = [\n \"assets\",\n \"quality\",\n \"visual\",\n \"lighthouse\",\n];\n\nexport type FormFactor = \"mobile\" | \"desktop\";\n\nexport interface BenchOptions {\n original: string;\n ported: string;\n /** Write report files here. Nothing is written when omitted. */\n outDir?: string;\n runs: number;\n widths: number[];\n formFactor: FormFactor;\n stages: StageName[];\n tolerance: number;\n budgets: string[];\n allowDev: boolean;\n chromePath?: string;\n timeout: number;\n}\n\nexport interface Target {\n side: Side;\n input: string;\n url: string;\n finalUrl: string;\n status: number;\n local: boolean;\n devServer: string | null;\n}\n\nexport interface Comparability {\n comparable: boolean;\n reasons: string[];\n}\n\nexport type AssetCategory =\n \"html\" | \"css\" | \"js\" | \"fonts\" | \"images\" | \"other\";\nexport const ASSET_CATEGORIES: readonly AssetCategory[] = [\n \"html\",\n \"css\",\n \"js\",\n \"fonts\",\n \"images\",\n \"other\",\n];\n\nexport interface AssetEntry {\n url: string;\n category: AssetCategory;\n thirdParty: boolean;\n /** Encoded body bytes received over the network. */\n transfer: number;\n /** Decoded body bytes. */\n raw: number;\n /** Deterministic gzip level 9 estimate, compressible categories only. */\n gzip: number | null;\n}\n\nexport interface AssetSummary {\n count: number;\n transfer: number;\n raw: number;\n /** Comparable size: the gzip estimate for HTML, CSS and JS, transferred bytes otherwise. */\n size: number;\n}\n\nexport interface SideAssets {\n finalUrl: string;\n viewport: { width: number; height: number };\n categories: Record<AssetCategory, AssetSummary>;\n /** Comparable size of HTML + CSS + JS + fonts. */\n coreSize: number;\n requests: number;\n transfer: number;\n entries: AssetEntry[];\n}\n\nexport type AssetsResult = Paired<SideAssets>;\n\nexport interface AxeViolation {\n id: string;\n impact: string | null;\n help: string;\n nodes: number;\n}\n\nexport interface Heading {\n level: number;\n text: string;\n}\n\nexport interface FailedRequest {\n url: string;\n /** An HTTP status, \"blocked by CORS\" or Chrome's network error. */\n reason: string;\n}\n\nexport interface SideQuality {\n finalUrl: string;\n title: string;\n description: string | null;\n lang: string | null;\n text: string;\n headings: Heading[];\n domElements: number;\n domBytes: number;\n nestedInteractiveControls: number;\n violations: AxeViolation[];\n errors: string[];\n failedRequests: FailedRequest[];\n}\n\nexport interface TextDifference {\n identical: boolean;\n originalWords: number;\n portedWords: number;\n /** Context around the first differing region, when the text differs. */\n original?: string;\n ported?: string;\n}\n\nexport interface QualityResult extends Paired<SideQuality> {\n viewport: { width: number; height: number };\n text: TextDifference;\n}\n\nexport interface Box {\n x: number;\n y: number;\n width: number;\n height: number;\n}\n\nexport interface SlotElement {\n slot: string;\n text: string;\n width: number;\n height: number;\n}\n\nexport interface Landmark extends Box {\n tag: string;\n}\n\nexport interface Capture {\n side: Side;\n width: number;\n /** Screenshot file, when report files are written. */\n screenshot: string | null;\n pageHeight: number;\n elements: SlotElement[];\n landmarks: Landmark[];\n}\n\nexport interface SlotDifference {\n slot: string;\n text: string;\n original: [number, number];\n ported: [number, number];\n}\n\nexport interface LandmarkDifference {\n index: number;\n tag: string;\n original: Box | null;\n ported: Box | null;\n}\n\nexport interface GeometryComparison {\n /** Named `data-slot` elements on each side; generic part names are left out. */\n originalElements: number;\n portedElements: number;\n matchedElements: number;\n differences: SlotDifference[];\n unmatchedOriginal: Record<string, number>;\n unmatchedPorted: Record<string, number>;\n landmarkDifferences: LandmarkDifference[];\n}\n\nexport interface PixelComparison {\n /** Diff image file, when report files are written. */\n diff: string | null;\n comparedWidth: number;\n comparedHeight: number;\n heightDelta: number;\n mismatchedPixels: number;\n /** Percentage of compared pixels that differ. */\n mismatch: number;\n}\n\nexport interface VisualWidthResult extends GeometryComparison {\n width: number;\n pixels: PixelComparison;\n}\n\nexport interface VisualResult {\n tolerance: number;\n captures: Capture[];\n widths: VisualWidthResult[];\n}\n\nexport type LighthouseMetric = \"score\" | \"fcp\" | \"lcp\" | \"tbt\" | \"cls\" | \"si\";\nexport const LIGHTHOUSE_METRICS: readonly LighthouseMetric[] = [\n \"score\",\n \"fcp\",\n \"lcp\",\n \"tbt\",\n \"cls\",\n \"si\",\n];\n\nexport type LighthouseValues = Record<LighthouseMetric, number | null>;\n\nexport interface LighthouseRun extends LighthouseValues {\n run: number;\n /** Full Lighthouse report file, when report files are written. */\n report: string | null;\n}\n\nexport interface LighthouseSide {\n runs: LighthouseRun[];\n median: LighthouseValues;\n}\n\nexport interface LighthouseResult extends Paired<LighthouseSide> {\n formFactor: FormFactor;\n version: string;\n runs: number;\n order: string;\n}\n\nexport type CheckStatus = \"pass\" | \"fail\" | \"warn\" | \"skip\";\n\nexport interface Check {\n id: string;\n label: string;\n status: CheckStatus;\n detail: string;\n}\n\nexport interface BudgetResult {\n budget: string;\n status: \"pass\" | \"fail\" | \"skip\";\n detail: string;\n}\n\nexport interface SkippedStage {\n stage: StageName;\n reason: string;\n}\n\nexport interface StageError {\n stage: StageName;\n message: string;\n}\n\nexport interface BenchReport {\n schemaVersion: 1;\n tool: { name: string; version: string };\n measuredAt: string;\n environment: {\n node: string;\n platform: string;\n arch: string;\n browser: string | null;\n };\n options: Omit<BenchOptions, \"original\" | \"ported\">;\n targets: Paired<Target>;\n comparability: Comparability;\n skipped: SkippedStage[];\n errors: StageError[];\n assets?: AssetsResult;\n quality?: QualityResult;\n visual?: VisualResult;\n lighthouse?: LighthouseResult;\n checks: Check[];\n budgets: BudgetResult[];\n passed: boolean;\n}\n","import { formatBytes, formatDelta, truncate } from \"./format\";\nimport { LIGHTHOUSE, formatValue } from \"./metrics\";\nimport {\n LIGHTHOUSE_METRICS,\n type AssetCategory,\n type AssetsResult,\n type BenchReport,\n type CheckStatus,\n type Comparability,\n type LighthouseResult,\n type QualityResult,\n type VisualResult,\n type VisualWidthResult,\n} from \"./types\";\n\n// What the Markdown report and the terminal summary show. Each renderer only\n// decides how to lay it out.\n\n/** Whether a change is an improvement or a regression for the port. */\nexport type Tone = \"better\" | \"worse\";\nexport type Cell = string | { text: string; tone: Tone };\n\nexport interface Table {\n headings: string[];\n align: (\"l\" | \"r\")[];\n rows: Cell[][];\n}\n\nexport interface Section {\n title: string;\n /** Short qualifiers, such as the viewport or the number of runs. */\n notes: string[];\n table: Table;\n}\n\nexport interface StatusItem {\n status: CheckStatus;\n label: string;\n detail: string;\n}\n\nexport const cellText = (cell: Cell) =>\n typeof cell === \"string\" ? cell : cell.text;\n\nexport const textTable = (headings: string[], rows: Cell[][]): Table => ({\n headings,\n align: headings.map(() => \"l\"),\n rows,\n});\n\n/** A label column followed by right-aligned values. */\nconst valueTable = (headings: string[], rows: Cell[][]): Table => ({\n headings,\n align: headings.map((_, index) => (index === 0 ? \"l\" : \"r\")),\n rows,\n});\n\nconst COMPARISON = [\"Original\", \"Ported\", \"Change\"];\n\nfunction comparisonRow(\n label: string,\n original: number | null,\n ported: number | null,\n format: (value: number) => string,\n higherIsBetter = false,\n): Cell[] {\n const delta = original === null || ported === null ? 0 : ported - original;\n const change = formatDelta(original, ported, format);\n return [\n label,\n original === null ? \"–\" : format(original),\n ported === null ? \"–\" : format(ported),\n delta === 0\n ? change\n : {\n text: change,\n tone: delta > 0 === higherIsBetter ? \"better\" : \"worse\",\n },\n ];\n}\n\nexport function budgetItems(report: BenchReport): StatusItem[] {\n return report.budgets.map(({ budget, status, detail }) => ({\n status,\n label: budget,\n detail,\n }));\n}\n\n/** Stages that failed or were skipped. */\nexport function stageItems(report: BenchReport): StatusItem[] {\n return [\n ...report.errors.map(({ stage, message }) => ({\n status: \"fail\" as const,\n label: stage,\n detail: message,\n })),\n ...report.skipped.map(({ stage, reason }) => ({\n status: \"skip\" as const,\n label: stage,\n detail: reason,\n })),\n ];\n}\n\nexport function lighthouseSection(\n lighthouse: LighthouseResult,\n comparability: Comparability,\n): Section {\n return {\n title: `Lighthouse ${lighthouse.formFactor}`,\n notes: [\n `median of ${lighthouse.runs} run${lighthouse.runs === 1 ? \"\" : \"s\"}`,\n ...(comparability.comparable ? [] : [\"timings not comparable\"]),\n ],\n table: valueTable(\n [\"Metric\", ...COMPARISON],\n LIGHTHOUSE_METRICS.map((metric) => {\n const { label, unit, higherIsBetter } = LIGHTHOUSE[metric];\n return comparisonRow(\n label,\n lighthouse.original.median[metric],\n lighthouse.ported.median[metric],\n (value) => formatValue(unit, value),\n higherIsBetter,\n );\n }),\n ),\n };\n}\n\nconst ASSET_ROWS: [AssetCategory, string][] = [\n [\"js\", \"JavaScript (gzip)\"],\n [\"css\", \"CSS (gzip)\"],\n [\"html\", \"HTML (gzip)\"],\n [\"fonts\", \"Fonts\"],\n [\"images\", \"Images\"],\n [\"other\", \"Other\"],\n];\n\nexport function assetsSection({ original, ported }: AssetsResult): Section {\n const { viewport } = original;\n return {\n title: \"Route assets\",\n notes: [`cold load at ${viewport.width}×${viewport.height}`],\n table: valueTable(\n [\"Asset\", ...COMPARISON],\n [\n ...ASSET_ROWS.map(([category, label]) => {\n const a = original.categories[category];\n const b = ported.categories[category];\n return comparisonRow(\n `${label}, ${a.count} → ${b.count} files`,\n a.size,\n b.size,\n formatBytes,\n );\n }),\n comparisonRow(\n \"HTML + CSS + JS + fonts\",\n original.coreSize,\n ported.coreSize,\n formatBytes,\n ),\n comparisonRow(\n \"All requests (transferred)\",\n original.transfer,\n ported.transfer,\n formatBytes,\n ),\n comparisonRow(\"Requests\", original.requests, ported.requests, String),\n ],\n ),\n };\n}\n\nexport function visualSection(visual: VisualResult): Section {\n return {\n title: \"Visual parity\",\n notes: [`within ${visual.tolerance}px`],\n table: valueTable(\n [\n \"Width\",\n \"Matched components\",\n \"Component diffs\",\n \"Region diffs\",\n \"Pixels differing\",\n \"Height change\",\n ],\n visual.widths.map((result) => [\n `${result.width}px`,\n `${result.matchedElements} of ${result.originalElements}`,\n String(result.differences.length),\n String(result.landmarkDifferences.length),\n `${result.pixels.mismatch}%`,\n `${result.pixels.heightDelta > 0 ? \"+\" : \"\"}${result.pixels.heightDelta}px`,\n ]),\n ),\n };\n}\n\nexport function qualitySection(quality: QualityResult): Section {\n const { original, ported, viewport } = quality;\n const row = (label: string, read: (side: typeof original) => string) => [\n label,\n read(original),\n read(ported),\n ];\n return {\n title: \"Page quality\",\n notes: [`${viewport.width}×${viewport.height}`],\n table: valueTable(\n [\"Measure\", \"Original\", \"Ported\"],\n [\n row(\"DOM elements\", (side) => String(side.domElements)),\n row(\"Serialized DOM\", (side) => formatBytes(side.domBytes)),\n row(\"axe-core violations\", (side) => String(side.violations.length)),\n row(\"Browser errors\", (side) => String(side.errors.length)),\n row(\"Failed requests\", (side) => String(side.failedRequests.length)),\n ],\n ),\n };\n}\n\n/** Matched components whose size differs, the first `limit` of them. */\nexport function componentDifferences(\n result: VisualWidthResult,\n { limit, textLength }: { limit: number; textLength: number },\n): Table {\n return textTable(\n [\"Component\", \"Text\", \"Original\", \"Ported\"],\n result.differences\n .slice(0, limit)\n .map((difference) => [\n difference.slot,\n truncate(difference.text, textLength),\n difference.original.join(\" × \"),\n difference.ported.join(\" × \"),\n ]),\n );\n}\n\nconst slotCounts = (counts: Record<string, number>) =>\n Object.entries(counts)\n .sort((a, b) => b[1] - a[1])\n .map(([slot, count]) => `${slot} (${count})`)\n .join(\", \") || \"none\";\n\n/** Slots that only one side has, which explain low match counts. */\nexport function unmatchedSlots(result: VisualWidthResult): string[] {\n const { unmatchedOriginal, unmatchedPorted } = result;\n if (\n Object.keys(unmatchedOriginal).length +\n Object.keys(unmatchedPorted).length ===\n 0\n )\n return [];\n return [\n `Only in original: ${slotCounts(unmatchedOriginal)}`,\n `Only in ported: ${slotCounts(unmatchedPorted)}`,\n ];\n}\n","import { relative } from \"node:path\";\nimport { truncate } from \"./format\";\nimport {\n assetsSection,\n budgetItems,\n cellText,\n componentDifferences,\n lighthouseSection,\n qualitySection,\n stageItems,\n textTable,\n unmatchedSlots,\n visualSection,\n type StatusItem,\n type Table,\n} from \"./sections\";\nimport type { BenchReport, Box, CheckStatus } from \"./types\";\n\nconst STATUS_LABEL: Record<CheckStatus, string> = {\n pass: \"✓ pass\",\n fail: \"✗ fail\",\n warn: \"! warn\",\n skip: \"– skip\",\n};\n\nconst MAX_DIFFERENCES = 20;\n\nfunction escapeCell(value: string) {\n return value.replace(/\\|/g, \"\\\\|\").replace(/\\n/g, \" \");\n}\n\nfunction markdownTable({ headings, align, rows }: Table) {\n const cells = [headings, ...rows].map((row) =>\n row.map((cell) => escapeCell(cellText(cell))),\n );\n const widths = headings.map((_, index) =>\n Math.max(3, ...cells.map((row) => row[index]!.length)),\n );\n const pad = (value: string, index: number) =>\n align[index] === \"r\"\n ? value.padStart(widths[index]!)\n : value.padEnd(widths[index]!);\n const line = (row: string[]) => `| ${row.map(pad).join(\" | \")} |`;\n const divider = `| ${widths.map((width, index) => (align[index] === \"r\" ? `${\"-\".repeat(width - 1)}:` : \"-\".repeat(width))).join(\" | \")} |`;\n return [line(cells[0]!), divider, ...cells.slice(1).map(line)].join(\"\\n\");\n}\n\nfunction statusTable(heading: string, items: readonly StatusItem[]) {\n return markdownTable(\n textTable(\n [heading, \"Status\", \"Detail\"],\n items.map((item) => [item.label, STATUS_LABEL[item.status], item.detail]),\n ),\n );\n}\n\nconst box = (value: Box | null) =>\n value\n ? `${value.width} × ${value.height} at ${value.x}, ${value.y}`\n : \"missing\";\n\nexport function renderMarkdown(report: BenchReport, outDir: string) {\n const link = (label: string, file: string | null) =>\n file\n ? `[${label}](${relative(outDir, file).split(\"\\\\\").join(\"/\")})`\n : label;\n const lines: string[] = [];\n // Blank lines keep headings, paragraphs and tables separate blocks.\n const push = (...values: string[]) =>\n lines.push(...values.flatMap((value) => [value, \"\"]));\n\n push(\n \"# UI bench report\",\n [\n `- **Original:** ${report.targets.original.finalUrl}`,\n `- **Ported:** ${report.targets.ported.finalUrl}`,\n `- **Measured:** ${report.measuredAt} with ${report.environment.browser ?? \"Chrome\"}, Node ${report.environment.node}`,\n ].join(\"\\n\"),\n );\n\n push(\n `## Result: ${report.passed ? \"PASS\" : \"FAIL\"}`,\n statusTable(\"Check\", report.checks),\n );\n const budgets = budgetItems(report);\n if (budgets.length > 0) push(\"### Budgets\", statusTable(\"Budget\", budgets));\n const stages = stageItems(report);\n if (stages.length > 0) push(\"### Stages\", statusTable(\"Stage\", stages));\n\n const { lighthouse, assets, visual, quality, comparability } = report;\n if (lighthouse) {\n const section = lighthouseSection(lighthouse, comparability);\n push(\n `## ${section.title}`,\n `Median of ${lighthouse.runs} Lighthouse ${lighthouse.version} runs per URL with simulated throttling, run in ${lighthouse.order} order. These are lab results, not field data.${comparability.comparable ? \"\" : \" **Timings are not comparable:** \" + comparability.reasons.join(\" \")}`,\n markdownTable(section.table),\n );\n }\n\n if (assets) {\n const section = assetsSection(assets);\n const { viewport } = assets.original;\n push(\n `## ${section.title}`,\n `Responses of a cold load at ${viewport.width}×${viewport.height}, until the network is idle. HTML, CSS and JavaScript are recompressed with gzip level 9 so servers with different compression compare equally; other assets use their transferred size.`,\n markdownTable(section.table),\n );\n }\n\n if (visual) {\n const section = visualSection(visual);\n push(\n `## ${section.title}`,\n `Visible \\`data-slot\\` components are matched by slot name, text and order, and their sizes compared within ${visual.tolerance}px. Top-level header, main, section and footer regions are compared by position and size. Screenshot pixels are compared over the area both pages cover.`,\n markdownTable(section.table),\n );\n for (const result of visual.widths) {\n const screenshot = (side: \"original\" | \"ported\") =>\n visual.captures.find(\n (item) => item.side === side && item.width === result.width,\n )?.screenshot ?? null;\n push(\n `### ${result.width}px`,\n `Screenshots: ${link(\"original\", screenshot(\"original\"))}, ${link(\"ported\", screenshot(\"ported\"))}, ${link(\"diff\", result.pixels.diff)}`,\n );\n if (result.differences.length > 0) {\n const more = result.differences.length - MAX_DIFFERENCES;\n push(\n markdownTable(\n componentDifferences(result, {\n limit: MAX_DIFFERENCES,\n textLength: 40,\n }),\n ) + (more > 0 ? `\\n\\n…and ${more} more in report.json.` : \"\"),\n );\n }\n if (result.landmarkDifferences.length > 0) {\n push(\n markdownTable(\n textTable(\n [\"Region\", \"Original\", \"Ported\"],\n result.landmarkDifferences\n .slice(0, MAX_DIFFERENCES)\n .map((difference) => [\n `${difference.index + 1}. ${difference.tag}`,\n box(difference.original),\n box(difference.ported),\n ]),\n ),\n ),\n );\n }\n const unmatched = unmatchedSlots(result);\n if (unmatched.length > 0)\n push(unmatched.map((line) => `- ${line}`).join(\"\\n\"));\n }\n }\n\n if (quality) {\n const section = qualitySection(quality);\n push(\n `## ${section.title}`,\n `Measured at ${section.notes.join(\", \")}.`,\n markdownTable(section.table),\n );\n const { text } = quality;\n if (!text.identical) {\n push(\n \"### First text difference\",\n `- **Original:** ${text.original}\\n- **Ported:** ${text.ported}`,\n );\n }\n for (const label of [\"original\", \"ported\"] as const) {\n const side = quality[label];\n if (side.violations.length > 0) {\n push(\n `### axe-core violations (${label})`,\n side.violations\n .map(\n (violation) =>\n `- \\`${violation.id}\\` (${violation.impact ?? \"unknown\"}, ${violation.nodes} nodes): ${violation.help}`,\n )\n .join(\"\\n\"),\n );\n }\n const problems = [\n ...side.errors,\n ...side.failedRequests.map(({ url, reason }) => `${url} (${reason})`),\n ];\n if (problems.length > 0) {\n push(\n `### Browser errors (${label})`,\n problems.map((problem) => `- ${truncate(problem, 300)}`).join(\"\\n\"),\n );\n }\n }\n }\n\n return `${lines.join(\"\\n\").trim()}\\n`;\n}\n","import type { Paired, Side } from \"./types\";\n\n/**\n * Measure the original, then the ported URL. The sides run one after the\n * other so they never compete for CPU or network while being measured.\n */\nexport async function bothSides<T>(\n measure: (side: Side) => Promise<T>,\n): Promise<Paired<T>> {\n const original = await measure(\"original\");\n const ported = await measure(\"ported\");\n return { original, ported };\n}\n","import { gzipSync } from \"node:zlib\";\nimport type { Browser, Request } from \"playwright-core\";\nimport { bothSides } from \"../sides\";\nimport {\n ASSET_CATEGORIES,\n type AssetCategory,\n type AssetEntry,\n type AssetSummary,\n type AssetsResult,\n type Paired,\n type SideAssets,\n} from \"../types\";\n\nexport const ASSETS_VIEWPORT = { width: 412, height: 823 };\n\nconst COMPRESSIBLE = new Set<AssetCategory>([\"html\", \"css\", \"js\"]);\n\nexport function categorize(\n request: Pick<Request, \"resourceType\" | \"isNavigationRequest\">,\n isMainFrame: boolean,\n): AssetCategory {\n switch (request.resourceType()) {\n case \"document\":\n return isMainFrame && request.isNavigationRequest() ? \"html\" : \"other\";\n case \"stylesheet\":\n return \"css\";\n case \"script\":\n return \"js\";\n case \"font\":\n return \"fonts\";\n case \"image\":\n case \"media\":\n return \"images\";\n default:\n return \"other\";\n }\n}\n\nconst sum = (\n entries: readonly AssetEntry[],\n read: (entry: AssetEntry) => number,\n) => entries.reduce((total, entry) => total + read(entry), 0);\n\nexport function summarizeAssets(entries: readonly AssetEntry[]) {\n const categories = Object.fromEntries(\n ASSET_CATEGORIES.map((category) => {\n const files = entries.filter((entry) => entry.category === category);\n const summary: AssetSummary = {\n count: files.length,\n transfer: sum(files, (entry) => entry.transfer),\n raw: sum(files, (entry) => entry.raw),\n size: COMPRESSIBLE.has(category)\n ? sum(files, (entry) => entry.gzip ?? 0)\n : sum(files, (entry) => entry.transfer),\n };\n return [category, summary];\n }),\n ) as Record<AssetCategory, AssetSummary>;\n return {\n categories,\n coreSize: ([\"html\", \"css\", \"js\", \"fonts\"] as const).reduce(\n (total, category) => total + categories[category].size,\n 0,\n ),\n requests: entries.length,\n transfer: sum(entries, (entry) => entry.transfer),\n };\n}\n\n/**\n * Record every response of a cold page load. Compression is recomputed from\n * the decoded bodies so local and remote servers are measured the same way.\n */\nexport function measureAssets(\n browser: Browser,\n urls: Paired<string>,\n timeout: number,\n): Promise<AssetsResult> {\n return bothSides((side) => measureSide(browser, urls[side], timeout));\n}\n\nasync function measureSide(\n browser: Browser,\n url: string,\n timeout: number,\n): Promise<SideAssets> {\n const context = await browser.newContext({\n viewport: ASSETS_VIEWPORT,\n serviceWorkers: \"block\",\n });\n try {\n const page = await context.newPage();\n const pending: Promise<Omit<AssetEntry, \"thirdParty\"> | null>[] = [];\n page.on(\"requestfinished\", (request) => {\n pending.push(\n (async () => {\n const response = await request.response();\n if (\n !response ||\n response.status() >= 300 ||\n response.status() === 204\n )\n return null;\n const category = categorize(\n request,\n request.frame() === page.mainFrame(),\n );\n const [sizes, body] = await Promise.all([\n request.sizes(),\n response.body().catch(() => null),\n ]);\n return {\n url: request.url(),\n category,\n transfer: sizes.responseBodySize,\n raw: body?.byteLength ?? 0,\n gzip:\n body && COMPRESSIBLE.has(category)\n ? gzipSync(body, { level: 9 }).byteLength\n : null,\n };\n })().catch(() => null),\n );\n });\n await page.goto(url, { waitUntil: \"networkidle\", timeout });\n const finalUrl = page.url();\n const origin = new URL(finalUrl).origin;\n const entries = (await Promise.all(pending))\n .filter((entry) => entry !== null)\n .filter((entry) => !entry.url.startsWith(\"data:\"))\n .map((entry) => ({\n ...entry,\n thirdParty: new URL(entry.url).origin !== origin,\n }))\n .sort(\n (a, b) =>\n a.category.localeCompare(b.category) || a.url.localeCompare(b.url),\n );\n return {\n finalUrl,\n viewport: ASSETS_VIEWPORT,\n ...summarizeAssets(entries),\n entries,\n };\n } finally {\n await context.close();\n }\n}\n","import { createRequire } from \"node:module\";\nimport type { Artifacts } from \"../artifacts\";\nimport { LIGHTHOUSE } from \"../metrics\";\nimport { median, round } from \"../stats\";\nimport {\n LIGHTHOUSE_METRICS,\n SIDES,\n type FormFactor,\n type LighthouseMetric,\n type LighthouseResult,\n type LighthouseRun,\n type LighthouseSide,\n type Paired,\n type Side,\n} from \"../types\";\n\n/** The score comes from the performance category, the rest from audits. */\nconst AUDITS: Record<Exclude<LighthouseMetric, \"score\">, string> = {\n fcp: \"first-contentful-paint\",\n lcp: \"largest-contentful-paint\",\n tbt: \"total-blocking-time\",\n cls: \"cumulative-layout-shift\",\n si: \"speed-index\",\n};\n\n/** Run order for one round. Alternating cancels out drift such as thermal throttling. */\nexport function roundOrder(run: number): Side[] {\n return run % 2 === 1 ? [...SIDES] : [...SIDES].reverse();\n}\n\nconst roundMetric = (\n metric: LighthouseMetric,\n value: number | null | undefined,\n) =>\n typeof value === \"number\" ? round(value, LIGHTHOUSE[metric].digits) : null;\n\nexport async function runLighthouse({\n urls,\n runs,\n formFactor,\n chromePath,\n artifacts,\n onProgress,\n}: {\n urls: Paired<string>;\n runs: number;\n formFactor: FormFactor;\n chromePath: string;\n /** Saves every full Lighthouse report, when report files are written. */\n artifacts: Artifacts | undefined;\n onProgress: (message: string) => void;\n}): Promise<LighthouseResult> {\n const { default: lighthouse, desktopConfig } = await import(\"lighthouse\");\n const { launch } = await import(\"chrome-launcher\");\n const version: string = createRequire(import.meta.url)(\n \"lighthouse/package.json\",\n ).version;\n\n const chrome = await launch({\n chromePath,\n chromeFlags: [\"--headless=new\", \"--disable-dev-shm-usage\"],\n });\n const results: Paired<LighthouseRun[]> = { original: [], ported: [] };\n try {\n for (let run = 1; run <= runs; run++) {\n for (const side of roundOrder(run)) {\n onProgress(`Lighthouse ${formFactor} run ${run}/${runs}: ${side}`);\n const result = await lighthouse(\n urls[side],\n {\n port: chrome.port,\n output: \"json\",\n logLevel: \"error\",\n onlyCategories: [\"performance\"],\n throttlingMethod: \"simulate\",\n maxWaitForLoad: 45_000,\n },\n formFactor === \"desktop\" ? desktopConfig : undefined,\n );\n if (!result)\n throw new Error(`Lighthouse returned no result for the ${side} URL`);\n const { lhr } = result;\n if (lhr.runtimeError) {\n throw new Error(\n `Lighthouse failed for the ${side} URL: ${lhr.runtimeError.message}`,\n );\n }\n const score = lhr.categories.performance?.score;\n const audit = (metric: keyof typeof AUDITS) =>\n roundMetric(metric, lhr.audits[AUDITS[metric]]?.numericValue);\n results[side].push({\n run,\n report:\n artifacts?.write(\n `lighthouse/${side}-run-${run}.json`,\n typeof result.report === \"string\"\n ? result.report\n : JSON.stringify(lhr),\n ) ?? null,\n score: roundMetric(\n \"score\",\n typeof score === \"number\" ? score * 100 : null,\n ),\n fcp: audit(\"fcp\"),\n lcp: audit(\"lcp\"),\n tbt: audit(\"tbt\"),\n cls: audit(\"cls\"),\n si: audit(\"si\"),\n });\n }\n }\n } finally {\n chrome.kill();\n }\n\n const summarize = (side: Side): LighthouseSide => ({\n runs: results[side],\n median: Object.fromEntries(\n LIGHTHOUSE_METRICS.map((metric) => [\n metric,\n roundMetric(metric, median(results[side].map((run) => run[metric]))),\n ]),\n ) as LighthouseSide[\"median\"],\n });\n\n return {\n formFactor,\n version,\n runs,\n order: \"alternating (original, ported), then (ported, original)\",\n original: summarize(\"original\"),\n ported: summarize(\"ported\"),\n };\n}\n","import type { FailedRequest } from \"../types\";\n\nconst CORS_BLOCK =\n /^Access to \\w+ at '([^']+)' from origin '[^']+' has been blocked by CORS policy/;\n\n/**\n * Collects browser errors and broken resources so each problem is reported\n * once: one entry per distinct error message and one per failed URL, with the\n * most specific reason any event gave for it.\n */\nexport function createProblemLog() {\n const errors = new Set<string>();\n const failures = new Map<string, string>();\n return {\n consoleError(text: string) {\n // Chrome repeats every failed load as a console error without its URL;\n // the request events already record those with the URL.\n if (text.startsWith(\"Failed to load resource\")) return;\n const cors = CORS_BLOCK.exec(text);\n if (cors) failures.set(cors[1]!, \"blocked by CORS\");\n else errors.add(text);\n },\n pageError(message: string) {\n errors.add(message);\n },\n response(url: string, status: number) {\n if (status >= 400) failures.set(url, `HTTP ${status}`);\n },\n requestFailed(url: string, reason: string) {\n // Cancelled loads (prefetches, media preloads, closing the page) are not\n // failures, and an HTTP status or CORS block already says more.\n if (reason === \"net::ERR_ABORTED\" || failures.has(url)) return;\n failures.set(url, reason);\n },\n result(): { errors: string[]; failedRequests: FailedRequest[] } {\n return {\n errors: [...errors],\n failedRequests: [...failures].map(([url, reason]) => ({ url, reason })),\n };\n },\n };\n}\n","import { createRequire } from \"node:module\";\nimport { readFileSync } from \"node:fs\";\nimport type { Browser } from \"playwright-core\";\nimport { loadPage } from \"../browser\";\nimport { bothSides } from \"../sides\";\nimport { compareText } from \"../text\";\nimport { createProblemLog } from \"./problems\";\nimport type { Paired, QualityResult, SideQuality } from \"../types\";\n\nconst QUALITY_VIEWPORT = { width: 1280, height: 800 };\n\nconst require = createRequire(import.meta.url);\nlet axeSource: string | undefined;\nfunction readAxeSource() {\n axeSource ??= readFileSync(require.resolve(\"axe-core/axe.min.js\"), \"utf8\");\n return axeSource;\n}\n\nexport async function measureQuality(\n browser: Browser,\n urls: Paired<string>,\n timeout: number,\n): Promise<QualityResult> {\n const sides = await bothSides((side) =>\n measureSide(browser, urls[side], timeout),\n );\n return {\n viewport: QUALITY_VIEWPORT,\n ...sides,\n text: compareText(sides.original.text, sides.ported.text),\n };\n}\n\nasync function measureSide(\n browser: Browser,\n url: string,\n timeout: number,\n): Promise<SideQuality> {\n // CSP would otherwise block injecting axe-core into live sites.\n const context = await browser.newContext({\n viewport: QUALITY_VIEWPORT,\n locale: \"en-US\",\n bypassCSP: true,\n serviceWorkers: \"block\",\n });\n try {\n const page = await context.newPage();\n const problems = createProblemLog();\n page.on(\"console\", (message) => {\n if (message.type() === \"error\") problems.consoleError(message.text());\n });\n page.on(\"pageerror\", (error) => problems.pageError(error.message));\n page.on(\"requestfailed\", (request) =>\n problems.requestFailed(\n request.url(),\n request.failure()?.errorText ?? \"failed\",\n ),\n );\n page.on(\"response\", (response) =>\n problems.response(response.url(), response.status()),\n );\n\n await loadPage(page, url, timeout);\n\n const data = await page.evaluate(() => {\n const normalize = (value: string) => value.replace(/\\s+/g, \" \").trim();\n return {\n title: document.title,\n description:\n document\n .querySelector('meta[name=\"description\"]')\n ?.getAttribute(\"content\") ?? null,\n lang: document.documentElement.getAttribute(\"lang\"),\n text: normalize(document.body.innerText),\n headings: [...document.querySelectorAll(\"h1, h2, h3, h4, h5, h6\")]\n .filter((element) => element.checkVisibility())\n .map((element) => ({\n level: Number(element.tagName[1]),\n text: normalize(element.textContent ?? \"\"),\n })),\n domElements: document.querySelectorAll(\"*\").length,\n domBytes: new TextEncoder().encode(document.documentElement.outerHTML)\n .byteLength,\n nestedInteractiveControls: document.querySelectorAll(\n \"button a, a button, button button, a a, button input, a input, button select, a select\",\n ).length,\n };\n });\n\n await page.addScriptTag({ content: readAxeSource() });\n const violations = await page.evaluate(async () => {\n const axe = (globalThis as unknown as { axe: typeof import(\"axe-core\") })\n .axe;\n const result = await axe.run(document);\n return result.violations.map((violation) => ({\n id: violation.id,\n impact: violation.impact ?? null,\n help: violation.help,\n nodes: violation.nodes.length,\n }));\n });\n\n return {\n finalUrl: page.url(),\n ...data,\n violations: violations.sort((a, b) => a.id.localeCompare(b.id)),\n ...problems.result(),\n };\n } finally {\n await context.close();\n }\n}\n","import pixelmatch from \"pixelmatch\";\nimport { PNG } from \"pngjs\";\nimport type { Browser } from \"playwright-core\";\nimport type { Artifacts } from \"../artifacts\";\nimport { loadPage } from \"../browser\";\nimport { bothSides } from \"../sides\";\nimport type {\n Box,\n Capture,\n GeometryComparison,\n LandmarkDifference,\n Paired,\n PixelComparison,\n Side,\n SlotDifference,\n SlotElement,\n VisualResult,\n VisualWidthResult,\n} from \"../types\";\n\nconst VISUAL_HEIGHT = 823;\n\nexport async function measureVisual(\n browser: Browser,\n {\n urls,\n widths,\n tolerance,\n timeout,\n artifacts,\n onProgress,\n }: {\n urls: Paired<string>;\n widths: readonly number[];\n tolerance: number;\n timeout: number;\n artifacts: Artifacts | undefined;\n onProgress: (message: string) => void;\n },\n): Promise<VisualResult> {\n const captures: Capture[] = [];\n const results: VisualWidthResult[] = [];\n for (const width of widths) {\n onProgress(`Capturing layout at ${width}px`);\n const { original, ported } = await bothSides((side) =>\n capture(browser, { side, url: urls[side], width, timeout, artifacts }),\n );\n const { comparison, diff } = comparePixels(original.image, ported.image);\n captures.push(original.capture, ported.capture);\n results.push({\n width,\n ...compareGeometry(original.capture, ported.capture, tolerance),\n pixels: {\n ...comparison,\n diff:\n artifacts?.write(\n `screenshots/diff-${width}.png`,\n PNG.sync.write(diff),\n ) ?? null,\n },\n });\n }\n return { tolerance, captures, widths: results };\n}\n\nasync function capture(\n browser: Browser,\n {\n side,\n url,\n width,\n timeout,\n artifacts,\n }: {\n side: Side;\n url: string;\n width: number;\n timeout: number;\n artifacts: Artifacts | undefined;\n },\n): Promise<{ capture: Capture; image: Buffer }> {\n const context = await browser.newContext({\n viewport: { width, height: VISUAL_HEIGHT },\n colorScheme: \"light\",\n reducedMotion: \"reduce\",\n serviceWorkers: \"block\",\n });\n try {\n const page = await context.newPage();\n await loadPage(page, url, timeout);\n const image = await page.screenshot({\n fullPage: true,\n animations: \"disabled\",\n });\n const data = await page.evaluate(() => {\n const round = (value: number) => Math.round(value * 100) / 100;\n const box = (element: Element) => {\n const rect = element.getBoundingClientRect();\n return {\n x: round(rect.x + window.scrollX),\n y: round(rect.y + window.scrollY),\n width: round(rect.width),\n height: round(rect.height),\n };\n };\n const elements = [...document.querySelectorAll(\"[data-slot]\")]\n .filter((element) => element.checkVisibility())\n .map((element) => {\n const { width, height } = box(element);\n return {\n slot: element.getAttribute(\"data-slot\") ?? \"\",\n text: (element.textContent ?? \"\").replace(/\\s+/g, \" \").trim(),\n width,\n height,\n };\n });\n // Page-level regions that are not nested in another region.\n const regions = \"header, footer, main, section, article, aside, nav\";\n const landmarks = [\n ...document.querySelectorAll(\"header, footer, main, section\"),\n ]\n .filter((element) => element.checkVisibility())\n .filter((element) => {\n const parent = element.parentElement?.closest(regions);\n return !parent || parent.tagName === \"MAIN\";\n })\n .map((element) => ({\n tag: element.tagName.toLowerCase(),\n ...box(element),\n }));\n return {\n elements,\n landmarks,\n pageHeight: document.documentElement.scrollHeight,\n };\n });\n const screenshot =\n artifacts?.write(`screenshots/${side}-${width}.png`, image) ?? null;\n return { capture: { side, width, screenshot, ...data }, image };\n } finally {\n await context.close();\n }\n}\n\n/**\n * Part names that don't identify a component (PrimeReact's root, outer and\n * inner wrappers). Matching on them pairs unrelated elements, so they are left\n * out of the comparison and the unmatched lists.\n */\nconst GENERIC_SLOTS = new Set([\"root\", \"outer\", \"inner\"]);\n\nconst named = (elements: readonly SlotElement[]) =>\n elements.filter((element) => !GENERIC_SLOTS.has(element.slot));\n\nconst slotKey = (element: SlotElement) =>\n `${element.slot}:${element.text.replace(/\\s/g, \"\")}`;\n\nfunction countBySlot(elements: readonly SlotElement[]) {\n const counts: Record<string, number> = {};\n for (const element of elements)\n counts[element.slot] = (counts[element.slot] ?? 0) + 1;\n return counts;\n}\n\n/**\n * Match named `data-slot` elements by slot name, normalized text and\n * occurrence order, then compare their sizes. Framework wrappers without a\n * matching element on the other side are reported but not treated as\n * differences.\n */\nexport function compareGeometry(\n original: Capture,\n ported: Capture,\n tolerance: number,\n): GeometryComparison {\n const originalElements = named(original.elements);\n const portedElements = named(ported.elements);\n const buckets = new Map<string, SlotElement[]>();\n for (const element of portedElements) {\n const key = slotKey(element);\n if (!buckets.has(key)) buckets.set(key, []);\n buckets.get(key)!.push(element);\n }\n const differences: SlotDifference[] = [];\n const unmatchedOriginal: SlotElement[] = [];\n let matchedElements = 0;\n for (const element of originalElements) {\n const other = buckets.get(slotKey(element))?.shift();\n if (!other) {\n unmatchedOriginal.push(element);\n continue;\n }\n matchedElements++;\n if (\n Math.abs(element.width - other.width) > tolerance ||\n Math.abs(element.height - other.height) > tolerance\n ) {\n differences.push({\n slot: element.slot,\n text: element.text,\n original: [element.width, element.height],\n ported: [other.width, other.height],\n });\n }\n }\n\n const landmarkDifferences: LandmarkDifference[] = [];\n const count = Math.max(original.landmarks.length, ported.landmarks.length);\n for (let index = 0; index < count; index++) {\n const a = original.landmarks[index];\n const b = ported.landmarks[index];\n const differs =\n !a ||\n !b ||\n a.tag !== b.tag ||\n ([\"x\", \"y\", \"width\", \"height\"] as const).some(\n (key) => Math.abs(a[key] - b[key]) > tolerance,\n );\n if (differs) {\n const strip = (landmark: typeof a): Box | null =>\n landmark\n ? {\n x: landmark.x,\n y: landmark.y,\n width: landmark.width,\n height: landmark.height,\n }\n : null;\n landmarkDifferences.push({\n index,\n tag: (a ?? b)!.tag,\n original: strip(a),\n ported: strip(b),\n });\n }\n }\n\n return {\n originalElements: originalElements.length,\n portedElements: portedElements.length,\n matchedElements,\n differences,\n unmatchedOriginal: countBySlot(unmatchedOriginal),\n unmatchedPorted: countBySlot([...buckets.values()].flat()),\n landmarkDifferences,\n };\n}\n\nfunction crop(png: PNG, width: number, height: number) {\n if (png.width === width && png.height === height) return png.data;\n const data = Buffer.alloc(width * height * 4);\n for (let y = 0; y < height; y++) {\n png.data.copy(\n data,\n y * width * 4,\n y * png.width * 4,\n (y * png.width + width) * 4,\n );\n }\n return data;\n}\n\n/**\n * Pixel-diff the overlapping area of two full-page screenshots. The diff image\n * is returned undecoded so it is only encoded when it gets written.\n */\nexport function comparePixels(\n original: Buffer,\n ported: Buffer,\n): { comparison: Omit<PixelComparison, \"diff\">; diff: PNG } {\n const a = PNG.sync.read(original);\n const b = PNG.sync.read(ported);\n const width = Math.min(a.width, b.width);\n const height = Math.min(a.height, b.height);\n const diff = new PNG({ width, height });\n const mismatchedPixels = pixelmatch(\n crop(a, width, height),\n crop(b, width, height),\n diff.data,\n width,\n height,\n {\n threshold: 0.1,\n },\n );\n const total = width * height;\n return {\n comparison: {\n comparedWidth: width,\n comparedHeight: height,\n heightDelta: b.height - a.height,\n mismatchedPixels,\n mismatch:\n total === 0\n ? 0\n : Math.round((mismatchedPixels / total) * 100_000) / 1000,\n },\n diff,\n };\n}\n","import type { Browser, Response } from \"playwright-core\";\nimport { BenchError } from \"./errors\";\nimport type { Comparability, Side, Target } from \"./types\";\n\nconst PRIVATE_IPV4 = [\n /^127\\./,\n /^10\\./,\n /^192\\.168\\./,\n /^172\\.(?:1[6-9]|2\\d|3[01])\\./,\n /^0\\.0\\.0\\.0$/,\n];\n\nexport function isLocalHost(hostname: string) {\n const host = hostname.replace(/^\\[|\\]$/g, \"\").toLowerCase();\n return (\n host === \"localhost\" ||\n host.endsWith(\".localhost\") ||\n host.endsWith(\".local\") ||\n host === \"::1\" ||\n PRIVATE_IPV4.some((pattern) => pattern.test(host))\n );\n}\n\n/** Accepts `localhost:4321` or `example.com` as well as full http(s) URLs. */\nexport function normalizeUrl(input: string) {\n const value = input.trim();\n if (!value) throw new BenchError(\"A URL is required.\");\n const hasScheme = /^[a-z][a-z\\d+.-]*:\\/\\//i.test(value);\n let url: URL;\n try {\n url = new URL(hasScheme ? value : `http://${value}`);\n } catch {\n throw new BenchError(`Invalid URL: ${input}`);\n }\n if (url.protocol !== \"http:\" && url.protocol !== \"https:\") {\n throw new BenchError(`Only http and https URLs are supported: ${input}`);\n }\n if (!hasScheme && !isLocalHost(url.hostname)) {\n url.protocol = \"https:\";\n }\n return url;\n}\n\n// Development servers ship unminified code, HMR clients and on-demand\n// transforms, so their performance numbers say nothing about production.\nconst DEV_SERVER_MARKERS: [string, RegExp][] = [\n [\"Astro\", /astro\\/dist\\/runtime\\/client\\/dev-toolbar|astro-dev-toolbar/],\n [\"Vite\", /\\/@vite\\/client/],\n [\n \"Next.js\",\n // Production Turbopack builds also ship a `turbopack-*.js` runtime chunk, so\n // match only the dev-only HMR client, devtools and webpack runtime.\n /\\/_next\\/static\\/[^\"']*(?:hmr-client|next-devtools|_browser_dev_|react-refresh)|\\/_next\\/static\\/chunks\\/webpack\\.js\\?v=|\\/_next\\/webpack-hmr/,\n ],\n [\"webpack\", /webpack-dev-server|__webpack_hmr/],\n [\"React Refresh\", /@react-refresh|react-refresh-runtime/],\n];\n\nexport function detectDevServer(html: string) {\n return (\n DEV_SERVER_MARKERS.find(([, pattern]) => pattern.test(html))?.[0] ?? null\n );\n}\n\n/** Bot-protection pages would be measured instead of the site. */\nconst CHALLENGE_HEADERS = [\"x-vercel-mitigated\", \"cf-mitigated\"];\n\n/**\n * Load the URL in the browser that takes the measurements. Bot protection on\n * hosts such as Vercel and Cloudflare challenges plain HTTP clients but lets\n * Chrome through, so a fetch-based check would reject reachable sites.\n */\nexport async function inspectTarget(\n browser: Browser,\n {\n side,\n input,\n url,\n timeout,\n }: { side: Side; input: string; url: URL; timeout: number },\n): Promise<Target> {\n const context = await browser.newContext({ serviceWorkers: \"block\" });\n try {\n const page = await context.newPage();\n let response: Response | null;\n try {\n response = await page.goto(url.href, {\n waitUntil: \"domcontentloaded\",\n timeout,\n });\n } catch (error) {\n const reason = (error instanceof Error ? error.message : String(error))\n .split(\"\\n\")[0]!\n .replace(/^page\\.goto: /, \"\");\n throw new BenchError(\n `Could not reach the ${side} URL ${url.href}: ${reason}`,\n );\n }\n if (!response) {\n throw new BenchError(`The ${side} URL ${url.href} returned no response.`);\n }\n const headers = response.headers();\n if (CHALLENGE_HEADERS.some((name) => headers[name] === \"challenge\")) {\n throw new BenchError(\n `The ${side} URL ${url.href} is behind a bot challenge, which would be measured instead of the page. Allow the benchmark through or measure a local build.`,\n );\n }\n if (!response.ok()) {\n throw new BenchError(\n `The ${side} URL ${url.href} responded with HTTP ${response.status()}.`,\n );\n }\n const contentType = headers[\"content-type\"] ?? \"\";\n const html = await response.text().catch(() => \"\");\n if (!/html/i.test(contentType) && !/<html[\\s>]/i.test(html)) {\n throw new BenchError(\n `The ${side} URL ${url.href} did not return an HTML page (${contentType || \"no content type\"}).`,\n );\n }\n const finalUrl = new URL(page.url());\n return {\n side,\n input,\n url: url.href,\n finalUrl: finalUrl.href,\n status: response.status(),\n local: isLocalHost(finalUrl.hostname),\n devServer: detectDevServer(html),\n };\n } finally {\n await context.close();\n }\n}\n\n/** Whether timing results from the two targets can be compared fairly. */\nexport function assessComparability(\n original: Target,\n ported: Target,\n): Comparability {\n const reasons: string[] = [];\n if (original.local !== ported.local) {\n reasons.push(\n `The ${original.local ? \"original\" : \"ported\"} URL is local and the ${original.local ? \"ported\" : \"original\"} URL is remote, so network, CDN and protocol differences are mixed into timings.`,\n );\n }\n for (const target of [original, ported]) {\n if (target.devServer) {\n reasons.push(\n `The ${target.side} URL is served by a development server (${target.devServer}); use a production build.`,\n );\n }\n }\n return { comparable: reasons.length === 0, reasons };\n}\n","import pkg from \"../package.json\" with { type: \"json\" };\nimport { createArtifacts } from \"./artifacts\";\nimport { launchBrowser, resolveChromePath } from \"./browser\";\nimport { evaluateBudgets, parseBudgets } from \"./budgets\";\nimport { reportChecks } from \"./checks\";\nimport { renderMarkdown } from \"./report\";\nimport { measureAssets } from \"./stages/assets\";\nimport { runLighthouse } from \"./stages/lighthouse\";\nimport { measureQuality } from \"./stages/quality\";\nimport { measureVisual } from \"./stages/visual\";\nimport { assessComparability, inspectTarget, normalizeUrl } from \"./targets\";\nimport type {\n BenchOptions,\n BenchReport,\n Paired,\n Side,\n SkippedStage,\n StageError,\n StageName,\n Target,\n} from \"./types\";\n\nexport interface RunResult {\n report: BenchReport;\n /** Report files, when `outDir` is set. */\n files?: { json: string; markdown: string };\n}\n\n/** Stages that measure performance, which development servers distort. */\nconst PRODUCTION_ONLY: readonly StageName[] = [\"assets\", \"lighthouse\"];\n\nexport async function runBench(\n options: BenchOptions,\n log: (message: string) => void = () => {},\n): Promise<RunResult> {\n // Reject bad input before spending minutes on measurements.\n const budgets = parseBudgets(options.budgets);\n const inputs: Paired<URL> = {\n original: normalizeUrl(options.original),\n ported: normalizeUrl(options.ported),\n };\n const chromePath = resolveChromePath(options.chromePath);\n const artifacts = createArtifacts(options.outDir);\n const measuredAt = new Date().toISOString();\n\n const browser = await launchBrowser(chromePath);\n const browserVersion = browser.version();\n const inspect = (side: Side) =>\n inspectTarget(browser, {\n side,\n input: options[side],\n url: inputs[side],\n timeout: options.timeout,\n });\n let targets: Paired<Target>;\n try {\n log(\"Checking both URLs\");\n // Settle both so the error is always the original's first when both fail.\n const [original, ported] = await Promise.allSettled([\n inspect(\"original\"),\n inspect(\"ported\"),\n ]);\n if (original.status === \"rejected\") throw original.reason;\n if (ported.status === \"rejected\") throw ported.reason;\n targets = { original: original.value, ported: ported.value };\n } catch (error) {\n await browser.close();\n throw error;\n }\n const urls: Paired<string> = {\n original: targets.original.finalUrl,\n ported: targets.ported.finalUrl,\n };\n const comparability = assessComparability(targets.original, targets.ported);\n\n const devServer = targets.original.devServer ?? targets.ported.devServer;\n const blocked = devServer && !options.allowDev ? PRODUCTION_ONLY : [];\n const stages = options.stages.filter((stage) => !blocked.includes(stage));\n const skipped: SkippedStage[] = options.stages\n .filter((stage) => blocked.includes(stage))\n .map((stage) => ({\n stage,\n reason: `A development server (${devServer}) was detected. Measure production builds, or pass --allow-dev to measure anyway.`,\n }));\n\n const errors: StageError[] = [];\n async function stage<T>(name: StageName, run: () => Promise<T>) {\n if (!stages.includes(name)) return undefined;\n try {\n return await run();\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n errors.push({ stage: name, message });\n log(`${name} failed: ${message}`);\n return undefined;\n }\n }\n\n const { timeout } = options;\n let measured: Pick<BenchReport, \"assets\" | \"quality\" | \"visual\">;\n try {\n measured = {\n assets: await stage(\"assets\", () => {\n log(\"Measuring route assets\");\n return measureAssets(browser, urls, timeout);\n }),\n quality: await stage(\"quality\", () => {\n log(\"Checking text, headings, accessibility and browser errors\");\n return measureQuality(browser, urls, timeout);\n }),\n visual: await stage(\"visual\", () =>\n measureVisual(browser, {\n urls,\n widths: options.widths,\n tolerance: options.tolerance,\n timeout,\n artifacts,\n onProgress: log,\n }),\n ),\n };\n } finally {\n await browser.close();\n }\n\n // Lighthouse runs last and alone so other browser work cannot skew timings.\n const lighthouse = await stage(\"lighthouse\", () =>\n runLighthouse({\n urls,\n runs: options.runs,\n formFactor: options.formFactor,\n chromePath,\n artifacts,\n onProgress: log,\n }),\n );\n\n const results = { ...measured, lighthouse };\n const checks = reportChecks({ comparability, ...results });\n const budgetResults = evaluateBudgets(budgets, { comparability, ...results });\n const { original: _original, ported: _ported, ...rest } = options;\n const report: BenchReport = {\n schemaVersion: 1,\n tool: { name: pkg.name, version: pkg.version },\n measuredAt,\n environment: {\n node: process.version,\n platform: process.platform,\n arch: process.arch,\n browser: `Chrome ${browserVersion}`,\n },\n options: { ...rest, outDir: artifacts?.dir },\n targets,\n comparability,\n skipped,\n errors,\n ...results,\n checks,\n budgets: budgetResults,\n passed:\n errors.length === 0 &&\n checks.every((check) => check.status !== \"fail\") &&\n budgetResults.every((budget) => budget.status !== \"fail\"),\n };\n\n if (!artifacts) return { report };\n return {\n report,\n files: {\n json: artifacts.write(\n \"report.json\",\n `${JSON.stringify(report, null, 2)}\\n`,\n ),\n markdown: artifacts.write(\n \"report.md\",\n renderMarkdown(report, artifacts.dir),\n ),\n },\n };\n}\n","import kleur from \"kleur\";\nimport {\n assetsSection,\n budgetItems,\n componentDifferences,\n lighthouseSection,\n qualitySection,\n stageItems,\n unmatchedSlots,\n visualSection,\n type Cell,\n type Section,\n type StatusItem,\n type Table,\n} from \"./sections\";\nimport type { BenchReport, CheckStatus } from \"./types\";\n\nconst STATUS_SYMBOL: Record<CheckStatus, string> = {\n pass: kleur.green(\"✓\"),\n fail: kleur.red(\"✗\"),\n warn: kleur.yellow(\"!\"),\n skip: kleur.dim(\"–\"),\n};\n\nconst INDENT = \" \";\nconst MAX_COMPONENT_DIFFERENCES = 5;\n\nconst visibleLength = (value: string) =>\n value.replace(/\\x1b\\[[0-9;]*m/g, \"\").length;\n\nfunction wrap(text: string, width: number) {\n const lines: string[] = [];\n let line = \"\";\n for (const word of text.split(\" \")) {\n if (line && visibleLength(line) + 1 + visibleLength(word) > width) {\n lines.push(line);\n line = word;\n } else {\n line = line ? `${line} ${word}` : word;\n }\n }\n if (line) lines.push(line);\n return lines;\n}\n\nconst colorCell = (cell: Cell) =>\n typeof cell === \"string\"\n ? cell\n : cell.tone === \"worse\"\n ? kleur.red(cell.text)\n : kleur.green(cell.text);\n\nfunction columns({ headings, align, rows }: Table) {\n const cells = rows.map((row) => row.map(colorCell));\n const widths = headings.map((_, index) =>\n Math.max(...[headings, ...cells].map((row) => visibleLength(row[index]!))),\n );\n const line = (row: string[]) =>\n INDENT +\n row\n .map((cell, index) => {\n const padding = \" \".repeat(widths[index]! - visibleLength(cell));\n return align[index] === \"r\" ? padding + cell : cell + padding;\n })\n .join(\" \")\n .trimEnd();\n return [kleur.dim(line(headings)), ...cells.map(line)].join(\"\\n\");\n}\n\n/** Label/detail list where details wrap to the terminal width. */\nfunction statusList(items: readonly StatusItem[], width: number) {\n const labelWidth = Math.max(...items.map((item) => item.label.length));\n const detailWidth = Math.max(30, width - INDENT.length - labelWidth - 4);\n return items\n .map((item) => {\n const [first = \"\", ...rest] = wrap(item.detail, detailWidth);\n const lead = `${INDENT}${STATUS_SYMBOL[item.status]} ${item.label.padEnd(labelWidth)} `;\n const hang = \" \".repeat(INDENT.length + 2 + labelWidth + 2);\n return [lead + first, ...rest.map((line) => hang + line)].join(\"\\n\");\n })\n .join(\"\\n\");\n}\n\nfunction heading(title: string, notes: readonly string[] = []) {\n return (\n kleur.bold(title) +\n (notes.length > 0 ? kleur.dim(` · ${notes.join(\" · \")}`) : \"\")\n );\n}\n\nconst sectionBlock = ({ title, notes, table }: Section) =>\n `${heading(title, notes)}\\n${columns(table)}`;\n\n/** Everything a reader needs from the report, formatted for a terminal. */\nexport function renderSummary(\n report: BenchReport,\n width = process.stdout.columns || 100,\n) {\n const { original, ported } = report.targets;\n const sections = [\n `${report.passed ? kleur.green().bold(\"PASS\") : kleur.red().bold(\"FAIL\")} ${original.finalUrl} ${kleur.dim(\"→\")} ${ported.finalUrl}`,\n `${heading(\"Checks\")}\\n${statusList(report.checks, width)}`,\n ];\n\n const budgets = budgetItems(report);\n if (budgets.length > 0)\n sections.push(`${heading(\"Budgets\")}\\n${statusList(budgets, width)}`);\n const stages = stageItems(report);\n if (stages.length > 0)\n sections.push(`${heading(\"Stages\")}\\n${statusList(stages, width)}`);\n\n const { lighthouse, assets, visual, quality, comparability } = report;\n if (lighthouse)\n sections.push(sectionBlock(lighthouseSection(lighthouse, comparability)));\n if (assets) sections.push(sectionBlock(assetsSection(assets)));\n\n if (visual) {\n const lines = [sectionBlock(visualSection(visual))];\n for (const result of visual.widths) {\n if (result.differences.length === 0) continue;\n const shown =\n result.differences.length > MAX_COMPONENT_DIFFERENCES\n ? ` (first ${MAX_COMPONENT_DIFFERENCES} of ${result.differences.length})`\n : \"\";\n lines.push(\n \"\",\n `${INDENT}Component differences at ${result.width}px${shown}`,\n columns(\n componentDifferences(result, {\n limit: MAX_COMPONENT_DIFFERENCES,\n textLength: 32,\n }),\n ),\n );\n }\n // Unmatched slots explain low match counts; widths usually agree.\n const unmatched = visual.widths[0] ? unmatchedSlots(visual.widths[0]) : [];\n if (unmatched.length > 0) {\n lines.push(\n \"\",\n ...unmatched\n .flatMap((line) => wrap(line, width - INDENT.length))\n .map((line) => INDENT + kleur.dim(line)),\n );\n }\n sections.push(lines.join(\"\\n\"));\n }\n\n if (quality) sections.push(sectionBlock(qualitySection(quality)));\n\n return sections.join(\"\\n\\n\");\n}\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,60 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bejamas/bench",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Compare an original site with its ported version: Lighthouse, route assets, accessibility, and visual parity.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": {
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
10
|
+
"default": "./dist/index.js"
|
|
11
|
+
},
|
|
12
|
+
"./package.json": "./package.json"
|
|
13
|
+
},
|
|
14
|
+
"bin": {
|
|
15
|
+
"bejamas-bench": "./dist/cli.js"
|
|
16
|
+
},
|
|
17
|
+
"files": [
|
|
18
|
+
"dist/"
|
|
19
|
+
],
|
|
20
|
+
"keywords": [
|
|
21
|
+
"benchmark",
|
|
22
|
+
"lighthouse",
|
|
23
|
+
"visual-regression",
|
|
24
|
+
"migration",
|
|
25
|
+
"bejamas"
|
|
26
|
+
],
|
|
27
|
+
"engines": {
|
|
28
|
+
"node": ">=22.19.0"
|
|
29
|
+
},
|
|
30
|
+
"scripts": {
|
|
31
|
+
"build": "tsdown",
|
|
32
|
+
"dev": "tsdown --watch",
|
|
33
|
+
"check-types": "tsc --noEmit",
|
|
34
|
+
"prepublishOnly": "bun run build",
|
|
35
|
+
"test": "bun run build && bun test"
|
|
36
|
+
},
|
|
37
|
+
"repository": {
|
|
38
|
+
"type": "git",
|
|
39
|
+
"url": "https://github.com/bejamas/ui.git",
|
|
40
|
+
"directory": "packages/bench"
|
|
41
|
+
},
|
|
42
|
+
"bugs": "https://github.com/bejamas/ui/issues",
|
|
43
|
+
"publishConfig": {
|
|
44
|
+
"access": "public"
|
|
45
|
+
},
|
|
46
|
+
"devDependencies": {
|
|
47
|
+
"@types/pngjs": "6.0.5",
|
|
48
|
+
"tsdown": "^0.23.0"
|
|
49
|
+
},
|
|
50
|
+
"dependencies": {
|
|
51
|
+
"axe-core": "4.14.0",
|
|
52
|
+
"chrome-launcher": "1.2.2",
|
|
53
|
+
"commander": "^15.0.0",
|
|
54
|
+
"kleur": "^4.1.5",
|
|
55
|
+
"lighthouse": "13.5.0",
|
|
56
|
+
"pixelmatch": "8.0.0",
|
|
57
|
+
"playwright-core": "1.63.0",
|
|
58
|
+
"pngjs": "7.0.0"
|
|
59
|
+
}
|
|
60
|
+
}
|