perf: enforce journey budgets and reference traces

This commit is contained in:
2026-09-02 07:48:02 +03:00
parent 8089d061ec
commit f4cf9fc77e
25 changed files with 1432 additions and 23 deletions

View File

@@ -0,0 +1,5 @@
export const PERFORMANCE_BUDGETS: Readonly<Record<string, number>>;
export function classifyArtifact(path: string, source?: string): string;
export function evaluateBudgetTotals(totals: Readonly<Record<string, number>>): {
violations: Array<{ budget: string; actual: number; threshold: number }>;
};

View File

@@ -0,0 +1,51 @@
export const PERFORMANCE_BUDGETS = Object.freeze({
initialHtmlCriticalCss: 100 * 1024,
initialApplicationJs: 120 * 1024,
heroPoster: 220 * 1024,
initialFonts: 120 * 1024,
firstLiveScene: 650 * 1024,
projectVideo: 2.5 * 1024 * 1024,
projectVideoDurationSeconds: 10,
projectVideoWidth: 1280,
projectVideoHeight: 720,
projectStill: 400 * 1024,
chart: 100 * 1024,
deferredProjectVisuals: 2.5 * 1024 * 1024,
highTierExtras: 750 * 1024,
deferredNonVideo: 3.5 * 1024 * 1024,
imageSceneConcurrency: 2,
videoConcurrency: 1,
mobileTextureDimension: 2048,
lowTierGpuEstimate: 96 * 1024 * 1024,
highTierGpuEstimate: 192 * 1024 * 1024,
});
export const classifyArtifact = (path, source = '') => {
if (path === 'index.html') return 'initial-html';
if (/^assets\/index-[^/]+\.css$/u.test(path)) return 'critical-css';
if (/^assets\/index-[^/]+\.js$/u.test(path) && /import\(/u.test(source)) return 'initial-application-js';
if (/^assets\/environment-[^/]+\.js$/u.test(path)) return 'first-live-scene';
if (/\.(?:woff2?|ttf|otf)$/u.test(path)) return 'initial-fonts';
if (/^assets\/hero-[^/]+\.(?:avif|webp|png|jpe?g)$/u.test(path)) return 'hero-poster';
if (/^assets\/project-[^/]+\.(?:webm|mp4)$/u.test(path)) return 'project-video';
if (/^assets\/project-[^/]*chart[^/]*\.(?:avif|webp|png|jpe?g)$/u.test(path)) return 'chart';
if (/^assets\/project-[^/]+\.(?:avif|webp|png|jpe?g|svg)$/u.test(path)) return 'project-still';
if (/^(?:robots\.txt|sitemap\.xml|_headers\.example|_redirects\.example)$/u.test(path)) return 'release-metadata';
throw new Error(`Artifact is unclassified: ${path}`);
};
const thresholdByTotal = Object.freeze({
initialHtmlCriticalCss: PERFORMANCE_BUDGETS.initialHtmlCriticalCss,
initialApplicationJs: PERFORMANCE_BUDGETS.initialApplicationJs,
heroPoster: PERFORMANCE_BUDGETS.heroPoster,
initialFonts: PERFORMANCE_BUDGETS.initialFonts,
firstLiveScene: PERFORMANCE_BUDGETS.firstLiveScene,
deferredProjectVisuals: PERFORMANCE_BUDGETS.deferredProjectVisuals,
deferredNonVideo: PERFORMANCE_BUDGETS.deferredNonVideo,
});
export const evaluateBudgetTotals = (totals) => ({
violations: Object.entries(thresholdByTotal)
.filter(([budget, threshold]) => (totals[budget] ?? 0) > threshold)
.map(([budget, threshold]) => ({ budget, actual: totals[budget], threshold })),
});

View File

@@ -0,0 +1,7 @@
export function aggregateFiveDriveTraces(runs: readonly unknown[]): {
schema: 'drive-trace-aggregation/v1';
profile: string;
runCount: 5;
summary: Record<string, number | boolean>;
passes: boolean;
};

View File

@@ -0,0 +1,37 @@
const median = (values) => {
if (values.length === 0) throw new Error('At least one value is required.');
const sorted = [...values].sort((left, right) => left - right);
return sorted[Math.floor(sorted.length / 2)];
};
export const aggregateFiveDriveTraces = (runs) => {
if (!Array.isArray(runs) || runs.length !== 5) throw new Error('Exactly five drive-trace.v1 runs are required.');
const profile = runs[0]?.profile;
if (typeof profile !== 'string' || runs.some((run) => run?.schema !== 'drive-trace.v1' || run.profile !== profile)) {
throw new Error('All runs must use one drive-trace.v1 profile.');
}
const lcp = runs.map((run) => run.metrics.lcp);
const tbt = runs.map((run) => run.metrics.tbt);
const everyPerRunBudgetPass = runs.every((run) => Object.values(run.budgetResults).every(Boolean));
const referenceEvidence = runs.every((run) => run.gatingReferenceResult === true && run.validReferenceCadence === true);
const summary = {
lcpWorst: Math.max(...lcp),
lcpMedian: median(lcp),
tbtWorst: Math.max(...tbt),
tbtMedian: median(tbt),
everyPerRunBudgetPass,
referenceEvidence,
};
return {
schema: 'drive-trace-aggregation/v1',
profile,
runCount: 5,
summary,
passes: referenceEvidence
&& everyPerRunBudgetPass
&& summary.lcpWorst <= 3_000
&& summary.lcpMedian <= 2_500
&& summary.tbtWorst <= 300
&& summary.tbtMedian <= 200,
};
};