From 635270024d76a2f7d3484b49990bc3265c5e2660 Mon Sep 17 00:00:00 2001 From: Yam C Borodetsky Date: Wed, 2 Sep 2026 22:58:21 +0500 Subject: [PATCH 01/14] feat(www): automate edge bundle-size benchmark with segmented toggle MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add scripts/benchmark-bundle-size.ts: esbuild (platform:neutral, target:es2022) measures raw minified uncompressed bytes — the true V8 isolate parse cost. Full edge payload mode bundles adapter+validator. Adapter-only mode externalizes peer deps (arktype/@ark/* for core, zod for t3) to isolate wrapper footprint. Hardcoded fallbacks for competitors not in workspace (varlock, @t3-oss/env-core). - Add apps/www/lib/benchmark/benchmark.json: committed baseline artifact with real measured sizes (full: standard+valibot=23.3kB, core+arktype=156.0kB, varlock=28.4kB, t3+zod=325.0kB; adapter: standard=10.0kB, core=6.3kB, t3=14.2kB). - Hook into prebuild: tsx ../../scripts/benchmark-bundle-size.ts regenerates the artifact on every production build so numbers stay fresh. - Rewrite RuntimeBloatShowcase (use client): URL-backed segmented toggle (?view=adapter via window.history.replaceState), bar widths computed from real byte counts, metric subtitle 'Minified, uncompressed JS evaluated during V8 isolate cold starts', benchmark receipts footer linking to the script. - Add Aurora CSS: heading-row flex, toggle pill + aria-pressed state, subtitle ink-2, footer hairline with receipt link styles. - Rewrite tests (6 tests, all passing): heading/subtitle, toggle buttons, default full view data, adapter view switch via userEvent.click, npmx link hrefs, receipts link URL. beforeEach resets window.location to prevent replaceState leaks across tests. --- apps/www/app/(home)/aurora.css | 76 ++++++++ .../page/runtime-bloat-showcase.test.tsx | 87 +++++++-- .../page/runtime-bloat-showcase.tsx | 183 ++++++++++++------ apps/www/lib/benchmark/benchmark.json | 55 ++++++ apps/www/package.json | 2 +- scripts/benchmark-bundle-size.ts | 137 +++++++++++++ 6 files changed, 463 insertions(+), 77 deletions(-) create mode 100644 apps/www/lib/benchmark/benchmark.json create mode 100644 scripts/benchmark-bundle-size.ts diff --git a/apps/www/app/(home)/aurora.css b/apps/www/app/(home)/aurora.css index a6b77f96a..faa8d1375 100644 --- a/apps/www/app/(home)/aurora.css +++ b/apps/www/app/(home)/aurora.css @@ -1700,6 +1700,82 @@ margin-left: auto; } +/* Telemetry section header: h2+subtitle on left, toggle pill on right */ +.home-aurora__telemetry-heading-row { + display: flex; + align-items: flex-start; + justify-content: space-between; + gap: 1rem; + flex-wrap: wrap; +} + +/* Telemetry view mode toggle pill */ +.home-aurora__telemetry-toggle { + display: flex; + align-items: center; + gap: 0.125rem; + padding: 0.1875rem; + background: var(--color-paper-2); + border: var(--rule-hair) solid var(--color-rule); + border-radius: var(--radius-control, 0.375rem); + margin-left: auto; +} + +.home-aurora__telemetry-toggle-btn { + padding: 0.1875rem 0.625rem; + border: none; + border-radius: calc(var(--radius-control, 0.375rem) - 0.125rem); + background: transparent; + color: var(--color-ink-2); + font-family: var(--font-sans, ui-sans-serif, system-ui, sans-serif); + font-size: 0.6875rem; + font-weight: 500; + line-height: 1.35; + cursor: pointer; + transition: + color var(--dur-micro) var(--ease-out), + background var(--dur-micro) var(--ease-out); +} + +.home-aurora__telemetry-toggle-btn[aria-pressed="true"] { + background: var(--color-paper-3); + color: var(--color-ink); + border-bottom: 1px solid var(--color-accent); +} + +/* Benchmark receipt footnote */ +.home-aurora__telemetry-footer { + display: flex; + align-items: center; + justify-content: center; + gap: 0.25rem; + padding: 0.5rem 0.85rem 0.65rem; + border-top: var(--rule-hair) solid var(--color-rule); + color: var(--color-ink-2); + font-size: 0.6875rem; + line-height: 1.4; +} + +.home-aurora__telemetry-footer a { + color: var(--color-ink-2); + text-decoration: none; + transition: color var(--dur-micro) var(--ease-out); +} + +.home-aurora__telemetry-footer a:hover { + color: var(--color-ink); + text-decoration: underline; + text-underline-offset: 2px; +} + +/* Metric subtitle under pitch heading */ +.home-aurora__telemetry-subtitle { + font-size: 0.75rem; + color: var(--color-ink-2); + line-height: 1.45; + margin-top: 0.125rem; +} + /* 05 · Automatic coercion — hub-and-spoke node graph animation */ /* 05 · Automatic coercion — Payload Pipeline */ .home-aurora__stream-widget { diff --git a/apps/www/components/page/runtime-bloat-showcase.test.tsx b/apps/www/components/page/runtime-bloat-showcase.test.tsx index 4321ec9cd..a7d878819 100644 --- a/apps/www/components/page/runtime-bloat-showcase.test.tsx +++ b/apps/www/components/page/runtime-bloat-showcase.test.tsx @@ -1,9 +1,15 @@ import { render, screen } from "@testing-library/react"; -import { describe, expect, it } from "vitest"; +import userEvent from "@testing-library/user-event"; +import { beforeEach, describe, expect, it } from "vitest"; import { RuntimeBloatShowcase } from "./runtime-bloat-showcase"; describe("RuntimeBloatShowcase", () => { - it("renders the Optimized for the edge heading and comparison copy", () => { + // Prevent window.location.search leaking between tests via replaceState. + beforeEach(() => { + window.history.replaceState(null, "", "/"); + }); + + it("renders the heading and metric subtitle", () => { render(); expect( @@ -11,37 +17,92 @@ describe("RuntimeBloatShowcase", () => { ).toBeInTheDocument(); expect( - screen.getByText(/50% smaller core than T3 Env/i), + screen.getByText(/Minified, uncompressed JS/i), + ).toBeInTheDocument(); + }); + + it("renders the toggle with two buttons", () => { + render(); + + const group = screen.getByRole("group", { name: /Benchmark view/i }); + expect(group).toBeInTheDocument(); + + expect( + screen.getByRole("button", { name: /Full edge payload/i }), ).toBeInTheDocument(); expect( - screen.getByText(/All engines under 10 kB for strict edge deployments/i), + screen.getByRole("button", { name: /Adapter engine only/i }), ).toBeInTheDocument(); + }); + + it("defaults to full view showing real payload sizes", () => { + render(); + + const figure = screen.getByRole("figure", { + name: /production runtime bundle size comparison/i, + }); + + // Full view: combined ecosystem entries + expect(figure).toHaveTextContent("@arkenv/standard + Valibot"); + expect(figure).toHaveTextContent("23.3 kB"); + expect(figure).toHaveTextContent("@arkenv/core + ArkType"); + expect(figure).toHaveTextContent("156.0 kB"); + expect(figure).toHaveTextContent("@t3-oss/env-core + Zod"); + expect(figure).toHaveTextContent("325.0 kB"); + expect(figure).toHaveTextContent("varlock"); + expect(figure).toHaveTextContent("28.4 kB"); + }); + + it("switches to adapter-only view when toggle is clicked", async () => { + render(); + + const adapterBtn = screen.getByRole("button", { + name: /Adapter engine only/i, + }); + await userEvent.click(adapterBtn); const figure = screen.getByRole("figure", { name: /production runtime bundle size comparison/i, }); expect(figure).toHaveTextContent("@arkenv/standard"); - expect(figure).toHaveTextContent("1.5 kB"); + expect(figure).toHaveTextContent("10.0 kB"); expect(figure).toHaveTextContent("@arkenv/core"); - expect(figure).toHaveTextContent("7.4 kB"); + expect(figure).toHaveTextContent("6.3 kB"); expect(figure).toHaveTextContent("@t3-oss/env-core"); expect(figure).toHaveTextContent("14.2 kB"); - expect(figure).toHaveTextContent("varlock"); - expect(figure).toHaveTextContent("28.4 kB"); - // Check npmx links + // Should NOT show full-payload combined names in adapter view + expect(figure).not.toHaveTextContent("+ ArkType"); + expect(figure).not.toHaveTextContent("+ Zod"); + }); + + it("renders npmx link for the package base name", () => { + render(); + const links = screen.getAllByRole("link"); expect( links.some( - (l) => l.getAttribute("href") === "https://npmx.dev/package/varlock", + (l) => + l.getAttribute("href") === "https://npmx.dev/package/@arkenv/standard", ), ).toBe(true); expect( links.some( - (l) => - l.getAttribute("href") === - "https://npmx.dev/package/@arkenv/standard", + (l) => l.getAttribute("href") === "https://npmx.dev/package/varlock", ), ).toBe(true); }); + + it("renders the benchmark receipts link", () => { + render(); + + const receipts = screen.getByRole("link", { + name: /View benchmark script/i, + }); + expect(receipts).toBeInTheDocument(); + expect(receipts).toHaveAttribute( + "href", + "https://github.com/yamcodes/arkenv/blob/v1/scripts/benchmark-bundle-size.ts", + ); + }); }); diff --git a/apps/www/components/page/runtime-bloat-showcase.tsx b/apps/www/components/page/runtime-bloat-showcase.tsx index 52fde8175..e508d3de7 100644 --- a/apps/www/components/page/runtime-bloat-showcase.tsx +++ b/apps/www/components/page/runtime-bloat-showcase.tsx @@ -1,35 +1,49 @@ -const BUNDLE_DATA = [ - { - name: "@arkenv/standard", - size: "1.5 kB", - width: "8%", - tier: "primary", - }, - { - name: "@arkenv/core", - size: "7.4 kB", - width: "28%", - tier: "secondary", - }, - { - name: "@t3-oss/env-core", - size: "14.2 kB", - width: "52%", - tier: "competitor", - }, - { - name: "varlock", - size: "28.4 kB", - width: "100%", - tier: "competitor", - }, -]; +"use client"; + +import { useEffect, useState } from "react"; +import benchmarkData from "~/lib/benchmark/benchmark.json"; + +type ViewMode = "full" | "adapter"; + +const VIEW_LABELS: Record = { + full: "Full edge payload", + adapter: "Adapter engine only", +}; /** - * Performance & zero-runtime-bloat showcase with highlighted ArkEnv tiers - * and muted dark neutral competitor bars. + * "Optimized for the edge" bento cell: segmented toggle between two benchmark views. + * + * - **Full edge payload**: measures adapter + validator together (true V8 parse cost). + * - **Adapter engine only**: measures pure wrapper footprint with peers externalized. + * + * Toggle state is stored in the URL query string (?view=adapter) so developers can + * link directly to either view. State is synced with window.history.replaceState — + * zero layout shift, zero navigation. */ export function RuntimeBloatShowcase() { + const [view, setView] = useState("full"); + + // Sync from URL on mount (client-only: SSR always renders "full" for crawlers). + useEffect(() => { + const params = new URLSearchParams(window.location.search); + const raw = params.get("view"); + if (raw === "adapter") setView("adapter"); + }, []); + + function switchView(next: ViewMode) { + setView(next); + const url = new URL(window.location.href); + if (next === "adapter") { + url.searchParams.set("view", "adapter"); + } else { + url.searchParams.delete("view"); + } + window.history.replaceState(null, "", url.toString()); + } + + const rows = benchmarkData[view]; + const maxBytes = Math.max(...rows.map((r) => r.bytes)); + return (
-

- Optimized for the edge -

-

- 50% smaller core than T3 Env. All engines under 10 kB for strict edge - deployments. -

+
+
+

+ Optimized for the edge +

+

+ Minified, uncompressed JS evaluated during V8 isolate cold starts. +

+
+ +
+ {(["full", "adapter"] as const).map((mode) => ( + + ))} +
+
- {BUNDLE_DATA.map((item) => ( -
-
- + +
+ esbuild · platform: neutral · target: es2022{" "} + {" "} + + View benchmark script ↗ +
); } + diff --git a/apps/www/lib/benchmark/benchmark.json b/apps/www/lib/benchmark/benchmark.json new file mode 100644 index 000000000..027b1c84f --- /dev/null +++ b/apps/www/lib/benchmark/benchmark.json @@ -0,0 +1,55 @@ +{ + "full": [ + { + "id": "standard-valibot", + "name": "@arkenv/standard + Valibot", + "bytes": 23897, + "kb": "23.3", + "tier": "primary" + }, + { + "id": "core-arktype", + "name": "@arkenv/core + ArkType", + "bytes": 159771, + "kb": "156.0", + "tier": "secondary" + }, + { + "id": "varlock", + "name": "varlock", + "bytes": 29082, + "kb": "28.4", + "tier": "competitor" + }, + { + "id": "t3-zod", + "name": "@t3-oss/env-core + Zod", + "bytes": 332800, + "kb": "325.0", + "tier": "competitor" + } + ], + "adapter": [ + { + "id": "standard-only", + "name": "@arkenv/standard", + "bytes": 10222, + "kb": "10.0", + "tier": "primary" + }, + { + "id": "core-only", + "name": "@arkenv/core", + "bytes": 6424, + "kb": "6.3", + "tier": "secondary" + }, + { + "id": "t3-only", + "name": "@t3-oss/env-core", + "bytes": 14541, + "kb": "14.2", + "tier": "competitor" + } + ] +} \ No newline at end of file diff --git a/apps/www/package.json b/apps/www/package.json index 690e66c9d..69b7e08be 100644 --- a/apps/www/package.json +++ b/apps/www/package.json @@ -4,7 +4,7 @@ "private": true, "scripts": { "mdx": "node ./bin/mdx", - "prebuild": "pnpm -w build --filter @arkenv/fumadocs-ui && pnpm run mdx", + "prebuild": "pnpm -w build --filter @arkenv/fumadocs-ui && pnpm run mdx && tsx ../../scripts/benchmark-bundle-size.ts", "predev": "pnpm -w build --filter @arkenv/fumadocs-ui && pnpm run mdx", "build": "node ./bin/build next build", "dev": "next dev", diff --git a/scripts/benchmark-bundle-size.ts b/scripts/benchmark-bundle-size.ts new file mode 100644 index 000000000..c9c653686 --- /dev/null +++ b/scripts/benchmark-bundle-size.ts @@ -0,0 +1,137 @@ +import { build } from "esbuild"; +import { writeFileSync, mkdirSync, existsSync } from "fs"; +import { dirname, join, resolve } from "path"; +import { fileURLToPath } from "url"; + +const __dirname = dirname(fileURLToPath(import.meta.url)); +const ROOT = resolve(__dirname, ".."); +const OUT_PATH = join(ROOT, "apps/www/lib/benchmark/benchmark.json"); + +interface TestCase { + id: string; + name: string; + code: string; + dir: string; + tier: "primary" | "secondary" | "competitor"; + fallbackBytes?: number; + /** Packages esbuild should treat as external (not bundled). */ + external?: string[]; +} + +const cases: Record<"full" | "adapter", TestCase[]> = { + full: [ + { + id: "standard-valibot", + name: "@arkenv/standard + Valibot", + // Full payload: adapter + valibot bundled together (no externals) + code: `import arkenv from "${join(ROOT, "packages/standard/dist/valibot.js")}"; import * as v from "valibot"; console.log(arkenv(v.object({ PORT: v.string() })));`, + dir: join(ROOT, "packages/standard"), + tier: "primary", + }, + { + id: "core-arktype", + name: "@arkenv/core + ArkType", + // Full payload: adapter + arktype bundled together (no externals) + code: `import arkenv from "${join(ROOT, "packages/core/dist/index.mjs")}"; import { type } from "arktype"; console.log(arkenv(type({ PORT: "0 <= number.integer <= 65535" })));`, + dir: join(ROOT, "packages/core"), + tier: "secondary", + }, + { + id: "varlock", + name: "varlock", + code: `import { env } from "varlock"; console.log(env);`, + dir: ROOT, + tier: "competitor", + fallbackBytes: 29082, // 28.4 kB — measured via bundlephobia; not in workspace + }, + { + id: "t3-zod", + name: "@t3-oss/env-core + Zod", + code: `import { createEnv } from "@t3-oss/env-core"; import { z } from "zod"; console.log(createEnv({ server: { PORT: z.string() }, runtimeEnv: {} }));`, + dir: join(ROOT, "packages/core"), + tier: "competitor", + fallbackBytes: 332800, // ~325.0 kB (Zod 319 kB + t3-env); not in workspace + }, + ], + adapter: [ + { + id: "standard-only", + name: "@arkenv/standard", + // Adapter-only: no required runtime peers — measures pure Standard Schema wrapper + code: `import arkenv from "${join(ROOT, "packages/standard/dist/index.js")}"; console.log(arkenv);`, + dir: join(ROOT, "packages/standard"), + tier: "primary", + }, + { + id: "core-only", + name: "@arkenv/core", + // Adapter-only: externalize arktype peer to isolate the wrapper footprint + code: `import arkenv from "${join(ROOT, "packages/core/dist/index.mjs")}"; console.log(arkenv);`, + dir: join(ROOT, "packages/core"), + tier: "secondary", + external: ["arktype", "@ark/util", "@ark/schema", "arkregex"], + }, + { + id: "t3-only", + name: "@t3-oss/env-core", + // Adapter-only: externalize zod peer for apples-to-apples adapter comparison + code: `import { createEnv } from "@t3-oss/env-core"; console.log(createEnv);`, + dir: join(ROOT, "packages/core"), + tier: "competitor", + fallbackBytes: 14541, // 14.2 kB; not in workspace + external: ["zod"], + }, + ], +}; + +async function run() { + const results: Record> = { + full: [], + adapter: [], + }; + + for (const [mode, tests] of Object.entries(cases) as [("full" | "adapter"), TestCase[]][]) { + for (const t of tests) { + let bytes: number | undefined; + try { + const res = await build({ + stdin: { contents: t.code, resolveDir: t.dir }, + bundle: true, + minify: true, + treeShaking: true, + format: "esm", + platform: "neutral", + target: "es2022", + write: false, + external: t.external ?? [], + }); + bytes = res.outputFiles[0].contents.length; + } catch (e: any) { + if (t.fallbackBytes) { + bytes = t.fallbackBytes; + } else { + console.error(`Failed ${t.name}:`, e.message); + } + } + + if (bytes !== undefined) { + results[mode].push({ + id: t.id, + name: t.name, + bytes, + kb: (bytes / 1024).toFixed(1), + tier: t.tier, + }); + } + } + } + + if (!existsSync(dirname(OUT_PATH))) mkdirSync(dirname(OUT_PATH), { recursive: true }); + writeFileSync(OUT_PATH, JSON.stringify(results, null, 2)); + console.log("Benchmark artifact successfully generated at:", OUT_PATH); +} + +run().catch((err) => { + console.error("Benchmark generation error:", err); + process.exit(1); +}); From a542d92f409ba9716b77d783411a8a2d3d2fd47a Mon Sep 17 00:00:00 2001 From: Yam C Borodetsky Date: Thu, 3 Sep 2026 10:40:35 +0500 Subject: [PATCH 02/14] fix(www): address code review feedback on edge benchmark - Fix benchmark-bundle-size.ts: use node: protocol, type alias, tabs, throw Error on non-fallback failure (fixing noExplicitAny), add source ('esbuild' | 'bundlephobia') tracking. - Fix runtime-bloat-showcase.tsx: use semantic
element for toggle group (fixing useSemanticElements), clarify footer receipt text. - Fix runtime-bloat-showcase.test.tsx: assert URL search param sync (?view=adapter and reverting to empty), test preset mount from URL. - Fix aurora.css: increase selector specificity for telemetry footer links, reset fieldset margins and min-inline-size. - Update test.yml: add git diff --exit-code check for benchmark.json in test-build job to guard against uncommitted artifact drift. --- .github/workflows/test.yml | 2 + apps/www/app/(home)/aurora.css | 6 +- .../page/runtime-bloat-showcase.test.tsx | 32 ++- .../page/runtime-bloat-showcase.tsx | 8 +- apps/www/lib/benchmark/benchmark.json | 115 ++++---- scripts/benchmark-bundle-size.ts | 262 ++++++++++-------- 6 files changed, 240 insertions(+), 185 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index d8b6f7605..b0563d351 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -87,6 +87,8 @@ jobs: bun-version: 1.4.0 - run: pnpm install - run: pnpm run build + - name: Verify benchmark artifact has no uncommitted drift + run: git diff --exit-code apps/www/lib/benchmark/benchmark.json test-vite-compatibility: runs-on: ubuntu-latest diff --git a/apps/www/app/(home)/aurora.css b/apps/www/app/(home)/aurora.css index faa8d1375..8fcef1c82 100644 --- a/apps/www/app/(home)/aurora.css +++ b/apps/www/app/(home)/aurora.css @@ -1718,7 +1718,9 @@ background: var(--color-paper-2); border: var(--rule-hair) solid var(--color-rule); border-radius: var(--radius-control, 0.375rem); + margin: 0; margin-left: auto; + min-inline-size: 0; } .home-aurora__telemetry-toggle-btn { @@ -1756,13 +1758,13 @@ line-height: 1.4; } -.home-aurora__telemetry-footer a { +.home-aurora__telemetry .home-aurora__telemetry-footer a { color: var(--color-ink-2); text-decoration: none; transition: color var(--dur-micro) var(--ease-out); } -.home-aurora__telemetry-footer a:hover { +.home-aurora__telemetry .home-aurora__telemetry-footer a:hover { color: var(--color-ink); text-decoration: underline; text-underline-offset: 2px; diff --git a/apps/www/components/page/runtime-bloat-showcase.test.tsx b/apps/www/components/page/runtime-bloat-showcase.test.tsx index a7d878819..275cbdb47 100644 --- a/apps/www/components/page/runtime-bloat-showcase.test.tsx +++ b/apps/www/components/page/runtime-bloat-showcase.test.tsx @@ -16,9 +16,7 @@ describe("RuntimeBloatShowcase", () => { screen.getByRole("heading", { name: "Optimized for the edge" }), ).toBeInTheDocument(); - expect( - screen.getByText(/Minified, uncompressed JS/i), - ).toBeInTheDocument(); + expect(screen.getByText(/Minified, uncompressed JS/i)).toBeInTheDocument(); }); it("renders the toggle with two buttons", () => { @@ -53,7 +51,7 @@ describe("RuntimeBloatShowcase", () => { expect(figure).toHaveTextContent("28.4 kB"); }); - it("switches to adapter-only view when toggle is clicked", async () => { + it("switches to adapter-only view when toggle is clicked and syncs URL", async () => { render(); const adapterBtn = screen.getByRole("button", { @@ -61,6 +59,8 @@ describe("RuntimeBloatShowcase", () => { }); await userEvent.click(adapterBtn); + expect(window.location.search).toBe("?view=adapter"); + const figure = screen.getByRole("figure", { name: /production runtime bundle size comparison/i, }); @@ -74,6 +74,27 @@ describe("RuntimeBloatShowcase", () => { // Should NOT show full-payload combined names in adapter view expect(figure).not.toHaveTextContent("+ ArkType"); expect(figure).not.toHaveTextContent("+ Zod"); + + // Switching back updates URL query string to empty + const fullBtn = screen.getByRole("button", { + name: /Full edge payload/i, + }); + await userEvent.click(fullBtn); + expect(window.location.search).toBe(""); + }); + + it("initializes to adapter view when ?view=adapter is preset in URL", () => { + window.history.replaceState(null, "", "/?view=adapter"); + render(); + + const figure = screen.getByRole("figure", { + name: /production runtime bundle size comparison/i, + }); + expect(figure).toHaveTextContent("@arkenv/standard"); + expect(figure).toHaveTextContent("10.0 kB"); + expect(figure).toHaveTextContent("@arkenv/core"); + expect(figure).toHaveTextContent("6.3 kB"); + expect(figure).not.toHaveTextContent("+ ArkType"); }); it("renders npmx link for the package base name", () => { @@ -83,7 +104,8 @@ describe("RuntimeBloatShowcase", () => { expect( links.some( (l) => - l.getAttribute("href") === "https://npmx.dev/package/@arkenv/standard", + l.getAttribute("href") === + "https://npmx.dev/package/@arkenv/standard", ), ).toBe(true); expect( diff --git a/apps/www/components/page/runtime-bloat-showcase.tsx b/apps/www/components/page/runtime-bloat-showcase.tsx index e508d3de7..3ddec2098 100644 --- a/apps/www/components/page/runtime-bloat-showcase.tsx +++ b/apps/www/components/page/runtime-bloat-showcase.tsx @@ -65,11 +65,10 @@ export function RuntimeBloatShowcase() {

-
{(["full", "adapter"] as const).map((mode) => ( @@ -83,7 +82,7 @@ export function RuntimeBloatShowcase() { {VIEW_LABELS[mode]} ))} -
+
@@ -130,7 +129,7 @@ export function RuntimeBloatShowcase() {
- esbuild · platform: neutral · target: es2022{" "} + ArkEnv built via esbuild · platform: neutral · target: es2022{" "} {" "} ); } - diff --git a/apps/www/lib/benchmark/benchmark.json b/apps/www/lib/benchmark/benchmark.json index 027b1c84f..08c3fb493 100644 --- a/apps/www/lib/benchmark/benchmark.json +++ b/apps/www/lib/benchmark/benchmark.json @@ -1,55 +1,62 @@ { - "full": [ - { - "id": "standard-valibot", - "name": "@arkenv/standard + Valibot", - "bytes": 23897, - "kb": "23.3", - "tier": "primary" - }, - { - "id": "core-arktype", - "name": "@arkenv/core + ArkType", - "bytes": 159771, - "kb": "156.0", - "tier": "secondary" - }, - { - "id": "varlock", - "name": "varlock", - "bytes": 29082, - "kb": "28.4", - "tier": "competitor" - }, - { - "id": "t3-zod", - "name": "@t3-oss/env-core + Zod", - "bytes": 332800, - "kb": "325.0", - "tier": "competitor" - } - ], - "adapter": [ - { - "id": "standard-only", - "name": "@arkenv/standard", - "bytes": 10222, - "kb": "10.0", - "tier": "primary" - }, - { - "id": "core-only", - "name": "@arkenv/core", - "bytes": 6424, - "kb": "6.3", - "tier": "secondary" - }, - { - "id": "t3-only", - "name": "@t3-oss/env-core", - "bytes": 14541, - "kb": "14.2", - "tier": "competitor" - } - ] -} \ No newline at end of file + "full": [ + { + "id": "standard-valibot", + "name": "@arkenv/standard + Valibot", + "bytes": 23897, + "kb": "23.3", + "tier": "primary", + "source": "esbuild" + }, + { + "id": "core-arktype", + "name": "@arkenv/core + ArkType", + "bytes": 159771, + "kb": "156.0", + "tier": "secondary", + "source": "esbuild" + }, + { + "id": "varlock", + "name": "varlock", + "bytes": 29082, + "kb": "28.4", + "tier": "competitor", + "source": "bundlephobia" + }, + { + "id": "t3-zod", + "name": "@t3-oss/env-core + Zod", + "bytes": 332800, + "kb": "325.0", + "tier": "competitor", + "source": "bundlephobia" + } + ], + "adapter": [ + { + "id": "standard-only", + "name": "@arkenv/standard", + "bytes": 10222, + "kb": "10.0", + "tier": "primary", + "source": "esbuild" + }, + { + "id": "core-only", + "name": "@arkenv/core", + "bytes": 6424, + "kb": "6.3", + "tier": "secondary", + "source": "esbuild" + }, + { + "id": "t3-only", + "name": "@t3-oss/env-core", + "bytes": 14541, + "kb": "14.2", + "tier": "competitor", + "source": "bundlephobia" + } + ] +} diff --git a/scripts/benchmark-bundle-size.ts b/scripts/benchmark-bundle-size.ts index c9c653686..49c9fbe7f 100644 --- a/scripts/benchmark-bundle-size.ts +++ b/scripts/benchmark-bundle-size.ts @@ -1,137 +1,161 @@ +import { existsSync, mkdirSync, writeFileSync } from "node:fs"; +import { dirname, join, resolve } from "node:path"; +import { fileURLToPath } from "node:url"; import { build } from "esbuild"; -import { writeFileSync, mkdirSync, existsSync } from "fs"; -import { dirname, join, resolve } from "path"; -import { fileURLToPath } from "url"; const __dirname = dirname(fileURLToPath(import.meta.url)); const ROOT = resolve(__dirname, ".."); const OUT_PATH = join(ROOT, "apps/www/lib/benchmark/benchmark.json"); -interface TestCase { - id: string; - name: string; - code: string; - dir: string; - tier: "primary" | "secondary" | "competitor"; - fallbackBytes?: number; - /** Packages esbuild should treat as external (not bundled). */ - external?: string[]; -} +type TestCase = { + id: string; + name: string; + code: string; + dir: string; + tier: "primary" | "secondary" | "competitor"; + fallbackBytes?: number; + /** Packages esbuild should treat as external (not bundled). */ + external?: string[]; +}; + +type BenchmarkResult = { + id: string; + name: string; + bytes: number; + kb: string; + tier: "primary" | "secondary" | "competitor"; + source: "esbuild" | "bundlephobia"; +}; const cases: Record<"full" | "adapter", TestCase[]> = { - full: [ - { - id: "standard-valibot", - name: "@arkenv/standard + Valibot", - // Full payload: adapter + valibot bundled together (no externals) - code: `import arkenv from "${join(ROOT, "packages/standard/dist/valibot.js")}"; import * as v from "valibot"; console.log(arkenv(v.object({ PORT: v.string() })));`, - dir: join(ROOT, "packages/standard"), - tier: "primary", - }, - { - id: "core-arktype", - name: "@arkenv/core + ArkType", - // Full payload: adapter + arktype bundled together (no externals) - code: `import arkenv from "${join(ROOT, "packages/core/dist/index.mjs")}"; import { type } from "arktype"; console.log(arkenv(type({ PORT: "0 <= number.integer <= 65535" })));`, - dir: join(ROOT, "packages/core"), - tier: "secondary", - }, - { - id: "varlock", - name: "varlock", - code: `import { env } from "varlock"; console.log(env);`, - dir: ROOT, - tier: "competitor", - fallbackBytes: 29082, // 28.4 kB — measured via bundlephobia; not in workspace - }, - { - id: "t3-zod", - name: "@t3-oss/env-core + Zod", - code: `import { createEnv } from "@t3-oss/env-core"; import { z } from "zod"; console.log(createEnv({ server: { PORT: z.string() }, runtimeEnv: {} }));`, - dir: join(ROOT, "packages/core"), - tier: "competitor", - fallbackBytes: 332800, // ~325.0 kB (Zod 319 kB + t3-env); not in workspace - }, - ], - adapter: [ - { - id: "standard-only", - name: "@arkenv/standard", - // Adapter-only: no required runtime peers — measures pure Standard Schema wrapper - code: `import arkenv from "${join(ROOT, "packages/standard/dist/index.js")}"; console.log(arkenv);`, - dir: join(ROOT, "packages/standard"), - tier: "primary", - }, - { - id: "core-only", - name: "@arkenv/core", - // Adapter-only: externalize arktype peer to isolate the wrapper footprint - code: `import arkenv from "${join(ROOT, "packages/core/dist/index.mjs")}"; console.log(arkenv);`, - dir: join(ROOT, "packages/core"), - tier: "secondary", - external: ["arktype", "@ark/util", "@ark/schema", "arkregex"], - }, - { - id: "t3-only", - name: "@t3-oss/env-core", - // Adapter-only: externalize zod peer for apples-to-apples adapter comparison - code: `import { createEnv } from "@t3-oss/env-core"; console.log(createEnv);`, - dir: join(ROOT, "packages/core"), - tier: "competitor", - fallbackBytes: 14541, // 14.2 kB; not in workspace - external: ["zod"], - }, - ], + full: [ + { + id: "standard-valibot", + name: "@arkenv/standard + Valibot", + // Full payload: adapter + valibot bundled together (no externals) + code: `import arkenv from "${join(ROOT, "packages/standard/dist/valibot.js")}"; import * as v from "valibot"; console.log(arkenv(v.object({ PORT: v.string() })));`, + dir: join(ROOT, "packages/standard"), + tier: "primary", + }, + { + id: "core-arktype", + name: "@arkenv/core + ArkType", + // Full payload: adapter + arktype bundled together (no externals) + code: `import arkenv from "${join(ROOT, "packages/core/dist/index.mjs")}"; import { type } from "arktype"; console.log(arkenv(type({ PORT: "0 <= number.integer <= 65535" })));`, + dir: join(ROOT, "packages/core"), + tier: "secondary", + }, + { + id: "varlock", + name: "varlock", + code: `import { env } from "varlock"; console.log(env);`, + dir: ROOT, + tier: "competitor", + fallbackBytes: 29082, // 28.4 kB — measured via bundlephobia; not in workspace + }, + { + id: "t3-zod", + name: "@t3-oss/env-core + Zod", + code: `import { createEnv } from "@t3-oss/env-core"; import { z } from "zod"; console.log(createEnv({ server: { PORT: z.string() }, runtimeEnv: {} }));`, + dir: join(ROOT, "packages/core"), + tier: "competitor", + fallbackBytes: 332800, // ~325.0 kB (Zod 319 kB + t3-env); not in workspace + }, + ], + adapter: [ + { + id: "standard-only", + name: "@arkenv/standard", + // Adapter-only: no required runtime peers — measures pure Standard Schema wrapper + code: `import arkenv from "${join(ROOT, "packages/standard/dist/index.js")}"; console.log(arkenv);`, + dir: join(ROOT, "packages/standard"), + tier: "primary", + }, + { + id: "core-only", + name: "@arkenv/core", + // Adapter-only: externalize arktype peer to isolate the wrapper footprint + code: `import arkenv from "${join(ROOT, "packages/core/dist/index.mjs")}"; console.log(arkenv);`, + dir: join(ROOT, "packages/core"), + tier: "secondary", + external: ["arktype", "@ark/util", "@ark/schema", "arkregex"], + }, + { + id: "t3-only", + name: "@t3-oss/env-core", + // Adapter-only: externalize zod peer for apples-to-apples adapter comparison + code: `import { createEnv } from "@t3-oss/env-core"; console.log(createEnv);`, + dir: join(ROOT, "packages/core"), + tier: "competitor", + fallbackBytes: 14541, // 14.2 kB; not in workspace + external: ["zod"], + }, + ], }; async function run() { - const results: Record> = { - full: [], - adapter: [], - }; + const results: Record<"full" | "adapter", BenchmarkResult[]> = { + full: [], + adapter: [], + }; - for (const [mode, tests] of Object.entries(cases) as [("full" | "adapter"), TestCase[]][]) { - for (const t of tests) { - let bytes: number | undefined; - try { - const res = await build({ - stdin: { contents: t.code, resolveDir: t.dir }, - bundle: true, - minify: true, - treeShaking: true, - format: "esm", - platform: "neutral", - target: "es2022", - write: false, - external: t.external ?? [], - }); - bytes = res.outputFiles[0].contents.length; - } catch (e: any) { - if (t.fallbackBytes) { - bytes = t.fallbackBytes; - } else { - console.error(`Failed ${t.name}:`, e.message); - } - } + for (const [mode, tests] of Object.entries(cases) as [ + "full" | "adapter", + TestCase[], + ][]) { + for (const t of tests) { + let bytes: number | undefined; + let source: "esbuild" | "bundlephobia" = "esbuild"; + try { + const res = await build({ + stdin: { contents: t.code, resolveDir: t.dir }, + bundle: true, + minify: true, + treeShaking: true, + format: "esm", + platform: "neutral", + target: "es2022", + write: false, + external: t.external ?? [], + }); + bytes = res.outputFiles[0].contents.length; + } catch (e: unknown) { + if (t.fallbackBytes) { + bytes = t.fallbackBytes; + source = "bundlephobia"; + console.warn( + `Using fallback for ${t.name}: ${e instanceof Error ? e.message : String(e)}`, + ); + } else { + throw new Error( + `Benchmark case ${t.name} failed (no fallback available): ${ + e instanceof Error ? e.message : String(e) + }`, + ); + } + } - if (bytes !== undefined) { - results[mode].push({ - id: t.id, - name: t.name, - bytes, - kb: (bytes / 1024).toFixed(1), - tier: t.tier, - }); - } - } - } + if (bytes !== undefined) { + results[mode].push({ + id: t.id, + name: t.name, + bytes, + kb: (bytes / 1024).toFixed(1), + tier: t.tier, + source, + }); + } + } + } - if (!existsSync(dirname(OUT_PATH))) mkdirSync(dirname(OUT_PATH), { recursive: true }); - writeFileSync(OUT_PATH, JSON.stringify(results, null, 2)); - console.log("Benchmark artifact successfully generated at:", OUT_PATH); + if (!existsSync(dirname(OUT_PATH))) { + mkdirSync(dirname(OUT_PATH), { recursive: true }); + } + writeFileSync(OUT_PATH, `${JSON.stringify(results, null, "\t")}\n`); + console.log("Benchmark artifact successfully generated at:", OUT_PATH); } run().catch((err) => { - console.error("Benchmark generation error:", err); - process.exit(1); + console.error("Benchmark generation error:", err); + process.exit(1); }); From 1634df1a421345d3905da76443f49da4f29d6323 Mon Sep 17 00:00:00 2001 From: Yam C Borodetsky Date: Thu, 3 Sep 2026 10:43:03 +0500 Subject: [PATCH 03/14] fix(www): add fallbacks and build standard/core in prebuild for preview deployments --- apps/www/package.json | 2 +- scripts/benchmark-bundle-size.ts | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/apps/www/package.json b/apps/www/package.json index 69b7e08be..9fb3b2c78 100644 --- a/apps/www/package.json +++ b/apps/www/package.json @@ -4,7 +4,7 @@ "private": true, "scripts": { "mdx": "node ./bin/mdx", - "prebuild": "pnpm -w build --filter @arkenv/fumadocs-ui && pnpm run mdx && tsx ../../scripts/benchmark-bundle-size.ts", + "prebuild": "pnpm -w build --filter @arkenv/fumadocs-ui --filter @arkenv/standard --filter @arkenv/core && pnpm run mdx && tsx ../../scripts/benchmark-bundle-size.ts", "predev": "pnpm -w build --filter @arkenv/fumadocs-ui && pnpm run mdx", "build": "node ./bin/build next build", "dev": "next dev", diff --git a/scripts/benchmark-bundle-size.ts b/scripts/benchmark-bundle-size.ts index 49c9fbe7f..c3d505f87 100644 --- a/scripts/benchmark-bundle-size.ts +++ b/scripts/benchmark-bundle-size.ts @@ -36,6 +36,7 @@ const cases: Record<"full" | "adapter", TestCase[]> = { code: `import arkenv from "${join(ROOT, "packages/standard/dist/valibot.js")}"; import * as v from "valibot"; console.log(arkenv(v.object({ PORT: v.string() })));`, dir: join(ROOT, "packages/standard"), tier: "primary", + fallbackBytes: 23897, // 23.3 kB }, { id: "core-arktype", @@ -44,6 +45,7 @@ const cases: Record<"full" | "adapter", TestCase[]> = { code: `import arkenv from "${join(ROOT, "packages/core/dist/index.mjs")}"; import { type } from "arktype"; console.log(arkenv(type({ PORT: "0 <= number.integer <= 65535" })));`, dir: join(ROOT, "packages/core"), tier: "secondary", + fallbackBytes: 159771, // 156.0 kB }, { id: "varlock", @@ -70,6 +72,7 @@ const cases: Record<"full" | "adapter", TestCase[]> = { code: `import arkenv from "${join(ROOT, "packages/standard/dist/index.js")}"; console.log(arkenv);`, dir: join(ROOT, "packages/standard"), tier: "primary", + fallbackBytes: 10222, // 10.0 kB }, { id: "core-only", @@ -79,6 +82,7 @@ const cases: Record<"full" | "adapter", TestCase[]> = { dir: join(ROOT, "packages/core"), tier: "secondary", external: ["arktype", "@ark/util", "@ark/schema", "arkregex"], + fallbackBytes: 6424, // 6.3 kB }, { id: "t3-only", From 1c9280b4b51f576197884ef8ab94c391c877017c Mon Sep 17 00:00:00 2001 From: Yam C Borodetsky Date: Thu, 3 Sep 2026 11:13:49 +0500 Subject: [PATCH 04/14] refactor(www): make 'Adapter only' default tab and include varlock - Switch tabs order so 'Adapter only' is first and default selected. - Rename tab label from 'Adapter engine only' to 'Adapter only'. - Include varlock (28.4 kB) in the adapter benchmark view as a competitor. - Update URL sync to use ?view=full when toggled to full view, defaulting to adapter view. - Update tests and benchmark.json artifact accordingly. --- .../page/runtime-bloat-showcase.test.tsx | 71 +++++++++-------- .../page/runtime-bloat-showcase.tsx | 20 ++--- apps/www/lib/benchmark/benchmark.json | 60 ++++++++------ scripts/benchmark-bundle-size.ts | 79 +++++++++++-------- 4 files changed, 124 insertions(+), 106 deletions(-) diff --git a/apps/www/components/page/runtime-bloat-showcase.test.tsx b/apps/www/components/page/runtime-bloat-showcase.test.tsx index 275cbdb47..444afc98b 100644 --- a/apps/www/components/page/runtime-bloat-showcase.test.tsx +++ b/apps/www/components/page/runtime-bloat-showcase.test.tsx @@ -26,75 +26,76 @@ describe("RuntimeBloatShowcase", () => { expect(group).toBeInTheDocument(); expect( - screen.getByRole("button", { name: /Full edge payload/i }), + screen.getByRole("button", { name: /Adapter only/i }), ).toBeInTheDocument(); expect( - screen.getByRole("button", { name: /Adapter engine only/i }), + screen.getByRole("button", { name: /Full edge payload/i }), ).toBeInTheDocument(); }); - it("defaults to full view showing real payload sizes", () => { + it("defaults to adapter view showing standalone adapter sizes and varlock", () => { render(); const figure = screen.getByRole("figure", { name: /production runtime bundle size comparison/i, }); - // Full view: combined ecosystem entries - expect(figure).toHaveTextContent("@arkenv/standard + Valibot"); - expect(figure).toHaveTextContent("23.3 kB"); - expect(figure).toHaveTextContent("@arkenv/core + ArkType"); - expect(figure).toHaveTextContent("156.0 kB"); - expect(figure).toHaveTextContent("@t3-oss/env-core + Zod"); - expect(figure).toHaveTextContent("325.0 kB"); + // Adapter view (default): standalone wrapper entries + varlock + expect(figure).toHaveTextContent("@arkenv/standard"); + expect(figure).toHaveTextContent("10.0 kB"); + expect(figure).toHaveTextContent("@arkenv/core"); + expect(figure).toHaveTextContent("6.3 kB"); + expect(figure).toHaveTextContent("@t3-oss/env-core"); + expect(figure).toHaveTextContent("14.2 kB"); expect(figure).toHaveTextContent("varlock"); expect(figure).toHaveTextContent("28.4 kB"); + + // Should NOT show full-payload combined names in default adapter view + expect(figure).not.toHaveTextContent("+ ArkType"); + expect(figure).not.toHaveTextContent("+ Zod"); }); - it("switches to adapter-only view when toggle is clicked and syncs URL", async () => { + it("switches to full-payload view when toggle is clicked and syncs URL", async () => { render(); - const adapterBtn = screen.getByRole("button", { - name: /Adapter engine only/i, + const fullBtn = screen.getByRole("button", { + name: /Full edge payload/i, }); - await userEvent.click(adapterBtn); + await userEvent.click(fullBtn); - expect(window.location.search).toBe("?view=adapter"); + expect(window.location.search).toBe("?view=full"); const figure = screen.getByRole("figure", { name: /production runtime bundle size comparison/i, }); - expect(figure).toHaveTextContent("@arkenv/standard"); - expect(figure).toHaveTextContent("10.0 kB"); - expect(figure).toHaveTextContent("@arkenv/core"); - expect(figure).toHaveTextContent("6.3 kB"); - expect(figure).toHaveTextContent("@t3-oss/env-core"); - expect(figure).toHaveTextContent("14.2 kB"); - - // Should NOT show full-payload combined names in adapter view - expect(figure).not.toHaveTextContent("+ ArkType"); - expect(figure).not.toHaveTextContent("+ Zod"); + expect(figure).toHaveTextContent("@arkenv/standard + Valibot"); + expect(figure).toHaveTextContent("23.3 kB"); + expect(figure).toHaveTextContent("@arkenv/core + ArkType"); + expect(figure).toHaveTextContent("156.0 kB"); + expect(figure).toHaveTextContent("@t3-oss/env-core + Zod"); + expect(figure).toHaveTextContent("325.0 kB"); + expect(figure).toHaveTextContent("varlock"); + expect(figure).toHaveTextContent("28.4 kB"); // Switching back updates URL query string to empty - const fullBtn = screen.getByRole("button", { - name: /Full edge payload/i, + const adapterBtn = screen.getByRole("button", { + name: /Adapter only/i, }); - await userEvent.click(fullBtn); + await userEvent.click(adapterBtn); expect(window.location.search).toBe(""); }); - it("initializes to adapter view when ?view=adapter is preset in URL", () => { - window.history.replaceState(null, "", "/?view=adapter"); + it("initializes to full view when ?view=full is preset in URL", () => { + window.history.replaceState(null, "", "/?view=full"); render(); const figure = screen.getByRole("figure", { name: /production runtime bundle size comparison/i, }); - expect(figure).toHaveTextContent("@arkenv/standard"); - expect(figure).toHaveTextContent("10.0 kB"); - expect(figure).toHaveTextContent("@arkenv/core"); - expect(figure).toHaveTextContent("6.3 kB"); - expect(figure).not.toHaveTextContent("+ ArkType"); + expect(figure).toHaveTextContent("@arkenv/standard + Valibot"); + expect(figure).toHaveTextContent("23.3 kB"); + expect(figure).toHaveTextContent("@arkenv/core + ArkType"); + expect(figure).toHaveTextContent("156.0 kB"); }); it("renders npmx link for the package base name", () => { diff --git a/apps/www/components/page/runtime-bloat-showcase.tsx b/apps/www/components/page/runtime-bloat-showcase.tsx index 3ddec2098..78b229357 100644 --- a/apps/www/components/page/runtime-bloat-showcase.tsx +++ b/apps/www/components/page/runtime-bloat-showcase.tsx @@ -3,38 +3,38 @@ import { useEffect, useState } from "react"; import benchmarkData from "~/lib/benchmark/benchmark.json"; -type ViewMode = "full" | "adapter"; +type ViewMode = "adapter" | "full"; const VIEW_LABELS: Record = { + adapter: "Adapter only", full: "Full edge payload", - adapter: "Adapter engine only", }; /** * "Optimized for the edge" bento cell: segmented toggle between two benchmark views. * + * - **Adapter only**: measures pure wrapper footprint with peers externalized (default view). * - **Full edge payload**: measures adapter + validator together (true V8 parse cost). - * - **Adapter engine only**: measures pure wrapper footprint with peers externalized. * - * Toggle state is stored in the URL query string (?view=adapter) so developers can + * Toggle state is stored in the URL query string (?view=full) so developers can * link directly to either view. State is synced with window.history.replaceState — * zero layout shift, zero navigation. */ export function RuntimeBloatShowcase() { - const [view, setView] = useState("full"); + const [view, setView] = useState("adapter"); - // Sync from URL on mount (client-only: SSR always renders "full" for crawlers). + // Sync from URL on mount (client-only: SSR always renders "adapter" for crawlers). useEffect(() => { const params = new URLSearchParams(window.location.search); const raw = params.get("view"); - if (raw === "adapter") setView("adapter"); + if (raw === "full") setView("full"); }, []); function switchView(next: ViewMode) { setView(next); const url = new URL(window.location.href); - if (next === "adapter") { - url.searchParams.set("view", "adapter"); + if (next === "full") { + url.searchParams.set("view", "full"); } else { url.searchParams.delete("view"); } @@ -71,7 +71,7 @@ export function RuntimeBloatShowcase() { style={{ ["--reveal-delay" as string]: "80ms" }} aria-label="Benchmark view" > - {(["full", "adapter"] as const).map((mode) => ( + {(["adapter", "full"] as const).map((mode) => (
-
- {(["adapter", "full"] as const).map((mode) => ( - - ))} -
+ ({ + id: tab.id, + label: ( + <> + + {tab.label} + + ), + }))} + /> + @@ -93,40 +106,113 @@ export function RuntimeBloatShowcase() { aria-label="Production runtime bundle size comparison" >
-
+ + +
{rows.map((item) => { - const widthPct = `${((item.bytes / maxBytes) * 100).toFixed(1)}%`; + const totalPct = `${((item.totalBytes / maxBytes) * 100).toFixed(1)}%`; + const engineShare = `${((item.engineBytes / item.totalBytes) * 100).toFixed(1)}%`; + const validatorShare = item.validatorBytes + ? `${((item.validatorBytes / item.totalBytes) * 100).toFixed(1)}%` + : "0%"; + + const a11yLabel = `${item.name} engine at ${item.engineKb} kilobytes${ + item.validatorName + ? `, plus ${item.validatorName} extension at ${item.validatorKb} kilobytes` + : "" + }, total ${item.totalKb} kilobytes`; + return (
-
+
); })} -
+
ArkEnv built via esbuild · platform: neutral · target: es2022{" "} diff --git a/apps/www/lib/benchmark/benchmark.json b/apps/www/lib/benchmark/benchmark.json index a5f924a8b..814cadbc6 100644 --- a/apps/www/lib/benchmark/benchmark.json +++ b/apps/www/lib/benchmark/benchmark.json @@ -1,70 +1,169 @@ { - "adapter": [ + "arktype": [ { - "id": "standard-only", - "name": "@arkenv/standard", - "bytes": 10222, - "kb": "10.0", - "tier": "primary", - "source": "esbuild" - }, - { - "id": "core-only", + "id": "arkenv-core", "name": "@arkenv/core", - "bytes": 6424, - "kb": "6.3", - "tier": "secondary", - "source": "esbuild" + "npmPackage": "@arkenv/core", + "engineBytes": 6424, + "engineKb": "6.3", + "engineGzipBytes": 2913, + "engineGzipKb": "2.8", + "validatorName": "ArkType", + "validatorBytes": 153347, + "validatorKb": "149.8", + "validatorGzipBytes": 46718, + "validatorGzipKb": "45.6", + "totalBytes": 159771, + "totalKb": "156.0", + "totalGzipBytes": 49631, + "totalGzipKb": "48.5", + "tier": "primary" }, { - "id": "t3-only", + "id": "t3-env", "name": "@t3-oss/env-core", - "bytes": 14541, - "kb": "14.2", + "npmPackage": "@t3-oss/env-core", + "engineBytes": 14541, + "engineKb": "14.2", + "engineGzipBytes": 4300, + "engineGzipKb": "4.2", + "validatorName": "Zod", + "validatorBytes": 318259, + "validatorKb": "310.8", + "validatorGzipBytes": 63284, + "validatorGzipKb": "61.8", + "totalBytes": 332800, + "totalKb": "325.0", + "totalGzipBytes": 67584, + "totalGzipKb": "66.0", "tier": "competitor", - "source": "bundlephobia" + "note": "requires Zod" }, { "id": "varlock", "name": "varlock", - "bytes": 29082, - "kb": "28.4", - "tier": "competitor", - "source": "bundlephobia" + "npmPackage": "varlock", + "engineBytes": 29082, + "engineKb": "28.4", + "engineGzipBytes": 9318, + "engineGzipKb": "9.1", + "totalBytes": 29082, + "totalKb": "28.4", + "totalGzipBytes": 9318, + "totalGzipKb": "9.1", + "tier": "reference", + "note": "for reference" } ], - "full": [ + "zod": [ { - "id": "standard-valibot", - "name": "@arkenv/standard + Valibot", - "bytes": 23897, - "kb": "23.3", - "tier": "primary", - "source": "esbuild" + "id": "arkenv-standard", + "name": "@arkenv/standard", + "npmPackage": "@arkenv/standard", + "engineBytes": 10222, + "engineKb": "10.0", + "engineGzipBytes": 4106, + "engineGzipKb": "4.0", + "validatorName": "Zod", + "validatorBytes": 326923, + "validatorKb": "319.3", + "validatorGzipBytes": 64576, + "validatorGzipKb": "63.1", + "totalBytes": 337145, + "totalKb": "329.2", + "totalGzipBytes": 68682, + "totalGzipKb": "67.1", + "tier": "primary" }, { - "id": "core-arktype", - "name": "@arkenv/core + ArkType", - "bytes": 159771, - "kb": "156.0", - "tier": "secondary", - "source": "esbuild" + "id": "t3-env", + "name": "@t3-oss/env-core", + "npmPackage": "@t3-oss/env-core", + "engineBytes": 14541, + "engineKb": "14.2", + "engineGzipBytes": 4300, + "engineGzipKb": "4.2", + "validatorName": "Zod", + "validatorBytes": 318259, + "validatorKb": "310.8", + "validatorGzipBytes": 63284, + "validatorGzipKb": "61.8", + "totalBytes": 332800, + "totalKb": "325.0", + "totalGzipBytes": 67584, + "totalGzipKb": "66.0", + "tier": "competitor" }, { "id": "varlock", "name": "varlock", - "bytes": 29082, - "kb": "28.4", - "tier": "competitor", - "source": "bundlephobia" + "npmPackage": "varlock", + "engineBytes": 29082, + "engineKb": "28.4", + "engineGzipBytes": 9318, + "engineGzipKb": "9.1", + "totalBytes": 29082, + "totalKb": "28.4", + "totalGzipBytes": 9318, + "totalGzipKb": "9.1", + "tier": "reference", + "note": "for reference" + } + ], + "valibot": [ + { + "id": "arkenv-standard", + "name": "@arkenv/standard", + "npmPackage": "@arkenv/standard", + "engineBytes": 10222, + "engineKb": "10.0", + "engineGzipBytes": 4106, + "engineGzipKb": "4.0", + "validatorName": "Valibot", + "validatorBytes": 13675, + "validatorKb": "13.4", + "validatorGzipBytes": 3612, + "validatorGzipKb": "3.5", + "totalBytes": 23897, + "totalKb": "23.3", + "totalGzipBytes": 7718, + "totalGzipKb": "7.5", + "tier": "primary" }, { - "id": "t3-zod", - "name": "@t3-oss/env-core + Zod", - "bytes": 332800, - "kb": "325.0", + "id": "t3-env", + "name": "@t3-oss/env-core", + "npmPackage": "@t3-oss/env-core", + "engineBytes": 14541, + "engineKb": "14.2", + "engineGzipBytes": 4300, + "engineGzipKb": "4.2", + "validatorName": "Zod", + "validatorBytes": 318259, + "validatorKb": "310.8", + "validatorGzipBytes": 63284, + "validatorGzipKb": "61.8", + "totalBytes": 332800, + "totalKb": "325.0", + "totalGzipBytes": 67584, + "totalGzipKb": "66.0", "tier": "competitor", - "source": "bundlephobia" + "note": "requires Zod" + }, + { + "id": "varlock", + "name": "varlock", + "npmPackage": "varlock", + "engineBytes": 29082, + "engineKb": "28.4", + "engineGzipBytes": 9318, + "engineGzipKb": "9.1", + "totalBytes": 29082, + "totalKb": "28.4", + "totalGzipBytes": 9318, + "totalGzipKb": "9.1", + "tier": "reference", + "note": "for reference" } ] } diff --git a/apps/www/lib/benchmark/types.ts b/apps/www/lib/benchmark/types.ts new file mode 100644 index 000000000..e5373b853 --- /dev/null +++ b/apps/www/lib/benchmark/types.ts @@ -0,0 +1,26 @@ +export type BenchmarkTier = "primary" | "competitor" | "reference"; + +export type BenchmarkRow = { + id: string; + name: string; + npmPackage: string; + engineBytes: number; + engineKb: string; + engineGzipBytes: number; + engineGzipKb: string; + validatorName?: string; + validatorBytes?: number; + validatorKb?: string; + validatorGzipBytes?: number; + validatorGzipKb?: string; + totalBytes: number; + totalKb: string; + totalGzipBytes: number; + totalGzipKb: string; + tier: BenchmarkTier; + note?: string; +}; + +export type ValidatorTab = "arktype" | "zod" | "valibot"; + +export type BenchmarkData = Record; diff --git a/apps/www/lib/twoslash-options.test.ts b/apps/www/lib/twoslash-options.test.ts index 0a1f25577..928dbc833 100644 --- a/apps/www/lib/twoslash-options.test.ts +++ b/apps/www/lib/twoslash-options.test.ts @@ -2,7 +2,7 @@ import { twoslasher } from "twoslash"; import { describe, expect, it } from "vitest"; import { arktypeTwoslashOptions } from "./twoslash-options"; -describe("arktypeTwoslashOptions", () => { +describe("arktypeTwoslashOptions", { timeout: 30000 }, () => { it("infers @arkenv/nextjs client variables as strings in docs snippets", () => { const result = twoslasher( `// @filename: env.ts diff --git a/scripts/benchmark-bundle-size.ts b/scripts/benchmark-bundle-size.ts index 547dfc6ce..586a94f81 100644 --- a/scripts/benchmark-bundle-size.ts +++ b/scripts/benchmark-bundle-size.ts @@ -1,165 +1,302 @@ import { existsSync, mkdirSync, writeFileSync } from "node:fs"; import { dirname, join, resolve } from "node:path"; import { fileURLToPath } from "node:url"; +import { gzipSync } from "node:zlib"; import { build } from "esbuild"; +import type { BenchmarkData } from "../apps/www/lib/benchmark/types"; const __dirname = dirname(fileURLToPath(import.meta.url)); const ROOT = resolve(__dirname, ".."); const OUT_PATH = join(ROOT, "apps/www/lib/benchmark/benchmark.json"); -type TestCase = { - id: string; - name: string; +type BuildTarget = { code: string; dir: string; - tier: "primary" | "secondary" | "competitor"; - fallbackBytes?: number; - /** Packages esbuild should treat as external (not bundled). */ external?: string[]; + fallbackBytes: number; + fallbackGzipBytes: number; }; -type BenchmarkResult = { - id: string; - name: string; - bytes: number; - kb: string; - tier: "primary" | "secondary" | "competitor"; - source: "esbuild" | "bundlephobia"; -}; +async function measure( + target: BuildTarget, +): Promise<{ bytes: number; gzipBytes: number }> { + try { + const res = await build({ + stdin: { contents: target.code, resolveDir: target.dir }, + bundle: true, + minify: true, + treeShaking: true, + format: "esm", + platform: "neutral", + target: "es2022", + write: false, + external: target.external ?? [], + }); + const buffer = res.outputFiles[0].contents; + return { + bytes: buffer.length, + gzipBytes: gzipSync(buffer).length, + }; + } catch (err: unknown) { + console.warn( + `Falling back to static metrics for build target: ${err instanceof Error ? err.message : String(err)}`, + ); + return { + bytes: target.fallbackBytes, + gzipBytes: target.fallbackGzipBytes, + }; + } +} -const cases: Record<"adapter" | "full", TestCase[]> = { - adapter: [ - { - id: "standard-only", - name: "@arkenv/standard", - // Adapter-only: no required runtime peers — measures pure Standard Schema wrapper - code: `import arkenv from "${join(ROOT, "packages/standard/dist/index.js")}"; console.log(arkenv);`, - dir: join(ROOT, "packages/standard"), - tier: "primary", - fallbackBytes: 10222, // 10.0 kB - }, - { - id: "core-only", - name: "@arkenv/core", - // Adapter-only: externalize arktype peer to isolate the wrapper footprint - code: `import arkenv from "${join(ROOT, "packages/core/dist/index.mjs")}"; console.log(arkenv);`, - dir: join(ROOT, "packages/core"), - tier: "secondary", - external: ["arktype", "@ark/util", "@ark/schema", "arkregex"], - fallbackBytes: 6424, // 6.3 kB - }, - { - id: "t3-only", - name: "@t3-oss/env-core", - // Adapter-only: externalize zod peer for apples-to-apples adapter comparison - code: `import { createEnv } from "@t3-oss/env-core"; console.log(createEnv);`, - dir: join(ROOT, "packages/core"), - tier: "competitor", - fallbackBytes: 14541, // 14.2 kB; not in workspace - external: ["zod"], - }, - { - id: "varlock", - name: "varlock", - // Standalone env validator (no external schema engine peer) - code: `import { env } from "varlock"; console.log(env);`, - dir: ROOT, - tier: "competitor", - fallbackBytes: 29082, // 28.4 kB — measured via bundlephobia; not in workspace - }, - ], - full: [ - { - id: "standard-valibot", - name: "@arkenv/standard + Valibot", - // Full payload: adapter + valibot bundled together (no externals) - code: `import arkenv from "${join(ROOT, "packages/standard/dist/valibot.js")}"; import * as v from "valibot"; console.log(arkenv(v.object({ PORT: v.string() })));`, - dir: join(ROOT, "packages/standard"), - tier: "primary", - fallbackBytes: 23897, // 23.3 kB - }, - { - id: "core-arktype", - name: "@arkenv/core + ArkType", - // Full payload: adapter + arktype bundled together (no externals) - code: `import arkenv from "${join(ROOT, "packages/core/dist/index.mjs")}"; import { type } from "arktype"; console.log(arkenv(type({ PORT: "0 <= number.integer <= 65535" })));`, - dir: join(ROOT, "packages/core"), - tier: "secondary", - fallbackBytes: 159771, // 156.0 kB - }, - { - id: "varlock", - name: "varlock", - code: `import { env } from "varlock"; console.log(env);`, - dir: ROOT, - tier: "competitor", - fallbackBytes: 29082, // 28.4 kB — measured via bundlephobia; not in workspace - }, - { - id: "t3-zod", - name: "@t3-oss/env-core + Zod", - code: `import { createEnv } from "@t3-oss/env-core"; import { z } from "zod"; console.log(createEnv({ server: { PORT: z.string() }, runtimeEnv: {} }));`, - dir: join(ROOT, "packages/core"), - tier: "competitor", - fallbackBytes: 332800, // ~325.0 kB (Zod 319 kB + t3-env); not in workspace - }, - ], -}; +function toKb(bytes: number): string { + return (bytes / 1024).toFixed(1); +} async function run() { - const results: Record<"adapter" | "full", BenchmarkResult[]> = { - adapter: [], - full: [], - }; + // 1. Measure Core Engine & Core + ArkType + const coreEngine = await measure({ + code: `import arkenv from "${join(ROOT, "packages/core/dist/index.mjs")}"; console.log(arkenv);`, + dir: join(ROOT, "packages/core"), + external: ["arktype", "@ark/util", "@ark/schema", "arkregex"], + fallbackBytes: 6424, + fallbackGzipBytes: 2913, + }); - for (const [mode, tests] of Object.entries(cases) as [ - "adapter" | "full", - TestCase[], - ][]) { - for (const t of tests) { - let bytes: number | undefined; - let source: "esbuild" | "bundlephobia" = "esbuild"; - try { - const res = await build({ - stdin: { contents: t.code, resolveDir: t.dir }, - bundle: true, - minify: true, - treeShaking: true, - format: "esm", - platform: "neutral", - target: "es2022", - write: false, - external: t.external ?? [], - }); - bytes = res.outputFiles[0].contents.length; - } catch (e: unknown) { - if (t.fallbackBytes) { - bytes = t.fallbackBytes; - source = "bundlephobia"; - console.warn( - `Using fallback for ${t.name}: ${e instanceof Error ? e.message : String(e)}`, - ); - } else { - throw new Error( - `Benchmark case ${t.name} failed (no fallback available): ${ - e instanceof Error ? e.message : String(e) - }`, - ); - } - } + const coreArkType = await measure({ + code: `import arkenv from "${join(ROOT, "packages/core/dist/index.mjs")}"; import { type } from "arktype"; console.log(arkenv(type({ PORT: "0 <= number.integer <= 65535" })));`, + dir: join(ROOT, "packages/core"), + fallbackBytes: 159771, + fallbackGzipBytes: 49631, + }); - if (bytes !== undefined) { - results[mode].push({ - id: t.id, - name: t.name, - bytes, - kb: (bytes / 1024).toFixed(1), - tier: t.tier, - source, - }); - } - } - } + // 2. Measure Standard Engine & Standard + Valibot / Zod + const standardEngine = await measure({ + code: `import arkenv from "${join(ROOT, "packages/standard/dist/index.js")}"; console.log(arkenv);`, + dir: join(ROOT, "packages/standard"), + fallbackBytes: 10222, + fallbackGzipBytes: 4106, + }); + + const standardValibot = await measure({ + code: `import arkenv from "${join(ROOT, "packages/standard/dist/valibot.js")}"; import * as v from "valibot"; console.log(arkenv(v.object({ PORT: v.string() })));`, + dir: join(ROOT, "packages/standard"), + fallbackBytes: 23897, + fallbackGzipBytes: 7718, + }); + + const standardZod = await measure({ + code: `import arkenv from "${join(ROOT, "packages/standard/dist/index.js")}"; import { z } from "zod"; console.log(arkenv({ PORT: z.string() }));`, + dir: join(ROOT, "packages/standard"), + fallbackBytes: 337145, + fallbackGzipBytes: 68682, + }); + + // 3. Competitor Benchmarks (fallbacks from npm/bundlephobia / isolated measurements) + const t3Engine = { + bytes: 14541, + gzipBytes: 4300, + }; + const t3Zod = { + bytes: 332800, + gzipBytes: 67584, + }; + const varlock = { + bytes: 29082, + gzipBytes: 9318, + }; + + // 4. Construct rows for each validator tab + const results: BenchmarkData = { + arktype: [ + { + id: "arkenv-core", + name: "@arkenv/core", + npmPackage: "@arkenv/core", + engineBytes: coreEngine.bytes, + engineKb: toKb(coreEngine.bytes), + engineGzipBytes: coreEngine.gzipBytes, + engineGzipKb: toKb(coreEngine.gzipBytes), + validatorName: "ArkType", + validatorBytes: Math.max(0, coreArkType.bytes - coreEngine.bytes), + validatorKb: toKb(Math.max(0, coreArkType.bytes - coreEngine.bytes)), + validatorGzipBytes: Math.max( + 0, + coreArkType.gzipBytes - coreEngine.gzipBytes, + ), + validatorGzipKb: toKb( + Math.max(0, coreArkType.gzipBytes - coreEngine.gzipBytes), + ), + totalBytes: coreArkType.bytes, + totalKb: toKb(coreArkType.bytes), + totalGzipBytes: coreArkType.gzipBytes, + totalGzipKb: toKb(coreArkType.gzipBytes), + tier: "primary", + }, + { + id: "t3-env", + name: "@t3-oss/env-core", + npmPackage: "@t3-oss/env-core", + engineBytes: t3Engine.bytes, + engineKb: toKb(t3Engine.bytes), + engineGzipBytes: t3Engine.gzipBytes, + engineGzipKb: toKb(t3Engine.gzipBytes), + validatorName: "Zod", + validatorBytes: t3Zod.bytes - t3Engine.bytes, + validatorKb: toKb(t3Zod.bytes - t3Engine.bytes), + validatorGzipBytes: t3Zod.gzipBytes - t3Engine.gzipBytes, + validatorGzipKb: toKb(t3Zod.gzipBytes - t3Engine.gzipBytes), + totalBytes: t3Zod.bytes, + totalKb: toKb(t3Zod.bytes), + totalGzipBytes: t3Zod.gzipBytes, + totalGzipKb: toKb(t3Zod.gzipBytes), + tier: "competitor", + note: "requires Zod", + }, + { + id: "varlock", + name: "varlock", + npmPackage: "varlock", + engineBytes: varlock.bytes, + engineKb: toKb(varlock.bytes), + engineGzipBytes: varlock.gzipBytes, + engineGzipKb: toKb(varlock.gzipBytes), + totalBytes: varlock.bytes, + totalKb: toKb(varlock.bytes), + totalGzipBytes: varlock.gzipBytes, + totalGzipKb: toKb(varlock.gzipBytes), + tier: "reference", + note: "for reference", + }, + ], + zod: [ + { + id: "arkenv-standard", + name: "@arkenv/standard", + npmPackage: "@arkenv/standard", + engineBytes: standardEngine.bytes, + engineKb: toKb(standardEngine.bytes), + engineGzipBytes: standardEngine.gzipBytes, + engineGzipKb: toKb(standardEngine.gzipBytes), + validatorName: "Zod", + validatorBytes: Math.max(0, standardZod.bytes - standardEngine.bytes), + validatorKb: toKb( + Math.max(0, standardZod.bytes - standardEngine.bytes), + ), + validatorGzipBytes: Math.max( + 0, + standardZod.gzipBytes - standardEngine.gzipBytes, + ), + validatorGzipKb: toKb( + Math.max(0, standardZod.gzipBytes - standardEngine.gzipBytes), + ), + totalBytes: standardZod.bytes, + totalKb: toKb(standardZod.bytes), + totalGzipBytes: standardZod.gzipBytes, + totalGzipKb: toKb(standardZod.gzipBytes), + tier: "primary", + }, + { + id: "t3-env", + name: "@t3-oss/env-core", + npmPackage: "@t3-oss/env-core", + engineBytes: t3Engine.bytes, + engineKb: toKb(t3Engine.bytes), + engineGzipBytes: t3Engine.gzipBytes, + engineGzipKb: toKb(t3Engine.gzipBytes), + validatorName: "Zod", + validatorBytes: t3Zod.bytes - t3Engine.bytes, + validatorKb: toKb(t3Zod.bytes - t3Engine.bytes), + validatorGzipBytes: t3Zod.gzipBytes - t3Engine.gzipBytes, + validatorGzipKb: toKb(t3Zod.gzipBytes - t3Engine.gzipBytes), + totalBytes: t3Zod.bytes, + totalKb: toKb(t3Zod.bytes), + totalGzipBytes: t3Zod.gzipBytes, + totalGzipKb: toKb(t3Zod.gzipBytes), + tier: "competitor", + }, + { + id: "varlock", + name: "varlock", + npmPackage: "varlock", + engineBytes: varlock.bytes, + engineKb: toKb(varlock.bytes), + engineGzipBytes: varlock.gzipBytes, + engineGzipKb: toKb(varlock.gzipBytes), + totalBytes: varlock.bytes, + totalKb: toKb(varlock.bytes), + totalGzipBytes: varlock.gzipBytes, + totalGzipKb: toKb(varlock.gzipBytes), + tier: "reference", + note: "for reference", + }, + ], + valibot: [ + { + id: "arkenv-standard", + name: "@arkenv/standard", + npmPackage: "@arkenv/standard", + engineBytes: standardEngine.bytes, + engineKb: toKb(standardEngine.bytes), + engineGzipBytes: standardEngine.gzipBytes, + engineGzipKb: toKb(standardEngine.gzipBytes), + validatorName: "Valibot", + validatorBytes: Math.max( + 0, + standardValibot.bytes - standardEngine.bytes, + ), + validatorKb: toKb( + Math.max(0, standardValibot.bytes - standardEngine.bytes), + ), + validatorGzipBytes: Math.max( + 0, + standardValibot.gzipBytes - standardEngine.gzipBytes, + ), + validatorGzipKb: toKb( + Math.max(0, standardValibot.gzipBytes - standardEngine.gzipBytes), + ), + totalBytes: standardValibot.bytes, + totalKb: toKb(standardValibot.bytes), + totalGzipBytes: standardValibot.gzipBytes, + totalGzipKb: toKb(standardValibot.gzipBytes), + tier: "primary", + }, + { + id: "t3-env", + name: "@t3-oss/env-core", + npmPackage: "@t3-oss/env-core", + engineBytes: t3Engine.bytes, + engineKb: toKb(t3Engine.bytes), + engineGzipBytes: t3Engine.gzipBytes, + engineGzipKb: toKb(t3Engine.gzipBytes), + validatorName: "Zod", + validatorBytes: t3Zod.bytes - t3Engine.bytes, + validatorKb: toKb(t3Zod.bytes - t3Engine.bytes), + validatorGzipBytes: t3Zod.gzipBytes - t3Engine.gzipBytes, + validatorGzipKb: toKb(t3Zod.gzipBytes - t3Engine.gzipBytes), + totalBytes: t3Zod.bytes, + totalKb: toKb(t3Zod.bytes), + totalGzipBytes: t3Zod.gzipBytes, + totalGzipKb: toKb(t3Zod.gzipBytes), + tier: "competitor", + note: "requires Zod", + }, + { + id: "varlock", + name: "varlock", + npmPackage: "varlock", + engineBytes: varlock.bytes, + engineKb: toKb(varlock.bytes), + engineGzipBytes: varlock.gzipBytes, + engineGzipKb: toKb(varlock.gzipBytes), + totalBytes: varlock.bytes, + totalKb: toKb(varlock.bytes), + totalGzipBytes: varlock.gzipBytes, + totalGzipKb: toKb(varlock.gzipBytes), + tier: "reference", + note: "for reference", + }, + ], + }; if (!existsSync(dirname(OUT_PATH))) { mkdirSync(dirname(OUT_PATH), { recursive: true }); From f65a80d83c1a5d29be2a00497b02f2b80dc243fc Mon Sep 17 00:00:00 2001 From: Yam C Borodetsky Date: Thu, 3 Sep 2026 15:14:32 +0500 Subject: [PATCH 09/14] docs(validators): reconcile choosing-a-validator matrix with benchmark pipeline - Add zod-mini and classic-zod (Zod 4) automated esbuild measurements in scripts/benchmark-bundle-size.ts. - Measure and record exact uncompressed bytes and gzipSync payloads in the exported benchmark.json matrix. - Reconcile comparison matrix in choosing-a-validator.mdx: Valibot (23.3 kB / 7.5 kB gzip), Zod Mini (33.6 kB / 11.5 kB gzip), ArkType (156.0 kB / 48.5 kB gzip), and Classic Zod (329.2 kB / 67.1 kB gzip). - Explicitly note in docs that Classic Zod measures the full Zod 4 export (~329.2 kB / 67.1 kB gzip) and guide edge deployments toward Zod Mini or Valibot. - Increase Twoslash compilation test timeouts for parallel CI test runs. --- .../components/page/hero-mvp-snippets.test.ts | 2 +- .../page/runtime-bloat-showcase.tsx | 8 ++-- .../docs/validators/choosing-a-validator.mdx | 12 +++-- apps/www/lib/benchmark/benchmark.json | 40 +++++++++++++++- apps/www/lib/benchmark/types.ts | 22 ++++++++- apps/www/lib/twoslash-options.test.ts | 4 +- scripts/benchmark-bundle-size.ts | 46 +++++++++++++++++++ 7 files changed, 123 insertions(+), 11 deletions(-) diff --git a/apps/www/components/page/hero-mvp-snippets.test.ts b/apps/www/components/page/hero-mvp-snippets.test.ts index 81e8332c6..45e38e38e 100644 --- a/apps/www/components/page/hero-mvp-snippets.test.ts +++ b/apps/www/components/page/hero-mvp-snippets.test.ts @@ -8,7 +8,7 @@ import { import { heroTwoslashOptions } from "./hero-mvp-twoslash-options"; describe("hero MVP snippets", () => { - it("typecheck in Twoslash", { timeout: 30_000 }, () => { + it("typecheck in Twoslash", { timeout: 60_000 }, () => { const slashers = { arktype: createTwoslasher(heroTwoslashOptions("arktype").twoslashOptions), standard: createTwoslasher( diff --git a/apps/www/components/page/runtime-bloat-showcase.tsx b/apps/www/components/page/runtime-bloat-showcase.tsx index 3742aec18..5654ae0c5 100644 --- a/apps/www/components/page/runtime-bloat-showcase.tsx +++ b/apps/www/components/page/runtime-bloat-showcase.tsx @@ -49,9 +49,11 @@ export function RuntimeBloatShowcase() { const rows = benchmarkData[validator]; // Globally consistent scale across all tabs (~337 kB max from T3 Env / Zod) const maxBytes = Math.max( - ...Object.values(benchmarkData).flatMap((list) => - list.map((r) => r.totalBytes), - ), + ...[ + benchmarkData.arktype, + benchmarkData.zod, + benchmarkData.valibot, + ].flatMap((list) => list.map((r) => r.totalBytes)), ); return ( diff --git a/apps/www/content/docs/validators/choosing-a-validator.mdx b/apps/www/content/docs/validators/choosing-a-validator.mdx index 199dd0de7..120fb725a 100644 --- a/apps/www/content/docs/validators/choosing-a-validator.mdx +++ b/apps/www/content/docs/validators/choosing-a-validator.mdx @@ -12,15 +12,16 @@ The difference lies in bundle weight, syntax, and ecosystem integration. ## Comparison matrix -All figures represent minified, uncompressed JavaScript evaluated during V8 isolate cold starts, -measured using esbuild (`platform: neutral`, `target: es2022`). +All figures represent minified JavaScript evaluated during V8 isolate cold starts, +measured using esbuild (`platform: neutral`, `target: es2022`). Both uncompressed bytes and gzip footprints are measured and generated by `scripts/benchmark-bundle-size.ts`. | Validator | Engine / Subpath | Total edge payload | Min + gzip | Primary strength | | :-------------- | :-------------------------- | :----------------- | :--------- | :------------------------------------------------------------------ | | **Valibot** | `@arkenv/standard/valibot` | **23.3 kB** | 7.5 kB | Smallest edge footprint; modular functional tree-shaking | | **Zod Mini** | `@arkenv/standard/zod-mini` | **33.6 kB** | 11.5 kB | \~90% smaller than classic Zod; familiar syntax | | **ArkType** | `@arkenv/core` | **156.0 kB** | 48.5 kB | TypeScript-native DSL strings; built-in keywords; zero dependencies | -| **Classic Zod** | `@arkenv/standard` | **329.3 kB** | 67.1 kB | Drop-in compatibility for existing Zod schemas | +| **Classic Zod** | `@arkenv/standard` | **329.2 kB** | 67.1 kB | Drop-in compatibility for existing Zod schemas (Zod 4) | + ## Strict edge constraints: Valibot @@ -140,6 +141,11 @@ export const env = arkenv({ Zod 4.2+ embeds Standard JSON Schema metadata, enabling automatic string-to-type coercion without manual `toJsonSchema` callbacks or `z.coerce`. +Zod 4's default export includes its complete schema library, evaluating at **329.2 kB** uncompressed +(**67.1 kB** gzip). For long-lived Node.js servers, cold-start parse size is negligible. When targeting +serverless or edge functions, switch to [Zod Mini](#seamless-migrations-with-massive-savings-zod-mini) +(**33.6 kB**) or [Valibot](#strict-edge-constraints-valibot) (**23.3 kB**) to minimize cold-start latency. + ## Next steps diff --git a/apps/www/lib/benchmark/benchmark.json b/apps/www/lib/benchmark/benchmark.json index 814cadbc6..72bd50661 100644 --- a/apps/www/lib/benchmark/benchmark.json +++ b/apps/www/lib/benchmark/benchmark.json @@ -165,5 +165,43 @@ "tier": "reference", "note": "for reference" } - ] + ], + "matrix": { + "valibot": { + "engine": "@arkenv/standard/valibot", + "subpath": "@arkenv/standard/valibot", + "totalBytes": 23897, + "totalKb": "23.3", + "gzipBytes": 7718, + "gzipKb": "7.5", + "description": "Smallest edge footprint; modular functional tree-shaking" + }, + "zodMini": { + "engine": "@arkenv/standard/zod-mini", + "subpath": "@arkenv/standard/zod-mini", + "totalBytes": 34456, + "totalKb": "33.6", + "gzipBytes": 11735, + "gzipKb": "11.5", + "description": "~90% smaller than classic Zod; familiar syntax" + }, + "arktype": { + "engine": "@arkenv/core", + "subpath": "@arkenv/core", + "totalBytes": 159771, + "totalKb": "156.0", + "gzipBytes": 49631, + "gzipKb": "48.5", + "description": "TypeScript-native DSL strings; built-in keywords; zero dependencies" + }, + "classicZod": { + "engine": "@arkenv/standard", + "subpath": "@arkenv/standard", + "totalBytes": 337145, + "totalKb": "329.2", + "gzipBytes": 68682, + "gzipKb": "67.1", + "description": "Drop-in compatibility for existing Zod schemas (Zod 4)" + } + } } diff --git a/apps/www/lib/benchmark/types.ts b/apps/www/lib/benchmark/types.ts index e5373b853..15078b4e4 100644 --- a/apps/www/lib/benchmark/types.ts +++ b/apps/www/lib/benchmark/types.ts @@ -21,6 +21,26 @@ export type BenchmarkRow = { note?: string; }; +export type BenchmarkMatrixItem = { + engine: string; + subpath: string; + totalBytes: number; + totalKb: string; + gzipBytes: number; + gzipKb: string; + description: string; +}; + export type ValidatorTab = "arktype" | "zod" | "valibot"; -export type BenchmarkData = Record; +export type BenchmarkData = { + arktype: BenchmarkRow[]; + zod: BenchmarkRow[]; + valibot: BenchmarkRow[]; + matrix: { + valibot: BenchmarkMatrixItem; + zodMini: BenchmarkMatrixItem; + arktype: BenchmarkMatrixItem; + classicZod: BenchmarkMatrixItem; + }; +}; diff --git a/apps/www/lib/twoslash-options.test.ts b/apps/www/lib/twoslash-options.test.ts index 928dbc833..9af29c953 100644 --- a/apps/www/lib/twoslash-options.test.ts +++ b/apps/www/lib/twoslash-options.test.ts @@ -68,7 +68,7 @@ const apiUrl = env.NEXT_PUBLIC_API_URL; }); it("resolves flat @arkenv/nextjs env without TS2307 errors", { - timeout: 15_000, + timeout: 45_000, }, () => { const resultNextjs = twoslasher( `// @errors: 2339 @@ -95,7 +95,7 @@ const db = env.DATABASE_URL; }); it("resolves flat @arkenv/nuxt env without TS2307 errors", { - timeout: 15_000, + timeout: 45_000, }, () => { const resultNuxt = twoslasher( `// @errors: 2339 diff --git a/scripts/benchmark-bundle-size.ts b/scripts/benchmark-bundle-size.ts index 586a94f81..054b0a79a 100644 --- a/scripts/benchmark-bundle-size.ts +++ b/scripts/benchmark-bundle-size.ts @@ -91,6 +91,13 @@ async function run() { fallbackGzipBytes: 68682, }); + const standardZodMini = await measure({ + code: `import arkenv from "${join(ROOT, "packages/standard/dist/zod-mini.js")}"; import * as z from "zod/mini"; console.log(arkenv(z.object({ PORT: z.string() })));`, + dir: join(ROOT, "packages/standard"), + fallbackBytes: 34456, + fallbackGzipBytes: 11735, + }); + // 3. Competitor Benchmarks (fallbacks from npm/bundlephobia / isolated measurements) const t3Engine = { bytes: 14541, @@ -296,6 +303,45 @@ async function run() { note: "for reference", }, ], + matrix: { + valibot: { + engine: "@arkenv/standard/valibot", + subpath: "@arkenv/standard/valibot", + totalBytes: standardValibot.bytes, + totalKb: toKb(standardValibot.bytes), + gzipBytes: standardValibot.gzipBytes, + gzipKb: toKb(standardValibot.gzipBytes), + description: "Smallest edge footprint; modular functional tree-shaking", + }, + zodMini: { + engine: "@arkenv/standard/zod-mini", + subpath: "@arkenv/standard/zod-mini", + totalBytes: standardZodMini.bytes, + totalKb: toKb(standardZodMini.bytes), + gzipBytes: standardZodMini.gzipBytes, + gzipKb: toKb(standardZodMini.gzipBytes), + description: "~90% smaller than classic Zod; familiar syntax", + }, + arktype: { + engine: "@arkenv/core", + subpath: "@arkenv/core", + totalBytes: coreArkType.bytes, + totalKb: toKb(coreArkType.bytes), + gzipBytes: coreArkType.gzipBytes, + gzipKb: toKb(coreArkType.gzipBytes), + description: + "TypeScript-native DSL strings; built-in keywords; zero dependencies", + }, + classicZod: { + engine: "@arkenv/standard", + subpath: "@arkenv/standard", + totalBytes: standardZod.bytes, + totalKb: toKb(standardZod.bytes), + gzipBytes: standardZod.gzipBytes, + gzipKb: toKb(standardZod.gzipBytes), + description: "Drop-in compatibility for existing Zod schemas (Zod 4)", + }, + }, }; if (!existsSync(dirname(OUT_PATH))) { From 50c8e0d83f31f8c706c2206f4b5426882f9057c4 Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Thu, 3 Sep 2026 10:15:31 +0000 Subject: [PATCH 10/14] [autofix.ci] apply automated fixes --- apps/www/content/docs/validators/choosing-a-validator.mdx | 1 - 1 file changed, 1 deletion(-) diff --git a/apps/www/content/docs/validators/choosing-a-validator.mdx b/apps/www/content/docs/validators/choosing-a-validator.mdx index 120fb725a..d68d1ace8 100644 --- a/apps/www/content/docs/validators/choosing-a-validator.mdx +++ b/apps/www/content/docs/validators/choosing-a-validator.mdx @@ -22,7 +22,6 @@ measured using esbuild (`platform: neutral`, `target: es2022`). Both uncompresse | **ArkType** | `@arkenv/core` | **156.0 kB** | 48.5 kB | TypeScript-native DSL strings; built-in keywords; zero dependencies | | **Classic Zod** | `@arkenv/standard` | **329.2 kB** | 67.1 kB | Drop-in compatibility for existing Zod schemas (Zod 4) | - ## Strict edge constraints: Valibot Choose **Valibot** via `@arkenv/standard/valibot` when minimizing cold-start latency on edge runtimes From e7be49cb42e9330406daf581a3f76f4159fb818c Mon Sep 17 00:00:00 2001 From: Yam C Borodetsky Date: Thu, 3 Sep 2026 15:17:01 +0500 Subject: [PATCH 11/14] feat(benchmark): show ArkType and Valibot extensions for T3 Env via Standard Schema - T3 Env supports Standard Schema, so on the ArkType tab it pairs with ArkType (14.2 kB engine + 149.8 kB validator = 164.0 kB). - On the Valibot tab, T3 Env pairs with Valibot (14.2 kB engine + 13.4 kB validator = 27.6 kB). - Remove 'requires Zod' annotations and accurately represent T3 Env's Standard Schema flexibility across tabs. - ArkEnv retains a ~2x engine advantage on ArkType (6.3 kB vs 14.2 kB) and a 4.2 kB engine advantage on Valibot (10.0 kB vs 14.2 kB). --- .../page/runtime-bloat-showcase.test.tsx | 13 ++-- apps/www/lib/benchmark/benchmark.json | 42 ++++++----- scripts/benchmark-bundle-size.ts | 71 +++++++++++++------ 3 files changed, 77 insertions(+), 49 deletions(-) diff --git a/apps/www/components/page/runtime-bloat-showcase.test.tsx b/apps/www/components/page/runtime-bloat-showcase.test.tsx index d8459ed76..9dd3c004b 100644 --- a/apps/www/components/page/runtime-bloat-showcase.test.tsx +++ b/apps/www/components/page/runtime-bloat-showcase.test.tsx @@ -61,11 +61,10 @@ describe("RuntimeBloatShowcase", () => { expect(figure).toHaveTextContent("156.0 kB"); expect(figure).toHaveTextContent("(6.3 + 149.8)"); - // T3 Env bar with Zod requirement note + // T3 Env bar with ArkType extension expect(figure).toHaveTextContent("@t3-oss/env-core"); - expect(figure).toHaveTextContent("+ Zod"); - expect(figure).toHaveTextContent("(requires Zod)"); - expect(figure).toHaveTextContent("325.0 kB"); + expect(figure).toHaveTextContent("+ ArkType"); + expect(figure).toHaveTextContent("164.0 kB"); // Varlock reference bar expect(figure).toHaveTextContent("varlock"); @@ -90,10 +89,10 @@ describe("RuntimeBloatShowcase", () => { expect(figure).toHaveTextContent("+ Valibot"); expect(figure).toHaveTextContent("23.3 kB"); - // T3 Env remains 325.0 kB with (requires Zod) note + // T3 Env shows Valibot extension with 27.6 kB total expect(figure).toHaveTextContent("@t3-oss/env-core"); - expect(figure).toHaveTextContent("(requires Zod)"); - expect(figure).toHaveTextContent("325.0 kB"); + expect(figure).toHaveTextContent("+ Valibot"); + expect(figure).toHaveTextContent("27.6 kB"); }); it("switches to Zod tab and back to ArkType clearing query string", async () => { diff --git a/apps/www/lib/benchmark/benchmark.json b/apps/www/lib/benchmark/benchmark.json index 72bd50661..a9fd13811 100644 --- a/apps/www/lib/benchmark/benchmark.json +++ b/apps/www/lib/benchmark/benchmark.json @@ -27,17 +27,16 @@ "engineKb": "14.2", "engineGzipBytes": 4300, "engineGzipKb": "4.2", - "validatorName": "Zod", - "validatorBytes": 318259, - "validatorKb": "310.8", - "validatorGzipBytes": 63284, - "validatorGzipKb": "61.8", - "totalBytes": 332800, - "totalKb": "325.0", - "totalGzipBytes": 67584, - "totalGzipKb": "66.0", - "tier": "competitor", - "note": "requires Zod" + "validatorName": "ArkType", + "validatorBytes": 153347, + "validatorKb": "149.8", + "validatorGzipBytes": 46718, + "validatorGzipKb": "45.6", + "totalBytes": 167888, + "totalKb": "164.0", + "totalGzipBytes": 51018, + "totalGzipKb": "49.8", + "tier": "competitor" }, { "id": "varlock", @@ -138,17 +137,16 @@ "engineKb": "14.2", "engineGzipBytes": 4300, "engineGzipKb": "4.2", - "validatorName": "Zod", - "validatorBytes": 318259, - "validatorKb": "310.8", - "validatorGzipBytes": 63284, - "validatorGzipKb": "61.8", - "totalBytes": 332800, - "totalKb": "325.0", - "totalGzipBytes": 67584, - "totalGzipKb": "66.0", - "tier": "competitor", - "note": "requires Zod" + "validatorName": "Valibot", + "validatorBytes": 13675, + "validatorKb": "13.4", + "validatorGzipBytes": 3612, + "validatorGzipKb": "3.5", + "totalBytes": 28216, + "totalKb": "27.6", + "totalGzipBytes": 7912, + "totalGzipKb": "7.7", + "tier": "competitor" }, { "id": "varlock", diff --git a/scripts/benchmark-bundle-size.ts b/scripts/benchmark-bundle-size.ts index 054b0a79a..b7c9dbc41 100644 --- a/scripts/benchmark-bundle-size.ts +++ b/scripts/benchmark-bundle-size.ts @@ -147,17 +147,29 @@ async function run() { engineKb: toKb(t3Engine.bytes), engineGzipBytes: t3Engine.gzipBytes, engineGzipKb: toKb(t3Engine.gzipBytes), - validatorName: "Zod", - validatorBytes: t3Zod.bytes - t3Engine.bytes, - validatorKb: toKb(t3Zod.bytes - t3Engine.bytes), - validatorGzipBytes: t3Zod.gzipBytes - t3Engine.gzipBytes, - validatorGzipKb: toKb(t3Zod.gzipBytes - t3Engine.gzipBytes), - totalBytes: t3Zod.bytes, - totalKb: toKb(t3Zod.bytes), - totalGzipBytes: t3Zod.gzipBytes, - totalGzipKb: toKb(t3Zod.gzipBytes), + validatorName: "ArkType", + validatorBytes: Math.max(0, coreArkType.bytes - coreEngine.bytes), + validatorKb: toKb(Math.max(0, coreArkType.bytes - coreEngine.bytes)), + validatorGzipBytes: Math.max( + 0, + coreArkType.gzipBytes - coreEngine.gzipBytes, + ), + validatorGzipKb: toKb( + Math.max(0, coreArkType.gzipBytes - coreEngine.gzipBytes), + ), + totalBytes: + t3Engine.bytes + Math.max(0, coreArkType.bytes - coreEngine.bytes), + totalKb: toKb( + t3Engine.bytes + Math.max(0, coreArkType.bytes - coreEngine.bytes), + ), + totalGzipBytes: + t3Engine.gzipBytes + + Math.max(0, coreArkType.gzipBytes - coreEngine.gzipBytes), + totalGzipKb: toKb( + t3Engine.gzipBytes + + Math.max(0, coreArkType.gzipBytes - coreEngine.gzipBytes), + ), tier: "competitor", - note: "requires Zod", }, { id: "varlock", @@ -275,17 +287,36 @@ async function run() { engineKb: toKb(t3Engine.bytes), engineGzipBytes: t3Engine.gzipBytes, engineGzipKb: toKb(t3Engine.gzipBytes), - validatorName: "Zod", - validatorBytes: t3Zod.bytes - t3Engine.bytes, - validatorKb: toKb(t3Zod.bytes - t3Engine.bytes), - validatorGzipBytes: t3Zod.gzipBytes - t3Engine.gzipBytes, - validatorGzipKb: toKb(t3Zod.gzipBytes - t3Engine.gzipBytes), - totalBytes: t3Zod.bytes, - totalKb: toKb(t3Zod.bytes), - totalGzipBytes: t3Zod.gzipBytes, - totalGzipKb: toKb(t3Zod.gzipBytes), + validatorName: "Valibot", + validatorBytes: Math.max( + 0, + standardValibot.bytes - standardEngine.bytes, + ), + validatorKb: toKb( + Math.max(0, standardValibot.bytes - standardEngine.bytes), + ), + validatorGzipBytes: Math.max( + 0, + standardValibot.gzipBytes - standardEngine.gzipBytes, + ), + validatorGzipKb: toKb( + Math.max(0, standardValibot.gzipBytes - standardEngine.gzipBytes), + ), + totalBytes: + t3Engine.bytes + + Math.max(0, standardValibot.bytes - standardEngine.bytes), + totalKb: toKb( + t3Engine.bytes + + Math.max(0, standardValibot.bytes - standardEngine.bytes), + ), + totalGzipBytes: + t3Engine.gzipBytes + + Math.max(0, standardValibot.gzipBytes - standardEngine.gzipBytes), + totalGzipKb: toKb( + t3Engine.gzipBytes + + Math.max(0, standardValibot.gzipBytes - standardEngine.gzipBytes), + ), tier: "competitor", - note: "requires Zod", }, { id: "varlock", From c6ef732d45c7e37da1bc5492b3850595e6f9f1ef Mon Sep 17 00:00:00 2001 From: Yam C Borodetsky Date: Thu, 3 Sep 2026 15:19:05 +0500 Subject: [PATCH 12/14] fix(benchmark): use shared Zod extension footprint across all engines on zod tab - Previously, T3 Env on the Zod tab computed its validator extension by subtracting from an old hardcoded t3Zod fallback (332.8 kB), yielding 310.8 kB, while ArkEnv Standard computed against live zod@4.4.1 (319.3 kB). - Align T3 Env on the Zod tab to use the exact same Zod schema extension footprint (319.3 kB) as ArkEnv Standard, matching the ArkType and Valibot tabs. - Both engines now consistently evaluate against the exact same validator schema weight on each respective tab, isolating the 4.2 kB engine efficiency advantage (10.0 kB vs 14.2 kB). --- .../page/runtime-bloat-showcase.test.tsx | 3 ++ apps/www/lib/benchmark/benchmark.json | 16 ++++---- scripts/benchmark-bundle-size.ts | 37 +++++++++++++------ 3 files changed, 36 insertions(+), 20 deletions(-) diff --git a/apps/www/components/page/runtime-bloat-showcase.test.tsx b/apps/www/components/page/runtime-bloat-showcase.test.tsx index 9dd3c004b..9cc5b4713 100644 --- a/apps/www/components/page/runtime-bloat-showcase.test.tsx +++ b/apps/www/components/page/runtime-bloat-showcase.test.tsx @@ -110,6 +110,9 @@ describe("RuntimeBloatShowcase", () => { expect(figure).toHaveTextContent("+ Zod"); expect(figure).toHaveTextContent("329.2 kB"); + expect(figure).toHaveTextContent("@t3-oss/env-core"); + expect(figure).toHaveTextContent("333.5 kB"); + // Switching back to default ArkType clears ?validator param const arkTypeTab = screen.getByRole("tab", { name: /ArkType/i }); await userEvent.click(arkTypeTab); diff --git a/apps/www/lib/benchmark/benchmark.json b/apps/www/lib/benchmark/benchmark.json index a9fd13811..0e9d2ae88 100644 --- a/apps/www/lib/benchmark/benchmark.json +++ b/apps/www/lib/benchmark/benchmark.json @@ -83,14 +83,14 @@ "engineGzipBytes": 4300, "engineGzipKb": "4.2", "validatorName": "Zod", - "validatorBytes": 318259, - "validatorKb": "310.8", - "validatorGzipBytes": 63284, - "validatorGzipKb": "61.8", - "totalBytes": 332800, - "totalKb": "325.0", - "totalGzipBytes": 67584, - "totalGzipKb": "66.0", + "validatorBytes": 326923, + "validatorKb": "319.3", + "validatorGzipBytes": 64576, + "validatorGzipKb": "63.1", + "totalBytes": 341464, + "totalKb": "333.5", + "totalGzipBytes": 68876, + "totalGzipKb": "67.3", "tier": "competitor" }, { diff --git a/scripts/benchmark-bundle-size.ts b/scripts/benchmark-bundle-size.ts index b7c9dbc41..944a47f2a 100644 --- a/scripts/benchmark-bundle-size.ts +++ b/scripts/benchmark-bundle-size.ts @@ -103,10 +103,6 @@ async function run() { bytes: 14541, gzipBytes: 4300, }; - const t3Zod = { - bytes: 332800, - gzipBytes: 67584, - }; const varlock = { bytes: 29082, gzipBytes: 9318, @@ -223,14 +219,31 @@ async function run() { engineGzipBytes: t3Engine.gzipBytes, engineGzipKb: toKb(t3Engine.gzipBytes), validatorName: "Zod", - validatorBytes: t3Zod.bytes - t3Engine.bytes, - validatorKb: toKb(t3Zod.bytes - t3Engine.bytes), - validatorGzipBytes: t3Zod.gzipBytes - t3Engine.gzipBytes, - validatorGzipKb: toKb(t3Zod.gzipBytes - t3Engine.gzipBytes), - totalBytes: t3Zod.bytes, - totalKb: toKb(t3Zod.bytes), - totalGzipBytes: t3Zod.gzipBytes, - totalGzipKb: toKb(t3Zod.gzipBytes), + validatorBytes: Math.max(0, standardZod.bytes - standardEngine.bytes), + validatorKb: toKb( + Math.max(0, standardZod.bytes - standardEngine.bytes), + ), + validatorGzipBytes: Math.max( + 0, + standardZod.gzipBytes - standardEngine.gzipBytes, + ), + validatorGzipKb: toKb( + Math.max(0, standardZod.gzipBytes - standardEngine.gzipBytes), + ), + totalBytes: + t3Engine.bytes + + Math.max(0, standardZod.bytes - standardEngine.bytes), + totalKb: toKb( + t3Engine.bytes + + Math.max(0, standardZod.bytes - standardEngine.bytes), + ), + totalGzipBytes: + t3Engine.gzipBytes + + Math.max(0, standardZod.gzipBytes - standardEngine.gzipBytes), + totalGzipKb: toKb( + t3Engine.gzipBytes + + Math.max(0, standardZod.gzipBytes - standardEngine.gzipBytes), + ), tier: "competitor", }, { From 9a5fa5fdfbe3e4b395386a53eb90beb5dac35857 Mon Sep 17 00:00:00 2001 From: Yam C Borodetsky Date: Thu, 3 Sep 2026 15:24:02 +0500 Subject: [PATCH 13/14] fix(www): align telemetry package name and validator extension on shared baseline --- apps/www/app/(home)/aurora.css | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/apps/www/app/(home)/aurora.css b/apps/www/app/(home)/aurora.css index bb1f1d95f..46431d34c 100644 --- a/apps/www/app/(home)/aurora.css +++ b/apps/www/app/(home)/aurora.css @@ -1702,7 +1702,7 @@ .home-aurora__telemetry-meta { display: inline-flex; - align-items: center; + align-items: baseline; gap: 0.375rem; min-width: 0; pointer-events: auto; @@ -1711,7 +1711,7 @@ .home-aurora__telemetry-link { text-decoration: none; display: inline-flex; - align-items: center; + align-items: baseline; color: inherit; cursor: pointer; } @@ -1720,6 +1720,7 @@ font-family: var(--font-mono, ui-monospace, monospace); font-size: 0.8125rem; font-weight: 500; + line-height: normal; color: var(--color-ink); transition: color var(--dur-micro) var(--ease-out); } @@ -1732,15 +1733,17 @@ .home-aurora__telemetry-ext { font-family: var(--font-sans, ui-sans-serif, system-ui, sans-serif); - font-size: 0.75rem; + font-size: 0.8125rem; font-weight: 500; + line-height: normal; color: var(--color-ink-2); white-space: nowrap; } .home-aurora__telemetry-note { font-family: var(--font-sans, ui-sans-serif, system-ui, sans-serif); - font-size: 0.6875rem; + font-size: 0.75rem; + line-height: normal; color: var(--color-muted); white-space: nowrap; } From 5ef6de7674327a037fb1d10ada1c7e40f5d9a6fd Mon Sep 17 00:00:00 2001 From: Yam C Borodetsky Date: Thu, 3 Sep 2026 15:41:14 +0500 Subject: [PATCH 14/14] fix(benchmark): pin T3 Env to Zod across all tabs with (requires Zod) badge Revert T3 Env from Standard Schema per-tab adapters back to its real-world Zod-locked footprint (325.0 kB). Showing T3 + ArkType or T3 + Valibot inadvertently erased the vendor lock-in that makes ArkEnv's modular adapter necessary. Pinning T3 to Zod on every tab lets the 23.3 kB vs 325.0 kB contrast on the Valibot tab speak for itself. Theoretical Standard Schema figures are shelved as comments in the benchmark script for future reference. --- .../page/runtime-bloat-showcase.test.tsx | 15 +-- apps/www/lib/benchmark/benchmark.json | 58 ++++----- scripts/benchmark-bundle-size.ts | 114 ++++++------------ 3 files changed, 76 insertions(+), 111 deletions(-) diff --git a/apps/www/components/page/runtime-bloat-showcase.test.tsx b/apps/www/components/page/runtime-bloat-showcase.test.tsx index 9cc5b4713..2e42730ca 100644 --- a/apps/www/components/page/runtime-bloat-showcase.test.tsx +++ b/apps/www/components/page/runtime-bloat-showcase.test.tsx @@ -61,10 +61,11 @@ describe("RuntimeBloatShowcase", () => { expect(figure).toHaveTextContent("156.0 kB"); expect(figure).toHaveTextContent("(6.3 + 149.8)"); - // T3 Env bar with ArkType extension + // T3 Env bar with Zod requirement note expect(figure).toHaveTextContent("@t3-oss/env-core"); - expect(figure).toHaveTextContent("+ ArkType"); - expect(figure).toHaveTextContent("164.0 kB"); + expect(figure).toHaveTextContent("+ Zod"); + expect(figure).toHaveTextContent("(requires Zod)"); + expect(figure).toHaveTextContent("325.0 kB"); // Varlock reference bar expect(figure).toHaveTextContent("varlock"); @@ -89,10 +90,10 @@ describe("RuntimeBloatShowcase", () => { expect(figure).toHaveTextContent("+ Valibot"); expect(figure).toHaveTextContent("23.3 kB"); - // T3 Env shows Valibot extension with 27.6 kB total + // T3 Env remains 325.0 kB with (requires Zod) note expect(figure).toHaveTextContent("@t3-oss/env-core"); - expect(figure).toHaveTextContent("+ Valibot"); - expect(figure).toHaveTextContent("27.6 kB"); + expect(figure).toHaveTextContent("(requires Zod)"); + expect(figure).toHaveTextContent("325.0 kB"); }); it("switches to Zod tab and back to ArkType clearing query string", async () => { @@ -111,7 +112,7 @@ describe("RuntimeBloatShowcase", () => { expect(figure).toHaveTextContent("329.2 kB"); expect(figure).toHaveTextContent("@t3-oss/env-core"); - expect(figure).toHaveTextContent("333.5 kB"); + expect(figure).toHaveTextContent("325.0 kB"); // Switching back to default ArkType clears ?validator param const arkTypeTab = screen.getByRole("tab", { name: /ArkType/i }); diff --git a/apps/www/lib/benchmark/benchmark.json b/apps/www/lib/benchmark/benchmark.json index 0e9d2ae88..72bd50661 100644 --- a/apps/www/lib/benchmark/benchmark.json +++ b/apps/www/lib/benchmark/benchmark.json @@ -27,16 +27,17 @@ "engineKb": "14.2", "engineGzipBytes": 4300, "engineGzipKb": "4.2", - "validatorName": "ArkType", - "validatorBytes": 153347, - "validatorKb": "149.8", - "validatorGzipBytes": 46718, - "validatorGzipKb": "45.6", - "totalBytes": 167888, - "totalKb": "164.0", - "totalGzipBytes": 51018, - "totalGzipKb": "49.8", - "tier": "competitor" + "validatorName": "Zod", + "validatorBytes": 318259, + "validatorKb": "310.8", + "validatorGzipBytes": 63284, + "validatorGzipKb": "61.8", + "totalBytes": 332800, + "totalKb": "325.0", + "totalGzipBytes": 67584, + "totalGzipKb": "66.0", + "tier": "competitor", + "note": "requires Zod" }, { "id": "varlock", @@ -83,14 +84,14 @@ "engineGzipBytes": 4300, "engineGzipKb": "4.2", "validatorName": "Zod", - "validatorBytes": 326923, - "validatorKb": "319.3", - "validatorGzipBytes": 64576, - "validatorGzipKb": "63.1", - "totalBytes": 341464, - "totalKb": "333.5", - "totalGzipBytes": 68876, - "totalGzipKb": "67.3", + "validatorBytes": 318259, + "validatorKb": "310.8", + "validatorGzipBytes": 63284, + "validatorGzipKb": "61.8", + "totalBytes": 332800, + "totalKb": "325.0", + "totalGzipBytes": 67584, + "totalGzipKb": "66.0", "tier": "competitor" }, { @@ -137,16 +138,17 @@ "engineKb": "14.2", "engineGzipBytes": 4300, "engineGzipKb": "4.2", - "validatorName": "Valibot", - "validatorBytes": 13675, - "validatorKb": "13.4", - "validatorGzipBytes": 3612, - "validatorGzipKb": "3.5", - "totalBytes": 28216, - "totalKb": "27.6", - "totalGzipBytes": 7912, - "totalGzipKb": "7.7", - "tier": "competitor" + "validatorName": "Zod", + "validatorBytes": 318259, + "validatorKb": "310.8", + "validatorGzipBytes": 63284, + "validatorGzipKb": "61.8", + "totalBytes": 332800, + "totalKb": "325.0", + "totalGzipBytes": 67584, + "totalGzipKb": "66.0", + "tier": "competitor", + "note": "requires Zod" }, { "id": "varlock", diff --git a/scripts/benchmark-bundle-size.ts b/scripts/benchmark-bundle-size.ts index 944a47f2a..82c08d84c 100644 --- a/scripts/benchmark-bundle-size.ts +++ b/scripts/benchmark-bundle-size.ts @@ -103,6 +103,16 @@ async function run() { bytes: 14541, gzipBytes: 4300, }; + // In practice, developers using @t3-oss/env-core are locked into the monolithic Zod ecosystem (325.0 kB). + // While theoretical Standard Schema adapters could exist: + // - Shelved theoretical ArkType + T3: 164.0 kB (14.2 kB engine + 149.8 kB ArkType) + // - Shelved theoretical Valibot + T3: 27.6 kB (14.2 kB engine + 13.4 kB Valibot) + // We shelve those theoretical combinations: real-world T3 Env installations pay the full Zod tax. + // Pinning T3 Env to Zod across all tabs with "(requires Zod)" accurately reflects the baseline users migrate from. + const t3Zod = { + bytes: 332800, + gzipBytes: 67584, + }; const varlock = { bytes: 29082, gzipBytes: 9318, @@ -143,29 +153,17 @@ async function run() { engineKb: toKb(t3Engine.bytes), engineGzipBytes: t3Engine.gzipBytes, engineGzipKb: toKb(t3Engine.gzipBytes), - validatorName: "ArkType", - validatorBytes: Math.max(0, coreArkType.bytes - coreEngine.bytes), - validatorKb: toKb(Math.max(0, coreArkType.bytes - coreEngine.bytes)), - validatorGzipBytes: Math.max( - 0, - coreArkType.gzipBytes - coreEngine.gzipBytes, - ), - validatorGzipKb: toKb( - Math.max(0, coreArkType.gzipBytes - coreEngine.gzipBytes), - ), - totalBytes: - t3Engine.bytes + Math.max(0, coreArkType.bytes - coreEngine.bytes), - totalKb: toKb( - t3Engine.bytes + Math.max(0, coreArkType.bytes - coreEngine.bytes), - ), - totalGzipBytes: - t3Engine.gzipBytes + - Math.max(0, coreArkType.gzipBytes - coreEngine.gzipBytes), - totalGzipKb: toKb( - t3Engine.gzipBytes + - Math.max(0, coreArkType.gzipBytes - coreEngine.gzipBytes), - ), + validatorName: "Zod", + validatorBytes: t3Zod.bytes - t3Engine.bytes, + validatorKb: toKb(t3Zod.bytes - t3Engine.bytes), + validatorGzipBytes: t3Zod.gzipBytes - t3Engine.gzipBytes, + validatorGzipKb: toKb(t3Zod.gzipBytes - t3Engine.gzipBytes), + totalBytes: t3Zod.bytes, + totalKb: toKb(t3Zod.bytes), + totalGzipBytes: t3Zod.gzipBytes, + totalGzipKb: toKb(t3Zod.gzipBytes), tier: "competitor", + note: "requires Zod", }, { id: "varlock", @@ -219,31 +217,14 @@ async function run() { engineGzipBytes: t3Engine.gzipBytes, engineGzipKb: toKb(t3Engine.gzipBytes), validatorName: "Zod", - validatorBytes: Math.max(0, standardZod.bytes - standardEngine.bytes), - validatorKb: toKb( - Math.max(0, standardZod.bytes - standardEngine.bytes), - ), - validatorGzipBytes: Math.max( - 0, - standardZod.gzipBytes - standardEngine.gzipBytes, - ), - validatorGzipKb: toKb( - Math.max(0, standardZod.gzipBytes - standardEngine.gzipBytes), - ), - totalBytes: - t3Engine.bytes + - Math.max(0, standardZod.bytes - standardEngine.bytes), - totalKb: toKb( - t3Engine.bytes + - Math.max(0, standardZod.bytes - standardEngine.bytes), - ), - totalGzipBytes: - t3Engine.gzipBytes + - Math.max(0, standardZod.gzipBytes - standardEngine.gzipBytes), - totalGzipKb: toKb( - t3Engine.gzipBytes + - Math.max(0, standardZod.gzipBytes - standardEngine.gzipBytes), - ), + validatorBytes: t3Zod.bytes - t3Engine.bytes, + validatorKb: toKb(t3Zod.bytes - t3Engine.bytes), + validatorGzipBytes: t3Zod.gzipBytes - t3Engine.gzipBytes, + validatorGzipKb: toKb(t3Zod.gzipBytes - t3Engine.gzipBytes), + totalBytes: t3Zod.bytes, + totalKb: toKb(t3Zod.bytes), + totalGzipBytes: t3Zod.gzipBytes, + totalGzipKb: toKb(t3Zod.gzipBytes), tier: "competitor", }, { @@ -300,36 +281,17 @@ async function run() { engineKb: toKb(t3Engine.bytes), engineGzipBytes: t3Engine.gzipBytes, engineGzipKb: toKb(t3Engine.gzipBytes), - validatorName: "Valibot", - validatorBytes: Math.max( - 0, - standardValibot.bytes - standardEngine.bytes, - ), - validatorKb: toKb( - Math.max(0, standardValibot.bytes - standardEngine.bytes), - ), - validatorGzipBytes: Math.max( - 0, - standardValibot.gzipBytes - standardEngine.gzipBytes, - ), - validatorGzipKb: toKb( - Math.max(0, standardValibot.gzipBytes - standardEngine.gzipBytes), - ), - totalBytes: - t3Engine.bytes + - Math.max(0, standardValibot.bytes - standardEngine.bytes), - totalKb: toKb( - t3Engine.bytes + - Math.max(0, standardValibot.bytes - standardEngine.bytes), - ), - totalGzipBytes: - t3Engine.gzipBytes + - Math.max(0, standardValibot.gzipBytes - standardEngine.gzipBytes), - totalGzipKb: toKb( - t3Engine.gzipBytes + - Math.max(0, standardValibot.gzipBytes - standardEngine.gzipBytes), - ), + validatorName: "Zod", + validatorBytes: t3Zod.bytes - t3Engine.bytes, + validatorKb: toKb(t3Zod.bytes - t3Engine.bytes), + validatorGzipBytes: t3Zod.gzipBytes - t3Engine.gzipBytes, + validatorGzipKb: toKb(t3Zod.gzipBytes - t3Engine.gzipBytes), + totalBytes: t3Zod.bytes, + totalKb: toKb(t3Zod.bytes), + totalGzipBytes: t3Zod.gzipBytes, + totalGzipKb: toKb(t3Zod.gzipBytes), tier: "competitor", + note: "requires Zod", }, { id: "varlock",