Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
199 changes: 143 additions & 56 deletions packages/utils/src/minification-data.ts
Original file line number Diff line number Diff line change
@@ -1,63 +1,150 @@
import minificationData from "../../../data/minification-benchmarks-data.json";

export const popularMinifiers = ["terser", "esbuild", "@swc/core", "uglify-js", "oxc-minify"];
const MINIFIERS = [
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removes the need for long if/else chain in the original function.

Adding new minifiers will now be easier, just add it as a new item in the array.

{ id: "terser", name: "Terser", fill: "#a78bfa" },
{ id: "esbuild", name: "ESBuild", fill: "#10b981" },
{ id: "@swc/core", name: "SWC", fill: "#38bdf8" },
{ id: "uglify-js", name: "UglifyJS", fill: "#f87171" },
{ id: "oxc-minify", name: "OXC", fill: "#fbbf24" },
] as const;

export const libraries = Object.entries(minificationData)
.map(([name, data]: [string, any]) => ({ name, size: data.size }))
type MinifierDefinition = (typeof MINIFIERS)[number];
type MinifierId = MinifierDefinition["id"];
type Metric = "time" | "compression";

interface BenchmarkResultData {
time?: number;
minzippedBytes?: number;
}

interface MinifierBenchmark {
result?: {
data?: BenchmarkResultData;
};
}

interface LibraryBenchmark {
size: number;
minified?: Record<string, MinifierBenchmark | undefined>;
}

type MinificationBenchmarkData = Record<string, LibraryBenchmark>;

interface LibraryDataPoint {
name: string;
value: number;
minzippedBytes: number;
fill: string;
}

function isRecord(value: unknown): value is Record<string, unknown> {
return typeof value === "object" && value !== null;
}

function isBenchmarkResultData(value: unknown): value is BenchmarkResultData {
return (
isRecord(value) &&
(value.time === undefined || typeof value.time === "number") &&
(value.minzippedBytes === undefined || typeof value.minzippedBytes === "number")
);
}

function isMinifierBenchmark(value: unknown): value is MinifierBenchmark {
if (!isRecord(value)) {
return false;
}

if (value.result === undefined) {
return true;
}

return (
isRecord(value.result) &&
(value.result.data === undefined || isBenchmarkResultData(value.result.data))
);
}

function isLibraryBenchmark(value: unknown): value is LibraryBenchmark {
if (!isRecord(value) || typeof value.size !== "number") {
return false;
}

if (value.minified === undefined) {
return true;
}

return (
isRecord(value.minified) &&
Object.values(value.minified).every(
(minifierBenchmark) =>
minifierBenchmark === undefined || isMinifierBenchmark(minifierBenchmark),
)
);
}

function getBenchmarkData(data: unknown): MinificationBenchmarkData {
Copy link
Contributor Author

@yangshun yangshun Mar 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The previous version used any, which is not typesafe.

Unfortunately it takes 60 more lines of code to make TypeScript happy. Happy to revert back to any if not desired / deemed overkill.

if (!isRecord(data)) {
return {};
}

return Object.entries(data).reduce<MinificationBenchmarkData>((libraries, [name, benchmark]) => {
if (isLibraryBenchmark(benchmark)) {
libraries[name] = benchmark;
}

return libraries;
}, {});
}

const benchmarkData = getBenchmarkData(minificationData);

export const popularMinifiers: MinifierId[] = MINIFIERS.map(({ id }) => id);

export const libraries = Object.entries(benchmarkData)
.map(([name, { size }]) => ({ name, size }))
.toSorted((a, b) => b.size - a.size)
.map((item) => item.name);

export const getLibraryData = (library: string, metric: "time" | "compression") => {
const libraryData = (minificationData as Record<string, any>)[library];
const data: any[] = [];

popularMinifiers.forEach((minifier) => {
const minifierData = libraryData.minified?.[minifier];
if (minifierData?.result?.data) {
let value: number;
let minzippedBytes = 0;
if (metric === "time") {
value = Math.round(minifierData.result.data.time || 0);
} else {
// compression ratio
const originalSize = libraryData.size;
minzippedBytes = minifierData.result.data.minzippedBytes || 0;
value = Math.round(((originalSize - minzippedBytes) / originalSize) * 100 * 10) / 10;
}

data.push({
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The main aim was to remove this.

name:
minifier === "@swc/core"
? "SWC"
: minifier === "uglify-js"
? "UglifyJS"
: minifier === "oxc-minify"
? "OXC"
: minifier === "esbuild"
? "ESBuild"
: minifier === "terser"
? "Terser"
: minifier,
value,
minzippedBytes: minzippedBytes || 0,
fill:
minifier === "terser"
? "#a78bfa"
: minifier === "esbuild"
? "#10b981"
: minifier === "@swc/core"
? "#38bdf8"
: minifier === "uglify-js"
? "#f87171"
: minifier === "oxc-minify"
? "#fbbf24"
: "#9ca3af",
});
}
});
function getMetricValue(
metric: Metric,
librarySize: number,
resultData: BenchmarkResultData,
): number {
if (metric === "time") {
return Math.round(resultData.time || 0);
}

// Sort data: time from smallest to largest (fastest to slowest), compression from largest to smallest (best to worst)
return data.toSorted((a, b) =>
metric === "time" ? a.value - b.value : a.minzippedBytes - b.minzippedBytes,
);
};
const minzippedBytes = resultData.minzippedBytes || 0;
return Math.round(((librarySize - minzippedBytes) / librarySize) * 100 * 10) / 10;
}

function toLibraryDataPoint(
minifier: MinifierDefinition,
libraryData: LibraryBenchmark,
metric: Metric,
): LibraryDataPoint | null {
const resultData = libraryData.minified?.[minifier.id]?.result?.data;
if (!resultData) {
return null;
}

return {
name: minifier.name,
value: getMetricValue(metric, libraryData.size, resultData),
minzippedBytes: metric === "compression" ? resultData.minzippedBytes || 0 : 0,
fill: minifier.fill,
};
}

export function getLibraryData(library: string, metric: Metric): LibraryDataPoint[] {
const libraryData = benchmarkData[library];
if (!libraryData) {
return [];
}

return MINIFIERS.map((minifier) => toLibraryDataPoint(minifier, libraryData, metric))
.filter((item): item is LibraryDataPoint => item !== null)
.toSorted((a, b) =>
metric === "time" ? a.value - b.value : a.minzippedBytes - b.minzippedBytes,
);
}
Loading