diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index 19440b0..d8e5e7d 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -7,7 +7,7 @@ on: push: branches: [main] pull_request: - branches: ['**'] + branches: ["**"] # Allow manually re-running the check from the Actions tab. workflow_dispatch: @@ -28,7 +28,7 @@ jobs: strategy: matrix: - node-version: ['24.14'] + node-version: ["24.14"] steps: # persist-credentials: false keeps the GITHUB_TOKEN out of .git/config, so a diff --git a/CLAUDE.md b/CLAUDE.md index 46c2c85..e1005e9 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -23,19 +23,21 @@ IMPORTANT for AI agents: Do NOT run `gro dev` - the developer will manage the de - Svelte 5 - component framework with runes - SvelteKit - application framework with static adapter -- fuz_css (`@fuzdev/fuz_css`) - CSS framework and design system +- fuz_css (`@fuzdev/fuz_css`) - semantic-first CSS framework and design system - fuz_ui (`@fuzdev/fuz_ui`) - UI components, theming, docs system - fuz_util (`@fuzdev/fuz_util`) - utility functions - fuz_code (`@fuzdev/fuz_code`) - syntax highlighting - Gro (`@fuzdev/gro`) - build system and task runner -- prettier + prettier-plugin-svelte - code formatting +- tsv (via Gro) - code formatting - mdz (`@fuzdev/mdz`) - markdown preprocessor wired into `svelte.config.js` - `@fuzdev/tsv_wasm` - tsv's formatter + parser as WASM; powers the playground, loaded lazily in the browser -Note: `@fuzdev/tsv_wasm` is loaded only on `/docs/playground` via a browser-only dynamic `import()`, so the ~900KB WASM never weighs down `/docs` or the prerendered pages. +Note: `@fuzdev/tsv_wasm` is loaded only on `/docs/playground` via a browser-only dynamic `import()`, so the ~900KB-gzipped WASM (~2.9MB decoded) never weighs down `/docs` or the prerendered pages. Note: several devDependencies — `@webref/css` (CSS spec data), `zimmerframe` (AST traversal), `@sveltejs/acorn-typescript`, `zod`, and `@fuzdev/blake3_wasm` — are *optional peer dependencies* of `@fuzdev/fuz_css`'s `vite_plugin_fuz_css`, declared here so its build-time CSS generation resolves them (e.g. `css_literal.ts` imports `@webref/css`, `css_class_extractor.ts` walks ASTs with `zimmerframe`). They aren't imported by this app's own source, so don't mistake them for dead deps. +Note: `vite` is deliberately held at 7.x (with `@sveltejs/vite-plugin-svelte` 6.x) — vite 8 + plugin-svelte 7 was buggy with this app or SvelteKit's integration. Don't upgrade to vite 8 without deliberately re-verifying the site works. + ## Scope tsv.fuz.dev is the public face of the tsv tool: @@ -56,8 +58,6 @@ tsv.fuz.dev is the public face of the tsv tool: ``` src/ -├── lib/ -│ └── sample.ts # placeholder constant (not yet wired up) ├── routes/ │ ├── +page.svelte # Home: hero, links to docs and benchmarks │ ├── +layout.svelte # Root layout: fuz_css/fuz_code CSS, ThemeRoot, SiteState @@ -81,23 +81,33 @@ src/ Benchmark data comes from `tsv`. Full workflow to update: ```bash -# 1. In ~/dev/tsv — run benchmarks (builds ffi + wasm automatically) +# 1. In ~/dev/tsv — run benchmarks across deno/node/bun (builds artifacts automatically) deno task bench -# 2. In ~/dev/tsv.fuz.dev — copy the latest result +# 2. In ~/dev/tsv.fuz.dev — copy the latest results npm run update-benchmarks ``` -Step 1 writes `benches/deno/results/report.json` (committed to tsv). -Step 2 copies it to `src/routes/docs/benchmarks/benchmarks.json`. -The JSON format matches `BenchmarkBaseline` in `benchmark_data.ts`. +Step 1 writes the per-runtime `benches/js/results/report..{json,md}` +siblings, the composed cross-runtime `report.{json,md}`, and the conformance +coverage report `report.conformance.node.json` (committed to tsv). +Step 2 copies three of them — `report.node.json` → `benchmarks.json`, the +composed `report.json` → `benchmarks_cross_runtime.json`, and +`report.conformance.node.json` → `benchmarks_conformance.json` — then runs +`gro format` over the copies. Note the script's source paths are hardcoded to +`../tsv` — if the reports were generated in a different worktree, copy them +into `~/dev/tsv` (or copy manually) first. +The JSON formats match the types in `benchmark_data.ts`. Key files in `src/routes/docs/benchmarks/`: -- `benchmarks.json` — raw benchmark data (copied from tsv) +- `benchmarks.json` — per-runtime Node report (copied from tsv) +- `benchmarks_cross_runtime.json` — composed cross-runtime report (copied from tsv) +- `benchmarks_conformance.json` — conformance parse-coverage report (copied from tsv) - `benchmark_data.ts` — TypeScript types matching the JSON format -- `benchmarks.ts` — re-exports the JSON with types +- `benchmarks.ts`, `benchmarks_cross_runtime.ts`, `benchmarks_conformance.ts` — re-export the JSON with types - `BenchmarksBar.svelte`, `BenchmarksGroup.svelte`, etc. — visualization components +- `BenchmarksBaselineGroup.svelte` — shared interactive column behind the format, parse, and binary-size groups: hovering a row re-baselines that group's ratios (each of the three groups per section is independent), restoring the default anchor (the canonical reference — Prettier for format, the JS baseline for parse — and the smallest build for size) on mouseleave. `benchmark_data.ts`'s `compute_baseline_ratio`/`format_baseline_ratio`/`baseline_ratio_color` carry the per-`BaselineDirection` (`speed`/`size`) formulas it and the derivations share ## Architecture @@ -117,7 +127,7 @@ Deploys to `https://tsv.fuz.dev/` via `gro deploy` (builds and pushes to deploy - TypeScript strict mode - Svelte 5 with runes API -- Prettier with tabs, 100 char width +- tsv with tabs, 100 char width - Node >= 24.14 - Private package (not published to npm) diff --git a/package-lock.json b/package-lock.json index ee9152a..f1da569 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,31 +14,28 @@ "@fuzdev/fuz_css": "^0.63.2", "@fuzdev/fuz_ui": "^0.206.5", "@fuzdev/fuz_util": "^0.65.2", - "@fuzdev/gro": "^0.205.1", + "@fuzdev/gro": "^0.206.0", "@fuzdev/mdz": "^0.2.0", "@fuzdev/tsv_wasm": "^0.1.0", "@ryanatkn/eslint-config": "^0.12.1", "@sveltejs/acorn-typescript": "^1.0.9", "@sveltejs/adapter-static": "^3.0.10", - "@sveltejs/kit": "^2.61.1", - "@sveltejs/vite-plugin-svelte": "^7.1.2", + "@sveltejs/kit": "^2.69.1", + "@sveltejs/vite-plugin-svelte": "^6.2.4", "@types/estree": "^1.0.8", "@types/node": "^24.12.4", "@webref/css": "^8.2.0", "eslint": "^9.39.1", "eslint-plugin-svelte": "^3.13.1", "esm-env": "^1.2.2", - "magic-string": "^0.30.21", - "prettier": "^3.7.4", - "prettier-plugin-svelte": "^3.4.1", - "svelte": "^5.56.0", - "svelte-check": "^4.3.6", + "svelte": "^5.56.4", + "svelte-check": "^4.7.2", "svelte-docinfo": "^0.5.4", "svelte2tsx": "^0.7.47", "tslib": "^2.8.1", "typescript": "^5.9.3", "typescript-eslint": "^8.48.1", - "vite": "^8.0.16", + "vite": "^7.3.6", "vitest": "^4.1.9", "zimmerframe": "^1.1.4", "zod": "^4.3.6" @@ -54,6 +51,7 @@ "dev": true, "license": "MIT", "optional": true, + "peer": true, "dependencies": { "@emnapi/wasi-threads": "1.2.1", "tslib": "^2.4.0" @@ -66,6 +64,7 @@ "dev": true, "license": "MIT", "optional": true, + "peer": true, "dependencies": { "tslib": "^2.4.0" } @@ -77,6 +76,7 @@ "dev": true, "license": "MIT", "optional": true, + "peer": true, "dependencies": { "tslib": "^2.4.0" } @@ -94,7 +94,6 @@ "os": [ "aix" ], - "peer": true, "engines": { "node": ">=18" } @@ -112,7 +111,6 @@ "os": [ "android" ], - "peer": true, "engines": { "node": ">=18" } @@ -130,7 +128,6 @@ "os": [ "android" ], - "peer": true, "engines": { "node": ">=18" } @@ -148,7 +145,6 @@ "os": [ "android" ], - "peer": true, "engines": { "node": ">=18" } @@ -166,7 +162,6 @@ "os": [ "darwin" ], - "peer": true, "engines": { "node": ">=18" } @@ -184,7 +179,6 @@ "os": [ "darwin" ], - "peer": true, "engines": { "node": ">=18" } @@ -202,7 +196,6 @@ "os": [ "freebsd" ], - "peer": true, "engines": { "node": ">=18" } @@ -220,7 +213,6 @@ "os": [ "freebsd" ], - "peer": true, "engines": { "node": ">=18" } @@ -238,7 +230,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">=18" } @@ -256,7 +247,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">=18" } @@ -274,7 +264,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">=18" } @@ -292,7 +281,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">=18" } @@ -310,7 +298,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">=18" } @@ -328,7 +315,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">=18" } @@ -346,7 +332,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">=18" } @@ -364,7 +349,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">=18" } @@ -382,7 +366,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">=18" } @@ -400,7 +383,6 @@ "os": [ "netbsd" ], - "peer": true, "engines": { "node": ">=18" } @@ -418,7 +400,6 @@ "os": [ "netbsd" ], - "peer": true, "engines": { "node": ">=18" } @@ -436,7 +417,6 @@ "os": [ "openbsd" ], - "peer": true, "engines": { "node": ">=18" } @@ -454,7 +434,6 @@ "os": [ "openbsd" ], - "peer": true, "engines": { "node": ">=18" } @@ -472,7 +451,6 @@ "os": [ "openharmony" ], - "peer": true, "engines": { "node": ">=18" } @@ -490,7 +468,6 @@ "os": [ "sunos" ], - "peer": true, "engines": { "node": ">=18" } @@ -508,7 +485,6 @@ "os": [ "win32" ], - "peer": true, "engines": { "node": ">=18" } @@ -526,7 +502,6 @@ "os": [ "win32" ], - "peer": true, "engines": { "node": ">=18" } @@ -544,7 +519,6 @@ "os": [ "win32" ], - "peer": true, "engines": { "node": ">=18" } @@ -884,18 +858,17 @@ } }, "node_modules/@fuzdev/gro": { - "version": "0.205.1", - "resolved": "https://registry.npmjs.org/@fuzdev/gro/-/gro-0.205.1.tgz", - "integrity": "sha512-o5WBXWSH/UlN1BsDnpBNIi0WHR7lj7UFGltH/JctSP7oSB2ojvmoXD5OLCEeVdRVcDaSQKl48xcXKUwHKqVSmQ==", + "version": "0.206.0", + "resolved": "https://registry.npmjs.org/@fuzdev/gro/-/gro-0.206.0.tgz", + "integrity": "sha512-11nkTAEWDOvkgzynRChQS+bj3tc7R0NduB+eHLXzXZJqz66nc/oop8Ttvz0UkjTBPX/zzgtU8QuDDNyA7buf5A==", "dev": true, "license": "MIT", "dependencies": { + "@fuzdev/tsv_wasm": "^0.1.0", "chokidar": "^5.0.0", "dotenv": "^17.2.3", "esm-env": "^1.2.2", "oxc-parser": "^0.99.0", - "prettier": "^3.7.4", - "prettier-plugin-svelte": "^3.5.1", "ts-blank-space": "^0.6.2", "tslib": "^2.8.1" }, @@ -1393,27 +1366,38 @@ "dev": true, "license": "MIT" }, - "node_modules/@rolldown/binding-android-arm64": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.3.tgz", - "integrity": "sha512-454rs7jHngixp/NMxd5srYD57OnzSlZ/eFTETjORQHLwJG1lRtmNOJcBerZlfu4GjKqeq8aCCIQrMdHyhI51Hw==", + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.62.2.tgz", + "integrity": "sha512-6o7ZLZK+BeenkZCFNDXqpbjw9bD6nuWonvS/lwQJp7NoVVxm6p3qE7qQ5jGuBjiFsgvqjD8mZAU5oWxTmbOeOg==", "cpu": [ - "arm64" + "arm" ], "dev": true, "license": "MIT", "optional": true, "os": [ "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.62.2.tgz", + "integrity": "sha512-BaH7BllCACHoH1LguOU56UItGfUWjujlO65kS9LAodViaN4bwIKd7oeW/ZHJ/4ljr/7MIiENnNy3HJ0zXv8Zkw==", + "cpu": [ + "arm64" ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] }, - "node_modules/@rolldown/binding-darwin-arm64": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.3.tgz", - "integrity": "sha512-PcAhP+ynjURNyy8SKGl5DQP94aGuB/7JrXJb/t7P+hanXvQVMWzUvRRhBAcg/lNRadBhoUPqSoP4xw5tR/KBEA==", + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.62.2.tgz", + "integrity": "sha512-v39RCCvj4He82I9sFmk+M1VZ0PLM9sfsLVikjfx2hYBNALhrrOR2D3JjQA6AhlaSOgcR+RzrKY7e1+bT6SUO/A==", "cpu": [ "arm64" ], @@ -1422,15 +1406,12 @@ "optional": true, "os": [ "darwin" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } + ] }, - "node_modules/@rolldown/binding-darwin-x64": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.3.tgz", - "integrity": "sha512-9YpfeUvSE2RS7wysJ81uOZkXJz7f7Q55H2Gvp3VEw/EsahqDtrphrZ0EwDLK5vvKOzaCrBsjF8JmnMLcUt78Gg==", + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.62.2.tgz", + "integrity": "sha512-yl0y2vq3S3lHeuXhEdss6TWfKW8vkujImO12tn4ZkG/4oghr09LvdYm2RElVjokTQiUvDUGXLGsYeLqUMCKpGA==", "cpu": [ "x64" ], @@ -1439,15 +1420,26 @@ "optional": true, "os": [ "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.62.2.tgz", + "integrity": "sha512-tT4pvt4qXD+vEoezupCWi+a1F0vvDiksiHc+PxRlYTOH1I6/X4id9jPxTP+Fg+545euaFT1jJVs4CEdHZAU1vw==", + "cpu": [ + "arm64" ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] }, - "node_modules/@rolldown/binding-freebsd-x64": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.3.tgz", - "integrity": "sha512-yB1IlAsSNHncV6SCTL27/MVGR5htvQsoGxIv5KMGXALp+Ll1wYsn+x98M9MW7qa+NdSbvrrY7ANI4wLJ0n1e6g==", + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.62.2.tgz", + "integrity": "sha512-6nU5F2wCW+qvCBhTn1pdIU3bzsIoF7EUwsCDRxilWGprQR6yd508YnH9+OKFCwpfS8pjZqDUmnCAr7exax0XCg==", "cpu": [ "x64" ], @@ -1456,32 +1448,46 @@ "optional": true, "os": [ "freebsd" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } + ] }, - "node_modules/@rolldown/binding-linux-arm-gnueabihf": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.3.tgz", - "integrity": "sha512-Yi30IVAAfLUCy2MseFjbB1jAMDl1VMCAas5StnYp8da9+CKvMd2H2cbEjWcw5NPaPqzvYkVIaF1nNUG+b7u/sw==", + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.62.2.tgz", + "integrity": "sha512-n1GJHPOvpIfhi3TmrCeh6S6URt9BFCt0KQE3qvexyGCTAKpR4Lg+eWvNZEqu7epxwus/8ElT3hacYEucm49SZg==", "cpu": [ "arm" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.62.2.tgz", + "integrity": "sha512-JqgflS8wEB+UXV/vS1RpRbifGBeN4D5lz8D8oOFbFZw4vedvdOgCFAjfBmIMdW3yL10XpQQ0Ambepw6MXrhOnA==", + "cpu": [ + "arm" ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/@rolldown/binding-linux-arm64-gnu": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.3.tgz", - "integrity": "sha512-jsO7R8To+AdlYgUmN5sHSCZbfhtMBkO0WUx8iORQnPcMMdgr7qM2DQmMwgabs3GhNztdmoKkMKQFHD6DTMCIQw==", + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.62.2.tgz", + "integrity": "sha512-wnFJkogWvN4jm/hQRF2UBaeUmk20j5+DmHvoyWii2b8HJDyvz1MF2OU/6ynXt2KR63rbZLWkFpoytpdc/yBuSA==", "cpu": [ "arm64" ], @@ -1493,15 +1499,12 @@ "optional": true, "os": [ "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } + ] }, - "node_modules/@rolldown/binding-linux-arm64-musl": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.3.tgz", - "integrity": "sha512-VWkUHwWriDciit80wleYwKILoR/KMvxh/IdwS/paX+ZgpuRpCrKLUdadJbc0NpBEiyhpYawsJ73j9aCvOH+f7Q==", + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.62.2.tgz", + "integrity": "sha512-HVu2bp0zhvJ8xHEV9+UUs7S90VadmBSY3LcIMvozbPo4AuMGDWlz3ymHLHZPX4hR67TKTt8Qp5PJ5RBg/i+RMQ==", "cpu": [ "arm64" ], @@ -1513,15 +1516,46 @@ "optional": true, "os": [ "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.62.2.tgz", + "integrity": "sha512-mQqqAV8QaoSgr9I2fKDLY2BAVvmKjWoGiu/cSYQonsLvtqwEn1E4QYfnCOcp5zoEqNhsDYin1s6jx/VJmrxlZg==", + "cpu": [ + "loong64" ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.62.2.tgz", + "integrity": "sha512-IxKLoxCQ2IWi6bT2akyDUBGsOImDKB+sPp4EsTmwFQ/fMwpCKm8uLSSgP/Kx/QYUgKis6SEZ5/Nlhup0DIA0PQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/@rolldown/binding-linux-ppc64-gnu": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.3.tgz", - "integrity": "sha512-5f1laC0SlIR0yDbFCd8acUhvJIag6N3zC5P7oUPN6wX0aOma+uKJ0wBDH5aq7I1PVI2ttTlhJwzwRIBnLiSGEg==", + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.62.2.tgz", + "integrity": "sha512-Mk5ha2RQSgyFfmYYLkBpPnUk8D8FriBxesO1u9O75X0mHgXL1UQcH5Itl2lurWL2tj0RxV9b9tJgipac0hRY9A==", "cpu": [ "ppc64" ], @@ -1533,17 +1567,31 @@ "optional": true, "os": [ "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.62.2.tgz", + "integrity": "sha512-CjvEnqJL/0/TQ3TXX3OPIJ/kmBellrWd4heXUmHeJlTnmwjKpSJzoehLaL6Xk0ZnMHBu9dZuFADNOrtjF4v+2w==", + "cpu": [ + "ppc64" ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/@rolldown/binding-linux-s390x-gnu": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.3.tgz", - "integrity": "sha512-Iq4ko0r4XsgbrF/LunNgHtAGLRRVE2kXonAXQ/MV0mC6jQpMOhW1SvtZja2EhC/kd05++bP78dsqBeIQyYJ6Yg==", + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.62.2.tgz", + "integrity": "sha512-1SiZbzwdkaDURsew/tSOrooKiYy7EQGT6m8ufavAi9NEyQb/6VuIxFXAL1fqa4iZe3g4NbNk4P7J32z2tw5Mgg==", "cpu": [ - "s390x" + "riscv64" ], "dev": true, "libc": [ @@ -1553,17 +1601,31 @@ "optional": true, "os": [ "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.62.2.tgz", + "integrity": "sha512-nQts12zJ3NQRoE6uYljOH89v7szzLDvG2JD/vsX+vGXU8w/At1GowTZ5/7qeFQ8m7L55rpR8Okugnuo5bgjy2Q==", + "cpu": [ + "riscv64" ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/@rolldown/binding-linux-x64-gnu": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.3.tgz", - "integrity": "sha512-B8m6tD5+/N5FeNQFbKlLA/2yVq9ycQP1SeedyEYYKWBNR3ZQbkvIUcNnDNM03lO1l5F2roiiFJGgvoLLyZXtSg==", + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.62.2.tgz", + "integrity": "sha512-E9/ll019jhPIJgpzfZoIkBGhcz+kKNgVWYRY0zr9srBdPPFVpvOKW8VaJKUbeK+eZXyQF9ltME+Kk6affeaPgg==", "cpu": [ - "x64" + "s390x" ], "dev": true, "libc": [ @@ -1573,15 +1635,29 @@ "optional": true, "os": [ "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.62.2.tgz", + "integrity": "sha512-5BqxR/pshjey51iliyzTD5Xi3EN0aLmQ2lZ3lvefVV9c82BvrLo2/6OT55iifpWBufs6kdwWbuOKS841DrmK9A==", + "cpu": [ + "x64" ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/@rolldown/binding-linux-x64-musl": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.3.tgz", - "integrity": "sha512-pSdpdUJHkuCxun9LE7jvgUB9qsRgaiyNNCX7m/AvHTcq67AiT/Yhoxvw5zPfhrM8k/BfP8ce/hMOpthKDpEUow==", + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.62.2.tgz", + "integrity": "sha512-uNN83XxQrRAh/w0/pmAfibcwyb6YWt4gP+dpnQKPVJshAloQ785ii8CT8ZCIxkGg9opVsvAlGhFitSm6D1Jjpg==", "cpu": [ "x64" ], @@ -1593,15 +1669,26 @@ "optional": true, "os": [ "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.62.2.tgz", + "integrity": "sha512-srjEIxSH3LRnJN6THczDHWQplqEMFiAJrTab0msUryh9kwNpkICf3Ea6q6MN/2cZwRFUNx5w+h6Hpi4QuHS6Zg==", + "cpu": [ + "x64" ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] }, - "node_modules/@rolldown/binding-openharmony-arm64": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.3.tgz", - "integrity": "sha512-OXXS3RKJgX2uLwM+gYyuH5omcH8fL1LJs96pZGgtetVCahON57+d4SJHzTgZiOjxgGkSnpXpOsWuPDGAKAigEg==", + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.62.2.tgz", + "integrity": "sha512-8hOJnxgbyObnCm5AlRA3A931xX19xq80RjVTKgJOvEKWqJruP/Uf12IbAOaDjjEXYRewwHLfmF0YRIdK3OwKWA==", "cpu": [ "arm64" ], @@ -1610,51 +1697,40 @@ "optional": true, "os": [ "openharmony" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } + ] }, - "node_modules/@rolldown/binding-wasm32-wasi": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.3.tgz", - "integrity": "sha512-JTtb8BWFynicNSoPrehsCzBtOKjZ6jhMiPFEmOiuXg1Fl8dn2KHQob+GuPSGR0dryQa1PQJbzjF3dqO/whhjLg==", + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.62.2.tgz", + "integrity": "sha512-mmF4AY1i0hG/bLWUctUq59gtmgaSIRa3cu/A3JFRp/sCNEme2bgDEiDS22P9FbnJB8NJNF4jPJiSP5RHQpUTDg==", "cpu": [ - "wasm32" + "arm64" ], "dev": true, "license": "MIT", "optional": true, - "dependencies": { - "@emnapi/core": "1.10.0", - "@emnapi/runtime": "1.10.0", - "@napi-rs/wasm-runtime": "^1.1.4" - }, - "engines": { - "node": "^20.19.0 || >=22.12.0" - } + "os": [ + "win32" + ] }, - "node_modules/@rolldown/binding-win32-arm64-msvc": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.3.tgz", - "integrity": "sha512-gEdFFEN70A/jxb2svrWsN3aDL7OUtmvlOy+6fa2jxG8K0wQ1ZbdeLGnidov6Yu5/733dI5ySfzFlQ/cb0bSz1g==", + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.62.2.tgz", + "integrity": "sha512-DZgkknc6jhHrk46V25vbAM0zZkyP0nSDkJB8/dRkLTxv470dOmWDqGoEJl/9A0dFfS7yE3REOwNDxpHwSLSt0Q==", "cpu": [ - "arm64" + "ia32" ], "dev": true, "license": "MIT", "optional": true, "os": [ "win32" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } + ] }, - "node_modules/@rolldown/binding-win32-x64-msvc": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.3.tgz", - "integrity": "sha512-eXB7CHuaQdqmJcc3koCNtNPmT/bj2gc999kUFgBxG8Ac0NdgXc4rkCHhqrgrhN3zddvvvrgzj1e90SuSfmyIXA==", + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.62.2.tgz", + "integrity": "sha512-T6xr6ucWSFto+VGajA8YH26LdpHRuP4YLHEKAtCWvJDOlnmWcDZVCI2Jmjr+IFHDlt2zRaTAKE4tfjTaWLgJBg==", "cpu": [ "x64" ], @@ -1663,17 +1739,21 @@ "optional": true, "os": [ "win32" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } + ] }, - "node_modules/@rolldown/pluginutils": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz", - "integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==", + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.62.2.tgz", + "integrity": "sha512-BfzEnDJOt9T8M989/lA37EcJgat01wLRnoi5dQf3QzOH7jzpqTAzdDbVfRljVr5r+jzKqpbHeyOfAaXxAd0PAA==", + "cpu": [ + "x64" + ], "dev": true, - "license": "MIT" + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] }, "node_modules/@ryanatkn/eslint-config": { "version": "0.12.1", @@ -1724,9 +1804,9 @@ } }, "node_modules/@sveltejs/kit": { - "version": "2.61.1", - "resolved": "https://registry.npmjs.org/@sveltejs/kit/-/kit-2.61.1.tgz", - "integrity": "sha512-Ny8s1SR1TyQS2hD2Rvw0XKzU2Nw1eUF52dTb6T2bdcgz7wSC+Nyb5IwjWYlR4b2dvbbR5NJDiQwHg3rnNseghg==", + "version": "2.69.1", + "resolved": "https://registry.npmjs.org/@sveltejs/kit/-/kit-2.69.1.tgz", + "integrity": "sha512-+nz8Fx/cElzb2ZPHC+6Ll3y3NEVIe4Na75PeplLlyTmd1dBXAjz2KR14y1ZgNjb2ThfAYzulu+PFy1UE3RCUzA==", "dev": true, "license": "MIT", "dependencies": { @@ -1765,24 +1845,53 @@ } } }, + "node_modules/@sveltejs/load-config": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@sveltejs/load-config/-/load-config-0.2.0.tgz", + "integrity": "sha512-1LgZ/qUqSoq+QorD83lk2hka79Px0wXNW2q5V1nZlxGhQgw1jrsIbVz5YiCeucVLo4XvFLjXukUaQjIiqowkcg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 18.0.0" + } + }, "node_modules/@sveltejs/vite-plugin-svelte": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte/-/vite-plugin-svelte-7.1.2.tgz", - "integrity": "sha512-DrUBA2UXRfDmUX/ZTiEopd3X40yavsJF1FX2RygcuIScHL7o5YX1fMvoYnDhjeJQC4weCOklirpNWlcb2NiSeA==", + "version": "6.2.4", + "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte/-/vite-plugin-svelte-6.2.4.tgz", + "integrity": "sha512-ou/d51QSdTyN26D7h6dSpusAKaZkAiGM55/AKYi+9AGZw7q85hElbjK3kEyzXHhLSnRISHOYzVge6x0jRZ7DXA==", "dev": true, "license": "MIT", "dependencies": { + "@sveltejs/vite-plugin-svelte-inspector": "^5.0.0", "deepmerge": "^4.3.1", "magic-string": "^0.30.21", "obug": "^2.1.0", - "vitefu": "^1.1.2" + "vitefu": "^1.1.1" }, "engines": { "node": "^20.19 || ^22.12 || >=24" }, "peerDependencies": { - "svelte": "^5.46.4", - "vite": "^8.0.0-beta.7 || ^8.0.0" + "svelte": "^5.0.0", + "vite": "^6.3.0 || ^7.0.0" + } + }, + "node_modules/@sveltejs/vite-plugin-svelte-inspector": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte-inspector/-/vite-plugin-svelte-inspector-5.0.2.tgz", + "integrity": "sha512-TZzRTcEtZffICSAoZGkPSl6Etsj2torOVrx6Uw0KpXxrec9Gg6jFWQ60Q3+LmNGfZSxHRCZL7vXVZIWmuV50Ig==", + "dev": true, + "license": "MIT", + "dependencies": { + "obug": "^2.1.0" + }, + "engines": { + "node": "^20.19 || ^22.12 || >=24" + }, + "peerDependencies": { + "@sveltejs/vite-plugin-svelte": "^6.0.0-next.0", + "svelte": "^5.0.0", + "vite": "^6.3.0 || ^7.0.0" } }, "node_modules/@tybys/wasm-util": { @@ -1822,9 +1931,9 @@ "license": "MIT" }, "node_modules/@types/estree": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", - "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", "dev": true, "license": "MIT" }, @@ -2545,16 +2654,6 @@ "node": ">=0.10.0" } }, - "node_modules/detect-libc": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", - "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=8" - } - }, "node_modules/devalue": { "version": "5.8.1", "resolved": "https://registry.npmjs.org/devalue/-/devalue-5.8.1.tgz", @@ -2589,7 +2688,6 @@ "dev": true, "hasInstallScript": true, "license": "MIT", - "peer": true, "bin": { "esbuild": "bin/esbuild" }, @@ -2801,9 +2899,9 @@ } }, "node_modules/esrap": { - "version": "2.2.9", - "resolved": "https://registry.npmjs.org/esrap/-/esrap-2.2.9.tgz", - "integrity": "sha512-4KijP+NxCWthMCUC3qHbE6n4vCjqgJS1uAYKhuT/GWfFTf1Qyive2TgOjep+gzbSzRfnNyaN/UU9YmdOt8Eg0A==", + "version": "2.2.13", + "resolved": "https://registry.npmjs.org/esrap/-/esrap-2.2.13.tgz", + "integrity": "sha512-m8jH5hZgJE2RRUK/jjkGPcJEDAV+dYnZYFkosQaPTcE+Yw4xynXHOo6FUdwaWBtdR3b1MMa7wEDTSHeR2VWsGA==", "dev": true, "license": "MIT", "dependencies": { @@ -3181,279 +3279,6 @@ "node": ">= 0.8.0" } }, - "node_modules/lightningcss": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", - "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==", - "dev": true, - "license": "MPL-2.0", - "dependencies": { - "detect-libc": "^2.0.3" - }, - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - }, - "optionalDependencies": { - "lightningcss-android-arm64": "1.32.0", - "lightningcss-darwin-arm64": "1.32.0", - "lightningcss-darwin-x64": "1.32.0", - "lightningcss-freebsd-x64": "1.32.0", - "lightningcss-linux-arm-gnueabihf": "1.32.0", - "lightningcss-linux-arm64-gnu": "1.32.0", - "lightningcss-linux-arm64-musl": "1.32.0", - "lightningcss-linux-x64-gnu": "1.32.0", - "lightningcss-linux-x64-musl": "1.32.0", - "lightningcss-win32-arm64-msvc": "1.32.0", - "lightningcss-win32-x64-msvc": "1.32.0" - } - }, - "node_modules/lightningcss-android-arm64": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz", - "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-darwin-arm64": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz", - "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-darwin-x64": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz", - "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-freebsd-x64": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz", - "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-arm-gnueabihf": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz", - "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-arm64-gnu": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz", - "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "libc": [ - "glibc" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-arm64-musl": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz", - "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==", - "cpu": [ - "arm64" - ], - "dev": true, - "libc": [ - "musl" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-x64-gnu": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz", - "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==", - "cpu": [ - "x64" - ], - "dev": true, - "libc": [ - "glibc" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-x64-musl": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz", - "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==", - "cpu": [ - "x64" - ], - "dev": true, - "libc": [ - "musl" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-win32-arm64-msvc": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz", - "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-win32-x64-msvc": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz", - "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, "node_modules/lilconfig": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz", @@ -3879,33 +3704,6 @@ "node": ">= 0.8.0" } }, - "node_modules/prettier": { - "version": "3.7.4", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.7.4.tgz", - "integrity": "sha512-v6UNi1+3hSlVvv8fSaoUbggEM5VErKmmpGA7Pl3HF8V6uKY7rvClBOJlH6yNwQtfTueNkGVpOv/mtWL9L4bgRA==", - "dev": true, - "license": "MIT", - "bin": { - "prettier": "bin/prettier.cjs" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/prettier/prettier?sponsor=1" - } - }, - "node_modules/prettier-plugin-svelte": { - "version": "3.5.1", - "resolved": "https://registry.npmjs.org/prettier-plugin-svelte/-/prettier-plugin-svelte-3.5.1.tgz", - "integrity": "sha512-65+fr5+cgIKWKiqM1Doum4uX6bY8iFCdztvvp2RcF+AJoieaw9kJOFMNcJo/bkmKYsxFaM9OsVZK/gWauG/5mg==", - "dev": true, - "license": "MIT", - "peerDependencies": { - "prettier": "^3.0.0", - "svelte": "^3.2.0 || ^4.0.0-next.0 || ^5.0.0-next.0" - } - }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", @@ -3940,48 +3738,49 @@ "node": ">=4" } }, - "node_modules/rolldown": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.3.tgz", - "integrity": "sha512-i00lAJ2ks1BYr7rjNjKC7BcqAS7nVfiT3QX1SI5aY+AFHblCmaUf9OE9dbdzDvW6dJxbi2ZCZiy9v3CcwOiX3g==", + "node_modules/rollup": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.62.2.tgz", + "integrity": "sha512-RFnrW4lhXA3s3eqHDZvN654g8OTjzRfqpIRJYczCGB6HzphckVAi/Qh4tbPUbRuDi7s1Llv8g/NspLkttY3gTA==", "dev": true, "license": "MIT", "dependencies": { - "@oxc-project/types": "=0.133.0", - "@rolldown/pluginutils": "^1.0.0" + "@types/estree": "1.0.9" }, "bin": { - "rolldown": "bin/cli.mjs" + "rollup": "dist/bin/rollup" }, "engines": { - "node": "^20.19.0 || >=22.12.0" + "node": ">=18.0.0", + "npm": ">=8.0.0" }, "optionalDependencies": { - "@rolldown/binding-android-arm64": "1.0.3", - "@rolldown/binding-darwin-arm64": "1.0.3", - "@rolldown/binding-darwin-x64": "1.0.3", - "@rolldown/binding-freebsd-x64": "1.0.3", - "@rolldown/binding-linux-arm-gnueabihf": "1.0.3", - "@rolldown/binding-linux-arm64-gnu": "1.0.3", - "@rolldown/binding-linux-arm64-musl": "1.0.3", - "@rolldown/binding-linux-ppc64-gnu": "1.0.3", - "@rolldown/binding-linux-s390x-gnu": "1.0.3", - "@rolldown/binding-linux-x64-gnu": "1.0.3", - "@rolldown/binding-linux-x64-musl": "1.0.3", - "@rolldown/binding-openharmony-arm64": "1.0.3", - "@rolldown/binding-wasm32-wasi": "1.0.3", - "@rolldown/binding-win32-arm64-msvc": "1.0.3", - "@rolldown/binding-win32-x64-msvc": "1.0.3" - } - }, - "node_modules/rolldown/node_modules/@oxc-project/types": { - "version": "0.133.0", - "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.133.0.tgz", - "integrity": "sha512-KzkdCd6Uxqnf6l3HOw1xfatAlUURA0g14cvBYFyJ5SaNOQbOUvBr9PKArcPcrNIeRsBdgcUzOGrhKveVpvOIGA==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/Boshen" + "@rollup/rollup-android-arm-eabi": "4.62.2", + "@rollup/rollup-android-arm64": "4.62.2", + "@rollup/rollup-darwin-arm64": "4.62.2", + "@rollup/rollup-darwin-x64": "4.62.2", + "@rollup/rollup-freebsd-arm64": "4.62.2", + "@rollup/rollup-freebsd-x64": "4.62.2", + "@rollup/rollup-linux-arm-gnueabihf": "4.62.2", + "@rollup/rollup-linux-arm-musleabihf": "4.62.2", + "@rollup/rollup-linux-arm64-gnu": "4.62.2", + "@rollup/rollup-linux-arm64-musl": "4.62.2", + "@rollup/rollup-linux-loong64-gnu": "4.62.2", + "@rollup/rollup-linux-loong64-musl": "4.62.2", + "@rollup/rollup-linux-ppc64-gnu": "4.62.2", + "@rollup/rollup-linux-ppc64-musl": "4.62.2", + "@rollup/rollup-linux-riscv64-gnu": "4.62.2", + "@rollup/rollup-linux-riscv64-musl": "4.62.2", + "@rollup/rollup-linux-s390x-gnu": "4.62.2", + "@rollup/rollup-linux-x64-gnu": "4.62.2", + "@rollup/rollup-linux-x64-musl": "4.62.2", + "@rollup/rollup-openbsd-x64": "4.62.2", + "@rollup/rollup-openharmony-arm64": "4.62.2", + "@rollup/rollup-win32-arm64-msvc": "4.62.2", + "@rollup/rollup-win32-ia32-msvc": "4.62.2", + "@rollup/rollup-win32-x64-gnu": "4.62.2", + "@rollup/rollup-win32-x64-msvc": "4.62.2", + "fsevents": "~2.3.2" } }, "node_modules/sade": { @@ -4120,9 +3919,9 @@ } }, "node_modules/svelte": { - "version": "5.56.0", - "resolved": "https://registry.npmjs.org/svelte/-/svelte-5.56.0.tgz", - "integrity": "sha512-kTXr26t1bchFp28ROrb957LtbujpBmBDibmqMGziVpUs7awBi96TGgX6SovrA8BNoEUDVRK2Fb9FkeYlGspoVg==", + "version": "5.56.4", + "resolved": "https://registry.npmjs.org/svelte/-/svelte-5.56.4.tgz", + "integrity": "sha512-/d0QHehmRuJW8gVz395MTkPcPozxzdjBMBE8oEYGz8O3b9KTMzzQ9ZHJQLuFKOHOPQbU6kx/X4iid/EBBzH7iw==", "dev": true, "license": "MIT", "dependencies": { @@ -4137,7 +3936,7 @@ "clsx": "^2.1.1", "devalue": "^5.8.1", "esm-env": "^1.2.1", - "esrap": "^2.2.9", + "esrap": "^2.2.12", "is-reference": "^3.0.3", "locate-character": "^3.0.0", "magic-string": "^0.30.11", @@ -4148,13 +3947,14 @@ } }, "node_modules/svelte-check": { - "version": "4.3.6", - "resolved": "https://registry.npmjs.org/svelte-check/-/svelte-check-4.3.6.tgz", - "integrity": "sha512-uBkz96ElE3G4pt9E1Tw0xvBfIUQkeH794kDQZdAUk795UVMr+NJZpuFSS62vcmO/DuSalK83LyOwhgWq8YGU1Q==", + "version": "4.7.2", + "resolved": "https://registry.npmjs.org/svelte-check/-/svelte-check-4.7.2.tgz", + "integrity": "sha512-GoS4XJdGswlq0rIT1vtFLzJY1bvHtY37McY9H9Gkm1Ggw/ICdZYn8J/Z8Yi0BEL0i3R4+jtaWVePjyppMlij/A==", "dev": true, "license": "MIT", "dependencies": { "@jridgewell/trace-mapping": "^0.3.25", + "@sveltejs/load-config": "^0.2.0", "chokidar": "^4.0.1", "fdir": "^6.2.0", "picocolors": "^1.0.0", @@ -4432,17 +4232,18 @@ "license": "MIT" }, "node_modules/vite": { - "version": "8.0.16", - "resolved": "https://registry.npmjs.org/vite/-/vite-8.0.16.tgz", - "integrity": "sha512-h9bXPmJichP5fLmVQo3PyaGSDE2n3aPuomeAlVRm0JLmt4rY6zmPKd59HYI4LNW8oTK7tlTsuC7l/m7awx9Jcw==", + "version": "7.3.6", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.6.tgz", + "integrity": "sha512-4XP60spRGjSZFf1qYH+dJIkK2znL3zQfl9KkOV9MkkRR/3Dls0dxaBsQPTloEc5BLXWPL9vsOxopxyKoMmDueg==", "dev": true, "license": "MIT", "dependencies": { - "lightningcss": "^1.32.0", - "picomatch": "^4.0.4", - "postcss": "^8.5.15", - "rolldown": "1.0.3", - "tinyglobby": "^0.2.17" + "esbuild": "^0.27.0 || ^0.28.0", + "fdir": "^6.5.0", + "picomatch": "^4.0.3", + "postcss": "^8.5.6", + "rollup": "^4.43.0", + "tinyglobby": "^0.2.15" }, "bin": { "vite": "bin/vite.js" @@ -4458,10 +4259,9 @@ }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", - "@vitejs/devtools": "^0.1.18", - "esbuild": "^0.27.0 || ^0.28.0", "jiti": ">=1.21.0", "less": "^4.0.0", + "lightningcss": "^1.21.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", @@ -4474,18 +4274,15 @@ "@types/node": { "optional": true }, - "@vitejs/devtools": { - "optional": true - }, - "esbuild": { - "optional": true - }, "jiti": { "optional": true }, "less": { "optional": true }, + "lightningcss": { + "optional": true + }, "sass": { "optional": true }, diff --git a/package.json b/package.json index e1c6a86..5bf4516 100644 --- a/package.json +++ b/package.json @@ -16,7 +16,7 @@ "test": "gro test", "preview": "vite preview", "deploy": "gro deploy", - "update-benchmarks": "cp ../tsv/benches/deno/results/report.json src/routes/docs/benchmarks/benchmarks.json" + "update-benchmarks": "cp ../tsv/benches/js/results/report.node.json src/routes/docs/benchmarks/benchmarks.json && cp ../tsv/benches/js/results/report.json src/routes/docs/benchmarks/benchmarks_cross_runtime.json && cp ../tsv/benches/js/results/report.conformance.node.json src/routes/docs/benchmarks/benchmarks_conformance.json && gro format" }, "type": "module", "engines": { @@ -28,52 +28,32 @@ "@fuzdev/fuz_css": "^0.63.2", "@fuzdev/fuz_ui": "^0.206.5", "@fuzdev/fuz_util": "^0.65.2", - "@fuzdev/gro": "^0.205.1", + "@fuzdev/gro": "^0.206.0", "@fuzdev/mdz": "^0.2.0", "@fuzdev/tsv_wasm": "^0.1.0", "@ryanatkn/eslint-config": "^0.12.1", "@sveltejs/acorn-typescript": "^1.0.9", "@sveltejs/adapter-static": "^3.0.10", - "@sveltejs/kit": "^2.61.1", - "@sveltejs/vite-plugin-svelte": "^7.1.2", + "@sveltejs/kit": "^2.69.1", + "@sveltejs/vite-plugin-svelte": "^6.2.4", "@types/estree": "^1.0.8", "@types/node": "^24.12.4", "@webref/css": "^8.2.0", "eslint": "^9.39.1", "eslint-plugin-svelte": "^3.13.1", "esm-env": "^1.2.2", - "magic-string": "^0.30.21", - "prettier": "^3.7.4", - "prettier-plugin-svelte": "^3.4.1", - "svelte": "^5.56.0", - "svelte-check": "^4.3.6", + "svelte": "^5.56.4", + "svelte-check": "^4.7.2", "svelte-docinfo": "^0.5.4", "svelte2tsx": "^0.7.47", "tslib": "^2.8.1", "typescript": "^5.9.3", "typescript-eslint": "^8.48.1", - "vite": "^8.0.16", + "vite": "^7.3.6", "vitest": "^4.1.9", "zimmerframe": "^1.1.4", "zod": "^4.3.6" }, - "prettier": { - "plugins": [ - "prettier-plugin-svelte" - ], - "useTabs": true, - "printWidth": 100, - "singleQuote": true, - "bracketSpacing": false, - "overrides": [ - { - "files": "package.json", - "options": { - "useTabs": false - } - } - ] - }, "sideEffects": [ "**/*.css" ], diff --git a/src/app.html b/src/app.html index e3061bc..a1c58fd 100644 --- a/src/app.html +++ b/src/app.html @@ -1,23 +1,29 @@ - - - - - + + + + + - + - %sveltekit.head% - - -
%sveltekit.body%
- + %sveltekit.head% + + +
%sveltekit.body%
+ diff --git a/src/lib/sample.ts b/src/lib/sample.ts deleted file mode 100644 index 429c922..0000000 --- a/src/lib/sample.ts +++ /dev/null @@ -1 +0,0 @@ -export const sample = 'TODO - publish tsv!'; diff --git a/src/routes/docs/benchmarks/+page.svelte b/src/routes/docs/benchmarks/+page.svelte index ef80d88..a761b5a 100644 --- a/src/routes/docs/benchmarks/+page.svelte +++ b/src/routes/docs/benchmarks/+page.svelte @@ -5,15 +5,23 @@ import {tome_get_by_slug} from '@fuzdev/fuz_ui/tome.ts'; import {benchmarks_json} from './benchmarks.ts'; + import {benchmarks_conformance_json} from './benchmarks_conformance.ts'; + import {benchmarks_cross_runtime_json} from './benchmarks_cross_runtime.ts'; + import {benchmarks_cli} from './benchmarks_cli.ts'; import { - category_color, derive_benchmark_groups, + derive_conformance_groups, derive_speedup_summary, + format_corpus_source_files, + corpus_source_url, } from './benchmark_data.ts'; import BenchmarksSummary from './BenchmarksSummary.svelte'; import BenchmarksGroup from './BenchmarksGroup.svelte'; + import BenchmarksConformance from './BenchmarksConformance.svelte'; import BenchmarksSizes from './BenchmarksSizes.svelte'; import BenchmarksMeta from './BenchmarksMeta.svelte'; + import BenchmarksCrossRuntime from './BenchmarksCrossRuntime.svelte'; + import BenchmarksCli from './BenchmarksCli.svelte'; const LIBRARY_ITEM_NAME = 'benchmarks'; @@ -21,8 +29,12 @@ const groups = derive_benchmark_groups(benchmarks_json); const speedup_rows = derive_speedup_summary(groups); + const conformance_groups = derive_conformance_groups(benchmarks_conformance_json); const corpus = $derived(benchmarks_json.corpus); + // Derived from the report so the "What's measured" figure can't drift from the + // copied data (the report carries per-language file counts, not bytes). + const corpus_file_count = $derived(Object.values(corpus).reduce((sum, n) => sum + n, 0)); const format_groups = groups.filter((g) => g.operation === 'format'); const parse_groups = groups.filter((g) => g.operation === 'parse'); @@ -30,120 +42,291 @@

- tsv is a toolchain for TypeScript/JS, CSS, and Svelte in Rust. Performance is its priority - after correctness, and the numbers compare favorably to Oxc and Biome for its supported - languages. + tsv is a toolchain for TypeScript/JS, CSS, and Svelte in Rust. After correctness, performance + is tsv's next priority. This page shows how it measures up against Prettier, which tsv closely + follows, and against Oxc and Biome, which are similar tools with wider language support (tsv + doesn't support JSX/TSX/SCSS/etc).

-
-
- tsv native - tsv native json - tsv wasm - tsv wasm json + +

+ Compared to Oxc and Biome, tsv is faster, smaller, and uses less memory to parse and format + its supported languages. This section has a prose summary; skip ahead for charts. +

+

+ The measurements on this page are single-threaded and in-process, where each tool parses or + formats one file at a time (isolating engine speed from multi-core parallelism), measured over + a large collection of real-world repos: {corpus_file_count.toLocaleString('en-US')} files + including Svelte's official repos (svelte, kit, svelte.dev) and the fuz.dev repos. On that basis: +

+
    +
  • + Formatting TypeScript, tsv is ~1.66x faster than Oxfmt (native-vs-native), ~26x faster than + Prettier (native Rust vs Prettier's JS), and ~6.5x faster than Biome (wasm-vs-wasm). +
  • +
  • + Formatting Svelte, tsv is is ~63x faster than Prettier (which Oxfmt's Svelte path delegates + to internally) and 7.3x faster than Biome. +
  • +
  • Formatting CSS, it's ~2.6x faster than Oxfmt and 12.4x faster than Biome.
  • +
  • + Parsing TypeScript, tsv is ~1.3x faster than Oxc (with the payload-matched span-only AST + both emit) and Biome doesn't expose its parser to JS. tsv's default AST adds a per-node + line/column loc for drop-in Svelte compatibility, with a fast path to + reconstruct locs in JS. +
  • +
  • + A fork of Oxc's official bench-formatter is an end-to-end CLI benchmark with its own corpus. On a real TypeScript repo, tsv formats + ~3.1x faster than Oxfmt and ~6.5x faster than Biome using 3–10x less memory. Wall-clock + measurements are warped by each tool's multi-file parallelism, so they scale with core count + and are machine-dependent, and could change with tuning. +
  • +
+

+ Each section below has notes that attempt to fairly contextualize its numbers. The charts show + numbers using Node v24, and at the end of the page is a cross-runtime comparison. +

+ + + + + + + + + +

+ tsv's formatter is similar to Oxfmt - biome - oxc - canonical -

-
+ and Biome. It formats Svelte, TypeScript, and + CSS, plus JS as strict-mode TypeScript: +

+ {#each format_groups as group (group.language)} + + {/each} + + - -

- Rather than supporting many languages, tsv focuses on Svelte/HTML, TypeScript/JS, and CSS. - This lets it be smaller when it's all you need, a quality that's more relevant when used in - the browser via wasm: + +

+ The parse entries that build a full JS AST are comparable in mechanism: tsv and oxc-parser + both serialize the AST to JSON in Rust and deserialize it in JS, native and wasm alike. But + the deliverables differ — tsv's default wire (tsv-json / tsv_wasm-json) carries a per-node loc (line/column) object that oxc-parser's default + span-only AST omits, and that loc is roughly half the wire bytes and most of its + JSON.parse cost. The tsv-json-no-locations / tsv_wasm-json-no-locations entries drop it, emitting the same span-only shape oxc does, so those are the + payload-matched, apples-to-apples comparison with oxc-parser (line/column stays derivable from + the offsets plus source, so nothing is lost). The tsv-internal and tsv_wasm-internal entries + build the native AST but skip JS-side materialization, so they show raw in-engine speed rather + than a cross-tool comparison.

- + {#each parse_groups as group (group.language)} + + {/each}
- - + +

+ Separate from the speed numbers above that use a real-world corpus of code, this section + measures parse coverage: how much of a much larger, deliberately hard corpus each + parser accepts — Prettier's format-test suites, Svelte's compiler test suite, CSS extracted + from + web-platform-tests, and test262's expected-valid strict-mode tests. +

+ +
- -

- tsv's formatter is similar to Oxfmt - and Biome. Today it can format Svelte, TypeScript, and CSS, - plus HTML and JS (as strict-mode TypeScript): + +

+ Rather than supporting many languages, tsv focuses on Svelte/HTML, TypeScript/JS, and CSS. + This lets it be smaller when it's all you need, a quality that's more relevant when used in + the browser via wasm.

- {#each format_groups as group (group.language)} - - {/each} +
- +

- The parse rows that build a full JS AST are directly comparable: tsv and oxc-parser both - serialize the AST to JSON in Rust and deserialize it in JS, native and wasm alike. The - tsv-internal and tsv_wasm-internal rows are tsv's parse-only numbers - they build the native - AST but skip JS-side materialization, so they show raw in-engine speed rather than a - cross-tool comparison. + The numbers above measure tsv's engine in-process, one file at a time, using tsv's original + benchmarks. This section describes the results from a fork of Oxc's official + bench-formatter + that + adds tsv, timing + the whole CLI end-to-end — process spawn, file discovery, I/O, and each tool's default + multi-file parallelism — plus peak memory. It's the "what you experience typing the command" + measure, run on real repositories. tsv appears in the two JSX-free scenarios (it has no + JSX/TSX parser).

- {#each parse_groups as group (group.language)} - - {/each} +
@@ -152,39 +335,54 @@

All numbers are single-threaded: every library formats or parses one file at a time, measured - sequentially with no cross-file parallelism. These are per-file, single-core latency and - throughput numbers - not the multi-core batch throughput a CLI gets when it formats many files - at once, which most of these tools (tsv included) can do. + sequentially with no cross-file parallelism. Each row is the total time to process the whole + corpus once on a single core — not the multi-core batch throughput a CLI gets when it formats + many files at once, which most of these tools (tsv included) can do.

- What's measured: around 5,500 files (~15 MB) of - .svelte/.html, .ts/.js, and - .css, from three sources: the fuz.dev libraries and apps, upstream framework - source (Svelte, SvelteKit, and the svelte.dev site), and formatter conformance fixtures - (Prettier's and prettier-plugin-svelte's own test suites - deliberately tricky edge cases, not - typical code, so they skew the corpus toward hard cases). + What's measured: {corpus_file_count.toLocaleString('en-US')} files of .svelte, + .ts/.js, and .css — real-world code only, from two + sources: the author's libraries, apps, and sites (the fuz.dev ecosystem plus personal + SvelteKit sites), and upstream framework source (Svelte, SvelteKit, and the svelte.dev site). + The CSS set also includes real-authored CSS extracted from those components' + <style> blocks, concatenated per repo — standalone CSS files are rare in + this ecosystem, and the same bytes appear in the Svelte rows (rows are never summed). Test + files count as real code and stay in; fixture files (the formatter test suites that used to be + part of this corpus, and fixture subtrees inside the measured repos) are excluded — + deliberately tricky edge cases measure conformance, not typical throughput, and are covered by + the parse-conformance section above.

-
- + + +

+ The same benchmark harness runs under three JS runtimes — Node, Deno, and Bun. The headline + numbers above are the Node run. The native entry differs by runtime: Node and Bun load tsv's + N-API addon, while Deno loads its C-FFI library. They share code but cross a different binding + boundary, so a per-runtime delta on the same row is a runtime effect — the JS engine or the + binding boundary — not a difference in tsv's own engine, which is identical across all three. +

+ + +
+ diff --git a/src/routes/docs/benchmarks/BenchmarksBar.svelte b/src/routes/docs/benchmarks/BenchmarksBar.svelte index 128c7b5..23540b6 100644 --- a/src/routes/docs/benchmarks/BenchmarksBar.svelte +++ b/src/routes/docs/benchmarks/BenchmarksBar.svelte @@ -14,6 +14,9 @@ ratio_text, ratio_color, annotation, + disabled = false, + on_enter, + on_leave, }: { label: string; bar_fraction: number; @@ -24,48 +27,95 @@ // optional extra context shown between value and ratio (corpus coverage in // benchmark groups, gzipped size in binary-size groups); omitted when absent annotation?: string | undefined; + // a grayed-out, inert placeholder (a tool that doesn't run in this group) — + // no bar, no value, no ratio, just the label held in its shared slot + disabled?: boolean; + // hover-to-rebaseline: fired when the pointer enters/leaves the row so the + // group can adopt this row as its ratio anchor; omitted on inert placeholders + on_enter?: (() => void) | undefined; + on_leave?: (() => void) | undefined; } = $props(); + + // the parenthesized binding suffix (`(wasm)`/`(node napi)`) describes how the + // tool actually ran in this (Node) report - meaningless for biome's disabled + // placeholder (its only variant is wasm, so there's no ambiguity to lose). + // oxc-parser keeps its suffix even when disabled since it has two placeholder + // rows (napi and wasm) that would otherwise become indistinguishable. + const display_label = $derived( + disabled && category === 'biome' + ? format_label(label).replace(/ \([^)]*\)$/, '') + : format_label(label), + ); -
- {format_label(label)} + + +
+ {display_label}
-
+ {#if !disabled} +
+ {/if}
- {value.value} {value.unit} + + {#if disabled} + n/a + {:else} + {value.value} {value.unit} + {/if} + {#if annotation != null} {annotation} {/if} - {ratio_text} + {#if !disabled}{ratio_text}{/if}
diff --git a/src/routes/docs/benchmarks/BenchmarksConformance.svelte b/src/routes/docs/benchmarks/BenchmarksConformance.svelte new file mode 100644 index 0000000..050dceb --- /dev/null +++ b/src/routes/docs/benchmarks/BenchmarksConformance.svelte @@ -0,0 +1,76 @@ + + +{#each groups as group (group.language)} +
+

+ Parsing {group.files_total.toLocaleString('en-US')} + {LANGUAGE_LABELS[group.language] ?? group.language} files +

+ + + + + + + + + + {#each group.rows as row (row.name)} + + + + + + {/each} + +
files acceptedcoverage
{row.name}{row.files_processed.toLocaleString('en-US')} / {row.files_total.toLocaleString( + 'en-US', + )}{format_coverage_percent(row.coverage_fraction)}
+
+{/each} + + diff --git a/src/routes/docs/benchmarks/BenchmarksCrossRuntime.svelte b/src/routes/docs/benchmarks/BenchmarksCrossRuntime.svelte new file mode 100644 index 0000000..16de023 --- /dev/null +++ b/src/routes/docs/benchmarks/BenchmarksCrossRuntime.svelte @@ -0,0 +1,146 @@ + + +
+ {#if report.mixed_vintage} + + {/if} + {#if report.mixed_machine} + + {/if} + {#if runtime_versions.length} +
    + {#each runtime_versions as { runtime, version } (runtime)} +
  • {runtime} {version}
  • + {/each} +
+ {/if} + {#each groups as group (group.group)} +
+

{group_label(group.operation, group.language)}

+ + + + + {#each runtimes as runtime (runtime)} + + {/each} + {#each others as runtime (runtime)} + + {/each} + + + + {#each group.rows as row (row.name)} + + + {#each runtimes as runtime (runtime)} + + {/each} + {#each others as runtime (runtime)} + {@const ratio = row.ratio_vs_base[runtime]} + + {/each} + + {/each} + +
{runtime}{runtime}/{base}
+ + {format_cross_runtime_label(row.name)} + {format_ops(row.ops_per_second[runtime])} + {ratio != null ? format_speedup(ratio) : 'fail'} +
+
+ {/each} +

+ sweeps/sec — one sweep is a full pass over the group's timed file set (higher is faster); ratios + are vs {base} (> 1 = faster than {base}). A + fail is an implementation that runtime can't load (see notes above). The + native rows load each runtime's idiomatic binding of the same engine — the N-API + addon under node and bun (tsv (node napi)), the C-FFI + library under deno (tsv (deno ffi)) — so the deno column + is a first-class FFI-vs-N-API comparison, not a re-run of the same binding. +

+
+ + diff --git a/src/routes/docs/benchmarks/BenchmarksGroup.svelte b/src/routes/docs/benchmarks/BenchmarksGroup.svelte index d3e2cb8..4ad6e1a 100644 --- a/src/routes/docs/benchmarks/BenchmarksGroup.svelte +++ b/src/routes/docs/benchmarks/BenchmarksGroup.svelte @@ -1,12 +1,6 @@ @@ -35,25 +38,7 @@ {count_label} {group.language} files - {#if has_coverage}files handled / total  ·  speed{/if} + total time  ·  speed

-
- {#each group.entries as entry (entry.name)} - - {/each} -
+
diff --git a/src/routes/docs/benchmarks/BenchmarksMeta.svelte b/src/routes/docs/benchmarks/BenchmarksMeta.svelte index 596ee77..ddea0ef 100644 --- a/src/routes/docs/benchmarks/BenchmarksMeta.svelte +++ b/src/routes/docs/benchmarks/BenchmarksMeta.svelte @@ -1,7 +1,7 @@ -{#if wasm_display.length > 0} -
-

wasm

-
- {#each wasm_display as s (s.label)} - - {/each} -
-
-{/if} - -{#if native_display.length > 0} +{#each size_groups as group (group.capability)}
-

native

-
- {#each native_display as s (s.label)} - - {/each} -
+

{group.heading}

+
-{/if} +{/each} diff --git a/src/routes/docs/benchmarks/benchmark_data.ts b/src/routes/docs/benchmarks/benchmark_data.ts index 9bf0958..4cda956 100644 --- a/src/routes/docs/benchmarks/benchmark_data.ts +++ b/src/routes/docs/benchmarks/benchmark_data.ts @@ -1,7 +1,12 @@ -// Raw types matching the tsv bench.ts `Baseline` format +// Raw types matching the tsv bench's per-runtime report format +// (`benches/js/results/report..json` — the site's flagship detailed +// view is the Node report, the N-API native path) export interface BenchmarkBaseline { version: number; + // The runtime that produced this report (`node` for the flagship view). + // Present from report `version` 5 on. + runtime?: string; timestamp: string; git_commit: string; corpus: Record; @@ -11,23 +16,91 @@ export interface BenchmarkBaseline { // Counts of silenced third-party stderr noise, keyed by message pattern. // Present from baseline `version` 4 on; not rendered, kept for parity. suppressed_noise?: Record; + // Which corpus/surface produced the report: `perf` (real-world corpus, + // format + parse) or `conformance` (the deliberately-hard fixture suites, + // disjoint from the perf corpus, parse only). Present from `version` 6 on. + corpus_kind?: 'perf' | 'conformance'; + // Per-entry corpus composition (path + loaded file count) — discloses which + // sources were present on the machine that produced the report. Present + // from `version` 6 on. + corpus_sources?: Array; + // The machine that produced the report — CPU model, OS/arch, runtime version. + // The throughput numbers are machine-relative, so this is the environment the + // meta panel discloses. Present from `version` 7 on (absent on older reports). + machine?: Machine; } +// The hardware/runtime a report was measured on. Excludes hostname (the reports +// are published) and volatile fields (free memory, load) that would churn. +export interface Machine { + cpu_model: string; + os: string; + arch: string; + // The producing runtime's own version (`node`/`deno`/`bun` version string). + runtime_version: string; +} + +export interface CorpusSource { + path: string; + files: number; + // Per-language split of `files` (svelte/typescript/css counts summing to + // `files`). Present on reports whose loader emitted it; older reports carry + // only the `files` total, so treat it as optional. + by_language?: Partial>; + // The source's GitHub origin, git-detected by the bench at report-build time + // (URL + commit + subpath). Present from `version` 8 on; absent on older + // reports and on sources with no GitHub remote — treat as optional. + repo?: CorpusRepoRef; +} + +// A corpus source's GitHub origin — see `CorpusSource.repo` and `corpus_source_url`. +export interface CorpusRepoRef { + // Canonical https GitHub URL, e.g. `https://github.com/sveltejs/svelte`. + url: string; + // `owner/name` (e.g. `sveltejs/svelte`) — a compact label. + slug: string; + // The commit the corpus was loaded at (full SHA); `''` for a harvested cache + // linked at its canonical upstream root (no pin). + commit: string; + // Path within the repo to this source (`''` = repo root). + subpath: string; +} + +/** + * The GitHub URL for a corpus source, pinned to the measured commit + subpath + * (`…/tree//`) when detected, or the repo root for a + * canonical-upstream cache (empty `commit`). `undefined` when the source has no + * detected origin (older reports, or the local `svelte_styles` cache). + */ +export const corpus_source_url = (source: CorpusSource): string | undefined => { + const repo = source.repo; + if (!repo) return undefined; + if (!repo.commit) return repo.url; + return repo.subpath + ? `${repo.url}/tree/${repo.commit}/${repo.subpath}` + : `${repo.url}/tree/${repo.commit}`; +}; + export interface BaselineEntry { name: string; group: string; - mean_ns: number; - p50_ns: number; - p75_ns: number; - p90_ns: number; - p95_ns: number; - p99_ns: number; - min_ns: number; - max_ns: number; - std_dev_ns: number; - cv: number; - ops_per_second: number; - sample_size: number; + // Timing stats are `null` on a coverage-only report (the conformance surface + // the site refreshes from — parse coverage measured, timed phase skipped). A + // perf report always carries real numbers, and only the perf path + // (`derive_benchmark_groups`) reads these, so the nulls are unreachable there + // but must be expressed for the shared cast to stay sound. + mean_ns: number | null; + p50_ns: number | null; + p75_ns: number | null; + p90_ns: number | null; + p95_ns: number | null; + p99_ns: number | null; + min_ns: number | null; + max_ns: number | null; + std_dev_ns: number | null; + cv: number | null; + ops_per_second: number | null; + sample_size: number | null; // Per-implementation preflight coverage: files this impl processed / the // language's total discovered files. Present from baseline `version` 3 on; // absent (or `null`) in older baselines. @@ -36,6 +109,9 @@ export interface BaselineEntry { // Files this impl was actually timed on (the per-group intersection in // default mode). Present from baseline `version` 4 on. files_iterated?: number | null; + // Present from report `version` 5 on (matches the report's top-level); + // not rendered, kept for parity. + runtime?: string; } export interface BaselineVersions { @@ -84,10 +160,14 @@ export interface BenchmarkDisplayEntry { name: string; mean_ns: number; bar_fraction: number; - speedup_vs_canonical: number | undefined; category: ImplementationCategory; files_processed: number | null; files_total: number | null; + // A placeholder entry mirrored from another language's group for a tool that + // doesn't run in this one (e.g. `oxc-parser` under svelte/css parse) — rendered + // grayed-out and inert so the parse groups share one entry order. Absent on real, + // measured entries. + disabled?: boolean; } export interface SpeedupRow { @@ -105,9 +185,11 @@ const CATEGORY_BY_NAME: Record = { 'acorn-typescript': 'canonical', tsv: 'tsv_native', 'tsv-json': 'tsv_native_json', + 'tsv-json-no-locations': 'tsv_native_json', 'tsv-internal': 'tsv_native', tsv_wasm: 'tsv_wasm', 'tsv_wasm-json': 'tsv_wasm_json', + 'tsv_wasm-json-no-locations': 'tsv_wasm_json', 'tsv_wasm-internal': 'tsv_wasm', 'biome-wasm': 'biome', 'oxc-parser': 'oxc', @@ -115,25 +197,15 @@ const CATEGORY_BY_NAME: Record = { oxfmt: 'oxc', }; -const categorize_name = (name: string): ImplementationCategory => CATEGORY_BY_NAME[name] ?? 'oxc'; +export const categorize_name = (name: string): ImplementationCategory => + CATEGORY_BY_NAME[name] ?? 'oxc'; export const categorize_size = (label: string): ImplementationCategory => { // covers `tsv_wasm` plus the `tsv_format_wasm`/`tsv_parse_wasm` subsets if (label.startsWith('tsv') && label.includes('wasm')) return 'tsv_wasm'; if (label.startsWith('tsv')) return 'tsv_native'; if (label.startsWith('biome')) return 'biome'; - if (label.includes('oxc') || label.includes('oxfmt')) return 'oxc'; - return 'oxc'; -}; - -// Canonical entry names per group -const CANONICAL_BY_GROUP: Record = { - 'parse/svelte': 'svelte/compiler', - 'parse/typescript': 'acorn-typescript', - 'parse/css': 'svelte/compiler', - 'format/svelte': 'prettier', - 'format/typescript': 'prettier', - 'format/css': 'prettier', + return 'oxc'; // oxc-parser / oxfmt, and any unrecognized label }; // Primary tsv entry names for speedup summary (fair comparisons) @@ -142,6 +214,46 @@ const PRIMARY_WASM_FORMAT = 'tsv_wasm'; // Derivation functions +// Shared display order for benchmark groups: format before parse, then by +// language — used by the detailed, conformance, and cross-runtime views alike. +const OPERATION_ORDER: Record = {format: 0, parse: 1}; +const LANGUAGE_ORDER: Record = { + svelte: 0, + typescript: 1, + css: 2, +}; + +/** + * Fixed slot for a format/parse row, applied in place of a size-ordered sort so the + * rows read in a stable, meaningful sequence across every group: the canonical + * reference first (the default 1.0x anchor), then the cross-tool comparisons (biome, + * then oxc), then tsv's JSON-materializing wires (the span-only `no-locations` wire + * before the default `loc`-carrying one), then tsv's raw internal engine. + */ +const speed_entry_rank = (entry: BenchmarkDisplayEntry): number => { + if (entry.category === 'canonical') return 0; + if (entry.category === 'biome') return 1; + if (entry.category === 'oxc') return 2; + if (entry.name.endsWith('-no-locations')) return 3; // tsv json, span-only wire + if (entry.name.endsWith('-json')) return 4; // tsv json, loc-carrying wire + return 5; // tsv-internal / tsv_wasm-internal — raw in-engine, no JS materialization +}; + +/** + * Orders format/parse rows by their fixed `speed_entry_rank` slot, then wasm before + * native within a tier (the browser-relevant build leads each pairing), then by name + * — shared by the initial sort and the re-sort after disabled placeholders are mixed + * in, so every group scans identically. + */ +const compare_speed_entries = (a: BenchmarkDisplayEntry, b: BenchmarkDisplayEntry): number => { + const rank = speed_entry_rank(a) - speed_entry_rank(b); + if (rank !== 0) return rank; + // wasm before native within a tier + const kind = (a.name.includes('wasm') ? 0 : 1) - (b.name.includes('wasm') ? 0 : 1); + if (kind !== 0) return kind; + return a.name.localeCompare(b.name); +}; + export const derive_benchmark_groups = (baseline: BenchmarkBaseline): Array => { const grouped: Map> = new Map(); for (const entry of baseline.entries) { @@ -159,29 +271,27 @@ export const derive_benchmark_groups = (baseline: BenchmarkBaseline): Array e.name === canonical_name); - const slowest = Math.max(...entries.map((e) => e.mean_ns)); + // The sort below leads each group with its canonical reference (Prettier for + // format, the JS baseline for parse), so the first row is the default 1.0x + // anchor; the shared component reads that default off the first row and + // recomputes every ratio, re-baselining onto whichever row is hovered. (Size + // groups lead with their smallest build; see `derive_size_groups`.) + // `?? 0` coerces the coverage-only null (unreachable on a perf report, the + // only kind this runs on) so the display entry's `mean_ns` stays a number. + const slowest = Math.max(...entries.map((e) => e.mean_ns ?? 0)); const display_entries: Array = entries.map((e) => ({ name: e.name, - mean_ns: e.mean_ns, - bar_fraction: slowest > 0 ? e.mean_ns / slowest : 0, - speedup_vs_canonical: - canonical_entry_raw && e.name !== canonical_name - ? canonical_entry_raw.mean_ns / e.mean_ns - : undefined, + mean_ns: e.mean_ns ?? 0, + bar_fraction: slowest > 0 ? (e.mean_ns ?? 0) / slowest : 0, category: categorize_name(e.name), files_processed: e.files_processed ?? null, files_total: e.files_total ?? null, })); - // Sort: canonical first, then by mean_ns descending (slowest first for visual) - display_entries.sort((a, b) => { - if (a.category === 'canonical' && b.category !== 'canonical') return -1; - if (b.category === 'canonical' && a.category !== 'canonical') return 1; - return b.mean_ns - a.mean_ns; - }); + // Fixed order (see `compare_speed_entries`): canonical leads as the default 1.0x + // anchor, then biome, oxc, tsv's json wires, then tsv's internal engine + display_entries.sort(compare_speed_entries); const iterated_counts = entries .map((e) => e.files_iterated) @@ -195,18 +305,131 @@ export const derive_benchmark_groups = (baseline: BenchmarkBaseline): Array = {svelte: 0, typescript: 1, css: 2}; - const OP_ORDER: Record = {format: 0, parse: 1}; result.sort( (a, b) => - (OP_ORDER[a.operation] ?? 9) - (OP_ORDER[b.operation] ?? 9) || - (LANG_ORDER[a.language] ?? 9) - (LANG_ORDER[b.language] ?? 9), + (OPERATION_ORDER[a.operation] ?? 9) - (OPERATION_ORDER[b.operation] ?? 9) || + (LANGUAGE_ORDER[a.language] ?? 9) - (LANGUAGE_ORDER[b.language] ?? 9), ); + // Neither `biome` nor (for svelte/css) `oxc-parser` has a real entry in every + // parse group. `biome`'s `@biomejs/js-api` never exposes a parser to JS at all + // (only formatting and linting), so no parse group has a real biome entry; + // `oxc-parser` only parses TypeScript/JS, so the svelte and css parse groups + // lack it. Mirror both in as disabled placeholders — biome always, oxc-parser + // only where it's missing — then re-sort so they fall into their fixed slots + // (biome then oxc, right after the canonical row), giving all three parse groups + // one shared entry order. + const ts_parse = result.find((g) => g.operation === 'parse' && g.language === 'typescript'); + const oxc_templates = ts_parse?.entries.filter((e) => e.category === 'oxc') ?? []; + for (const group of result) { + if (group.operation !== 'parse') continue; + const biome_placeholder: BenchmarkDisplayEntry = { + name: 'biome-wasm', + mean_ns: 0, + bar_fraction: 0, + category: 'biome', + files_processed: null, + files_total: null, + disabled: true, + }; + const needs_oxc = + group.language !== 'typescript' && !group.entries.some((e) => e.category === 'oxc'); + const oxc_placeholders: Array = needs_oxc + ? oxc_templates.map((e) => ({ + ...e, + bar_fraction: 0, + files_processed: null, + files_total: null, + disabled: true, + })) + : []; + group.entries.push(biome_placeholder, ...oxc_placeholders); + group.entries.sort(compare_speed_entries); + } + return result; }; +// Parse-conformance coverage (the conformance report's headline metric) + +export interface ConformanceRow { + name: string; + files_processed: number; + files_total: number; + // files_processed / files_total, rendered as the coverage percentage + coverage_fraction: number; +} + +export interface ConformanceGroup { + language: string; + // the language's total discovered files (every row shares it) + files_total: number; + rows: Array; +} + +/** + * One coverage row per ENGINE, not per binding: the conformance headline is + * "which files does this parser accept," which is identical across a tool's + * native/wasm/internal variants — so the `-wasm` and `-internal` duplicates + * are dropped and `tsv-json` stands in for tsv (relabeled plainly, since the + * JSON-materialization qualifier is a speed concern, not a coverage one). + */ +const CONFORMANCE_ENGINE_NAMES: Record = { + 'svelte/compiler': 'svelte/compiler', + 'acorn-typescript': 'acorn-typescript', + 'tsv-json': 'tsv', + 'oxc-parser': 'oxc-parser', +}; + +/** + * Derives per-language parse-coverage groups from a conformance report + * (`corpus_kind: 'conformance'` — parse groups only). Rows are ordered by coverage, + * highest first; entries without coverage data are dropped. + */ +export const derive_conformance_groups = (baseline: BenchmarkBaseline): Array => { + const by_language: Map> = new Map(); + for (const entry of baseline.entries) { + const [operation, language] = entry.group.split('/'); + if (operation !== 'parse' || !language) continue; + const display_name = CONFORMANCE_ENGINE_NAMES[entry.name]; + if (!display_name) continue; + if (entry.files_processed == null || entry.files_total == null) continue; + let rows = by_language.get(language); + if (!rows) { + rows = []; + by_language.set(language, rows); + } + rows.push({ + name: display_name, + files_processed: entry.files_processed, + files_total: entry.files_total, + coverage_fraction: entry.files_total > 0 ? entry.files_processed / entry.files_total : 0, + }); + } + + const result: Array = []; + for (const [language, rows] of by_language) { + // coverage descending (highest acceptance first), name as a stable tiebreak + rows.sort((a, b) => b.coverage_fraction - a.coverage_fraction || a.name.localeCompare(b.name)); + result.push({ + language, + files_total: Math.max(0, ...rows.map((r) => r.files_total)), + rows, + }); + } + result.sort((a, b) => (LANGUAGE_ORDER[a.language] ?? 9) - (LANGUAGE_ORDER[b.language] ?? 9)); + return result; +}; + +/** + * Formats a coverage fraction as a percentage with two decimals (`99.85%`), + * FLOORED rather than rounded — rounding would render e.g. 44219/44220 as + * `100.00%` next to a visibly non-total count. Only exact totality reads 100% + * (matching the harness's own `coverage_pct` convention in tsv's report.ts). + */ +export const format_coverage_percent = (fraction: number): string => + `${(Math.floor(fraction * 10_000) / 100).toFixed(2)}%`; + export const derive_speedup_summary = (groups: Array): Array => { const find_speedup = ( operation: string, @@ -236,6 +459,266 @@ export const derive_speedup_summary = (groups: Array): Array { + const has_parse = label.includes('parse'); + const has_format = label.includes('format') || label.includes('fmt'); + // a build that does both is a full toolchain (e.g. the combined oxc-parser + oxfmt entry) + if (has_parse && has_format) return 'full'; + if (has_parse) return 'parser'; // tsv parse (ffi), tsv_parse_wasm, oxc-parser + if (has_format) return 'formatter'; // tsv format, oxfmt + return 'full'; // tsv (napi/ffi), tsv_wasm, biome +}; + +export interface SizeDisplayEntry extends BinarySize { + bar_fraction: number; + category: ImplementationCategory; + // a grayed-out, inert placeholder for a build that doesn't exist (e.g. oxfmt's + // absent wasm build) — no bar, no size, just the label held in its slot. Absent + // on real, measured entries. + disabled?: boolean; +} + +export interface SizeCapabilityGroup { + capability: SizeCapability; + heading: string; + // sorted smallest-first, so the leading entry is the default ratio anchor (1.0x) + entries: Array; +} + +const SIZE_CAPABILITY_ORDER: ReadonlyArray<{ + capability: SizeCapability; + heading: string; +}> = [ + {capability: 'full', heading: 'Full toolchain (parse + format)'}, + {capability: 'formatter', heading: 'Formatter'}, + {capability: 'parser', heading: 'Parser'}, +]; + +/** Display label for the synthesized combined oxc full-toolchain build. */ +export const OXC_FULL_LABEL = 'oxc-parser + oxfmt (napi)'; + +/** Display label for oxfmt's absent wasm build placeholder — see `derive_size_groups`. */ +export const OXFMT_WASM_LABEL = 'oxfmt (wasm)'; + +/** + * Synthesizes oxc's full-toolchain native build by summing its separately-shipped + * parser (`oxc-parser (napi)`) and formatter (`oxfmt (napi)`) packages — together + * they're the closest equivalent to tsv's single parse+format build, so the entry + * stands beside `tsv (napi)` in the full-toolchain group. Returns `undefined` when + * either half is missing (older baselines), and sums gzip only when both carry it. + */ +const synthesize_oxc_full = (sizes: Array): BinarySize | undefined => { + const parser = sizes.find((s) => s.label === 'oxc-parser (napi)'); + const formatter = sizes.find((s) => s.label === 'oxfmt (napi)'); + if (!parser || !formatter) return undefined; + return { + label: OXC_FULL_LABEL, + bytes: parser.bytes + formatter.bytes, + kind: 'native', + gzip_bytes: + parser.gzip_bytes != null && formatter.gzip_bytes != null + ? parser.gzip_bytes + formatter.gzip_bytes + : null, + }; +}; + +/** + * Groups the binary sizes by capability (full / formatter / parser), each group + * mixing wasm and native builds sorted smallest-first. Bars scale to the group's + * largest build; the `vs` ratio anchors on the group's single smallest build, so + * exactly one entry reads 1.0x and every other is a multiple of it. (In the current + * data that smallest build is always one of tsv's, so tsv reads 1.0x and the heavier + * competitors read >1.0x.) A combined `oxc-parser + oxfmt` entry is + * synthesized into the full-toolchain group, since oxc ships parse and format apart. + * oxfmt has no wasm build, so the formatter group gets a disabled `oxfmt (wasm)` + * placeholder slotted just above its real `oxfmt (napi)` entry, holding the slot + * rather than omitting it. + */ +export const derive_size_groups = (sizes: Array): Array => { + const oxc_full = synthesize_oxc_full(sizes); + const all_sizes = oxc_full ? [...sizes, oxc_full] : sizes; + const groups: Array = []; + for (const {capability, heading} of SIZE_CAPABILITY_ORDER) { + const items = all_sizes.filter((s) => categorize_size_capability(s.label) === capability); + if (items.length === 0) continue; + const sorted = items.toSorted((a, b) => a.bytes - b.bytes); + const max = Math.max(0, ...items.map((s) => s.bytes)); + // `sorted` is ascending, so the smallest build leads the group — its single + // default ratio anchor (1.0x), one baseline whether or not the group mixes wasm + // and native (rather than the confusing pair a per-kind anchor produced). The + // shared component reads every ratio from that leading row. + const entries: Array = sorted.map((s) => ({ + ...s, + bar_fraction: max > 0 ? s.bytes / max : 0, + category: categorize_size(s.label), + })); + if (capability === 'formatter' && !entries.some((e) => e.label === OXFMT_WASM_LABEL)) { + const native_index = entries.findIndex((e) => e.label === 'oxfmt (napi)'); + const placeholder: SizeDisplayEntry = { + label: OXFMT_WASM_LABEL, + bytes: 0, + kind: 'wasm', + gzip_bytes: null, + bar_fraction: 0, + category: categorize_size(OXFMT_WASM_LABEL), + disabled: true, + }; + entries.splice(native_index === -1 ? entries.length : native_index, 0, placeholder); + } + groups.push({capability, heading, entries}); + } + return groups; +}; + +// Cross-runtime combined report (the bench composer's `report.json`, `kind: 'combined'`) + +export type BenchmarkRuntime = 'deno' | 'node' | 'bun'; + +export interface CrossRuntimeRow { + group: string; + name: string; + ops_per_second: Partial>; + mean_ns: Partial>; + files_iterated: Partial>; +} + +export interface CrossRuntimeReport { + version: number; + kind: 'combined'; + generated: string; + runtimes: Array; + // The sibling reports came from different commits/versions — ratios are + // unreliable until every runtime is re-run. Present from combined + // `version` 6 on. + mixed_vintage?: boolean; + // The sibling reports were produced on different hardware — ratios are not + // comparable. Present from combined `version` 7 on. + mixed_machine?: boolean; + sources: Array<{ + runtime: BenchmarkRuntime; + timestamp: string; + git_commit: string | null; + tsv: string | null; + // The producing box's machine block; present from combined `version` 7 on. + machine?: Machine | null; + }>; + rows: Array; +} + +export interface CrossRuntimeDisplayRow { + name: string; + category: ImplementationCategory; + ops_per_second: Partial>; + // ratio of each runtime vs the base (first present) runtime; `> 1` = faster + ratio_vs_base: Partial>; +} + +export interface CrossRuntimeGroup { + group: string; + operation: string; + language: string; + rows: Array; +} + +// The combined report stores runtimes deno-first (matching the bench's +// `report.md`); the site presents them node-first (the flagship N-API runtime), +// then deno, then bun. +const CROSS_RUNTIME_DISPLAY_ORDER: Array = ['node', 'deno', 'bun']; + +/** + * The report's runtimes in the site's display order — node (the flagship, the + * ratio anchor) first, then deno, then bun. Shared by `derive_cross_runtime_groups` + * and the table headers so the anchor can't drift between them. + */ +export const order_cross_runtime_runtimes = ( + runtimes: Array, +): Array => CROSS_RUNTIME_DISPLAY_ORDER.filter((r) => runtimes.includes(r)); + +/** One runtime's own version string (`node 24.14.1`), for the cross-runtime section. */ +export interface RuntimeVersion { + runtime: BenchmarkRuntime; + version: string; +} + +/** + * The per-runtime version strings from a cross-runtime report, in the site's + * display order (node first). Drops any runtime whose source carries no machine + * block (reports predating combined `version` 7). The cross-runtime section + * renders these so the three-runtime tables disclose which node/deno/bun version + * each column was measured under; the environment panel above stays scoped to the + * flagship Node baseline. The shared hardware identity (CPU/OS/arch) isn't + * repeated here — it lives in the environment panel, and a per-runtime hardware + * mismatch is the report's `mixed_machine` flag's concern. + */ +export const derive_runtime_versions = (report: CrossRuntimeReport): Array => { + const by_runtime = new Map(report.sources.map((source) => [source.runtime, source])); + const result: Array = []; + for (const runtime of order_cross_runtime_runtimes(report.runtimes)) { + const version = by_runtime.get(runtime)?.machine?.runtime_version; + if (version) result.push({runtime, version}); + } + return result; +}; + +/** + * Groups the combined report's rows by benchmark group, in the same display + * order as `derive_benchmark_groups` (format before parse, then svelte / + * typescript / css). The ratio base is the first runtime in display order + * (node when present — the flagship N-API path), regardless of the report's + * own deno-first storage order. + */ +export const derive_cross_runtime_groups = ( + report: CrossRuntimeReport, +): Array => { + const runtimes = order_cross_runtime_runtimes(report.runtimes); + const base = runtimes[0]; + const grouped: Map> = new Map(); + for (const row of report.rows) { + let rows = grouped.get(row.group); + if (!rows) { + rows = []; + grouped.set(row.group, rows); + } + const base_ops = base ? row.ops_per_second[base] : undefined; + const ratio_vs_base: Partial> = {}; + for (const runtime of runtimes) { + const ops = row.ops_per_second[runtime]; + if (ops != null && base_ops != null && base_ops > 0) { + ratio_vs_base[runtime] = ops / base_ops; + } + } + rows.push({ + name: row.name, + category: categorize_name(row.name), + ops_per_second: row.ops_per_second, + ratio_vs_base, + }); + } + + const result: Array = []; + for (const [group, rows] of grouped) { + const [operation, language] = group.split('/'); + result.push({group, operation: operation!, language: language!, rows}); + } + result.sort( + (a, b) => + (OPERATION_ORDER[a.operation] ?? 9) - (OPERATION_ORDER[b.operation] ?? 9) || + (LANGUAGE_ORDER[a.language] ?? 9) - (LANGUAGE_ORDER[b.language] ?? 9), + ); + return result; +}; + // Formatting utilities export interface FormattedUnit { @@ -261,15 +744,92 @@ export const format_bytes = (bytes: number): FormattedUnit => { }; /** - * Formats per-implementation corpus coverage as `processed/total`, or - * `undefined` when either value is missing (older baselines without coverage). + * Formats a corpus source's file count as a per-language breakdown + * (`124 typescript, 15 svelte, 31 css`), largest language first and dropping + * zero-count languages. Falls back to the plain `N files` total when the report + * predates the per-language split (or lists no recognized language). + */ +export const format_corpus_source_files = (source: CorpusSource): string => { + const total = `${source.files.toLocaleString('en-US')} files`; + if (!source.by_language) return total; + const parts = Object.entries(source.by_language) + .filter((entry): entry is [string, number] => (entry[1] ?? 0) > 0) + .sort((a, b) => b[1] - a[1]) + .map(([language, count]) => `${count.toLocaleString('en-US')} ${language}`); + return parts.length > 0 ? parts.join(', ') : total; +}; + +// Corpus source repos (site-owned path → URL mapping) + +export interface CorpusRepo { + // public repo URL the entry links to + url: string; + // `org/name`, derived from the URL — the linkified display label + label: string; +} + +/** + * Maps a corpus source's on-disk path to its public repo URL. The bench records + * only local paths (`../zzz/src`), so the site owns this mapping — the links then + * survive an `update-benchmarks` refresh without touching the copied JSON. Keyed by + * the repo's directory prefix, each ending in a slash so siblings like `../fuz_css/` + * and `../fuz_code/`, and `../svelte/` vs `../svelte.dev/`, stay distinct. A source + * matching no prefix — the derived `svelte_styles` CSS cache, which isn't a single + * repo — is dropped from the repos list rather than shown unlinked. */ -export const format_coverage = ( - processed: number | null | undefined, - total: number | null | undefined, -): string | undefined => { - if (processed == null || total == null) return undefined; - return `${processed}/${total}`; +const CORPUS_REPO_URL_BY_PREFIX: ReadonlyArray = [ + ['../zzz/', 'https://github.com/fuzdev/zzz'], + ['../fuz_app/', 'https://github.com/fuzdev/fuz_app'], + ['../fuz_blog/', 'https://github.com/fuzdev/fuz_blog'], + ['../fuz_code/', 'https://github.com/fuzdev/fuz_code'], + ['../fuz_css/', 'https://github.com/fuzdev/fuz_css'], + ['../fuz_docs/', 'https://github.com/fuzdev/fuz_docs'], + ['../fuz_gitops/', 'https://github.com/fuzdev/fuz_gitops'], + ['../fuz_mastodon/', 'https://github.com/fuzdev/fuz_mastodon'], + ['../fuz_template/', 'https://github.com/fuzdev/fuz_template'], + ['../fuz_ui/', 'https://github.com/fuzdev/fuz_ui'], + ['../fuz_util/', 'https://github.com/fuzdev/fuz_util'], + ['../mdz/', 'https://github.com/fuzdev/mdz'], + ['../gro/', 'https://github.com/fuzdev/gro'], + ['../svelte-docinfo/', 'https://github.com/fuzdev/svelte-docinfo'], + ['../tsv.fuz.dev/', 'https://github.com/fuzdev/tsv.fuz.dev'], + ['../ryanatkn.com/', 'https://github.com/ryanatkn/ryanatkn.com'], + ['../webdevladder.net/', 'https://github.com/ryanatkn/webdevladder.net'], + ['../kit/', 'https://github.com/sveltejs/kit'], + ['../svelte.dev/', 'https://github.com/sveltejs/svelte.dev'], + ['../svelte/', 'https://github.com/sveltejs/svelte'], +]; + +/** The repo URL for a corpus source path, or `undefined` when it maps to no repo. */ +const corpus_repo_url = (path: string): string | undefined => + CORPUS_REPO_URL_BY_PREFIX.find(([prefix]) => path.startsWith(prefix))?.[1]; + +/** `org/name` from a `https://github.com/org/name` URL — the linkified label. */ +const corpus_repo_label = (url: string): string => new URL(url).pathname.slice(1); + +/** + * The distinct source repos behind a report's corpus, one entry per URL in + * first-seen order (the author's ecosystem leads, the upstream framework repos + * trail, matching the source order). Sources sharing a repo (svelte.dev's several + * packages) collapse to one entry; sources with no mapped repo (the `svelte_styles` + * CSS cache) are dropped. URLs come from the site-owned `CORPUS_REPO_URL_BY_PREFIX`, + * not the report, so the links survive a refresh. + */ +export const derive_corpus_repos = ( + sources: Array | undefined, +): Array => { + const by_url: Map = new Map(); + for (const source of sources ?? []) { + // Prefer the report's git-detected repo; fall back to the legacy prefix + // map for reports predating `version` 8 (which lack `source.repo`). + const url = source.repo?.url ?? corpus_repo_url(source.path); + if (!url || by_url.has(url)) continue; + by_url.set(url, { + url, + label: source.repo?.slug ?? corpus_repo_label(url), + }); + } + return [...by_url.values()]; }; /** @@ -287,10 +847,55 @@ export const format_gzip_size = (gzip_bytes: number | null | undefined): string export const format_speedup = (ratio: number): string => ratio >= 10 ? `${ratio.toFixed(1)}x` : `${ratio.toFixed(2)}x`; +/** + * Signed speedup: entries at or above the anchor read as a plain multiple + * (`2.50x`), while slower entries show the reciprocal negated (`0.15x` → `-6.67x`) + * so "how many times slower" is directly legible instead of a fraction the reader + * has to invert. The minus is a convention for "times slower", not a literal + * negative rate. Used for the format/parse speed bars, where entries span widely + * on both sides of the anchor; the near-parity cross-runtime table and the + * bigger-is-worse size ratios stay on the plain fractional `format_speedup`. + */ +export const format_speedup_signed = (ratio: number): string => { + const magnitude = ratio >= 1 ? ratio : 1 / ratio; + const digits = magnitude >= 10 ? 1 : 2; + return `${ratio < 1 ? '-' : ''}${magnitude.toFixed(digits)}x`; +}; + /** Hyphenated tool names that should preserve their hyphens in display labels. */ const HYPHENATED_NAMES = ['acorn-typescript', 'oxc-parser']; +/** + * Display labels for the raw benchmark entry names in the main (Node) tables, + * annotating each with the runtime **and** binding it runs under — the native + * builds load the N-API addon under Node, so they read `(node napi)` to + * distinguish them from the Deno FFI numbers the cross-runtime table surfaces + * (`tsv (deno ffi)`); the third-party wasm builds are marked `(wasm)`. Mirrors + * the parenthesized suffixes the binary-size section's labels already carry. + * tsv's own wasm entries keep their `tsv_wasm` package-name style, as in the size + * groups, so they aren't listed here. The already-parenthesized size labels + * aren't keys, so they fall through to the generic formatting below unchanged. + * The cross-runtime table neutralizes the `(node napi)` suffix per row (its + * columns span runtimes) via `format_cross_runtime_label`. + */ +const LABEL_OVERRIDES: Record = { + tsv: 'tsv (node napi)', + 'tsv-json': 'tsv json (node napi)', + // `no-locs` (not `no-locations`) — the full word eats too much column width. + 'tsv-json-no-locations': 'tsv json no-locs (node napi)', + // the one tsv_wasm entry listed here: the generic formatting below would + // break the `no-locs` hyphen its native sibling deliberately keeps + 'tsv_wasm-json-no-locations': 'tsv_wasm json no-locs', + 'tsv-internal': 'tsv internal (node napi)', + 'oxc-parser': 'oxc-parser (node napi)', + oxfmt: 'oxfmt (node napi)', + 'biome-wasm': 'biome (wasm)', + 'oxc-parser-wasm': 'oxc-parser (wasm)', +}; + export const format_label = (name: string): string => { + const override = LABEL_OVERRIDES[name]; + if (override) return override; for (const tool of HYPHENATED_NAMES) { if (name.startsWith(tool)) { return tool + name.slice(tool.length).replaceAll('-', ' '); @@ -299,6 +904,16 @@ export const format_label = (name: string): string => { return name.replaceAll('-', ' '); }; +/** + * The label for a cross-runtime row. Each row spans node/deno/bun columns, and a + * native build's binding is runtime-specific (N-API on node & bun, C-FFI on + * deno), so the row can't pin one binding — neutralize `format_label`'s + * node-centric `(node napi)` suffix to `(native)`. The per-runtime binding is + * disclosed once in the table caption instead (see `BenchmarksCrossRuntime`). + */ +export const format_cross_runtime_label = (name: string): string => + format_label(name).replace(' (node napi)', ' (native)'); + /** Returns a CSS background color variable for a category. */ export const category_color = (category: ImplementationCategory): string => { switch (category) { @@ -319,11 +934,17 @@ export const category_color = (category: ImplementationCategory): string => { } }; -/** Returns a CSS color variable for a size ratio (inverted — bigger is worse). */ +/** + * Returns a CSS color variable for a size ratio (inverted — bigger is worse). + * Green is reserved for builds smaller than the baseline (ratio < 1, only reachable + * once a hover re-baselines onto a larger build); any build at or above the baseline + * reads yellow at the floor, ramping through orange to red as it grows. + */ export const size_ratio_color = (ratio: number): string => { - if (ratio < 2) return 'var(--color_b_50)'; // green — similar - if (ratio < 5) return 'var(--color_e_50)'; // yellow — notably larger - return 'var(--color_c_50)'; // red — much larger + if (ratio < 1) return 'var(--color_b_50)'; // green — smaller than baseline + if (ratio < 3) return 'var(--color_e_50)'; // yellow — larger + if (ratio < 10) return 'var(--color_h_50)'; // orange — much larger + return 'var(--color_c_50)'; // red — enormous }; /** Returns a CSS color variable for the speedup ratio. */ @@ -334,3 +955,73 @@ export const speedup_color = (ratio: number): string => { if (ratio < 5) return 'var(--color_b_50)'; // green — fast return 'var(--color_j_50)'; // teal — exceptional }; + +/** + * Cross-runtime ratio cell background: a stable fuz_css red (`color_c`) / green + * (`color_b`) whose ALPHA varies with distance from parity. Fully transparent at ratio + * `1.0` (parity recedes), ramping to `0.3` alpha at ratio `0.8` and below (red) or `1.2` + * and above (green). The hue stays constant — only opacity moves — so it reads + * consistently in light and dark themes while the cell's text keeps the default color. + * Deliberately its own scale — NOT the shared `speedup_color` — because cross-runtime + * deltas cluster tightly near 1.0 and this must not bleed into the other displays. + */ +export const cross_runtime_ratio_background = (ratio: number): string => { + // 0 alpha at ratio 1.0, up to 0.3 at ratio ≤ 0.8 (red) or ≥ 1.2 (green) + const alpha = Math.min(1, Math.abs(ratio - 1) / 0.2) * 0.3; + const color = ratio < 1 ? 'var(--color_c_50)' : 'var(--color_b_50)'; + return `color-mix(in srgb, ${color} ${(alpha * 100).toFixed(1)}%, transparent)`; +}; + +// Interactive baseline (hover-to-rebaseline) + +/** + * Which way a group's ratio runs, so re-anchoring on hover stays consistent. A + * `speed` group (format/parse) reads its anchor as a reference speed — faster + * entries are positive multiples, slower ones negative (`format_speedup_signed` / + * `speedup_color`). A `size` group reads its anchor as a reference size — bigger + * builds are multiples ≥ 1 (`format_size_ratio` / `size_ratio_color`). The two + * ratios are reciprocals, which is exactly why one direction flag suffices. + */ +export type BaselineDirection = 'speed' | 'size'; + +/** An entry's ratio against its group's anchor, in the group's direction. */ +export const compute_baseline_ratio = ( + direction: BaselineDirection, + entry_raw: number, + anchor_raw: number, +): number => (direction === 'speed' ? anchor_raw / entry_raw : entry_raw / anchor_raw); + +/** Plain size ratio (`2.3x`) — its own formatter since sizes never take the signed treatment. */ +export const format_size_ratio = (ratio: number): string => `${ratio.toFixed(1)}x`; + +/** Formats a baseline ratio for display in the given direction. */ +export const format_baseline_ratio = (direction: BaselineDirection, ratio: number): string => + direction === 'speed' ? format_speedup_signed(ratio) : format_size_ratio(ratio); + +/** Color for a baseline ratio in the given direction. */ +export const baseline_ratio_color = (direction: BaselineDirection, ratio: number): string => + direction === 'speed' ? speedup_color(ratio) : size_ratio_color(ratio); + +/** + * A normalized row for `BenchmarksBaselineGroup` — the shared hover-to-rebaseline + * column behind the format, parse, and binary-size groups. `raw` is the number the + * ratio derives from (sweep mean ns for speed, bytes for size); `value` is the + * row's displayed measurement (the whole-sweep mean — total time over the group's + * iterated corpus — for speed, bytes for size). Flattening the group-specific + * display entries to this one shape lets a single component own the anchor state + * for all three sections. + */ +export interface BaselineRow { + // stable identity for `#each` and anchor matching (the entry name / size label) + key: string; + // raw name/label; `BenchmarksBar` formats it for display + label: string; + category: ImplementationCategory; + bar_fraction: number; + value: FormattedUnit; + // the number the ratio derives from — mean ns (speed) or bytes (size) + raw: number; + annotation: string | undefined; + // grayed-out, inert placeholder — never an anchor, no hover highlight + disabled: boolean; +} diff --git a/src/routes/docs/benchmarks/benchmarks.json b/src/routes/docs/benchmarks/benchmarks.json index bf56417..eefd677 100644 --- a/src/routes/docs/benchmarks/benchmarks.json +++ b/src/routes/docs/benchmarks/benchmarks.json @@ -1,685 +1,1162 @@ { - "version": 4, - "timestamp": "2026-06-12T09:22:00.588Z", - "git_commit": "98918f9", + "version": 7, + "runtime": "node", + "corpus_kind": "perf", + "timestamp": "2026-07-16T11:43:29.034Z", + "git_commit": "135b7b93", + "machine": { + "cpu_model": "AMD Ryzen 5 PRO 7530U with Radeon Graphics", + "os": "linux", + "arch": "x86_64", + "runtime_version": "24.14.1" + }, "corpus": { - "svelte": 1240, - "typescript": 4025, - "css": 193 + "svelte": 763, + "typescript": 2437, + "css": 49 }, + "corpus_sources": [ + { + "path": "../zzz/src", + "files": 326, + "by_language": { + "svelte": 173, + "typescript": 152, + "css": 1 + }, + "repo": { + "url": "https://github.com/fuzdev/zzz", + "slug": "fuzdev/zzz", + "commit": "838d499c7cc746ebe1f5da8470e7679c8895aee0", + "subpath": "src" + } + }, + { + "path": "../fuz_app/src", + "files": 665, + "by_language": { + "svelte": 33, + "typescript": 631, + "css": 1 + }, + "repo": { + "url": "https://github.com/fuzdev/fuz_app", + "slug": "fuzdev/fuz_app", + "commit": "a0e63a732b979df8dd7ae6730548227cca4bc79c", + "subpath": "src" + } + }, + { + "path": "../fuz_blog/src", + "files": 37, + "by_language": { + "svelte": 20, + "typescript": 16, + "css": 1 + }, + "repo": { + "url": "https://github.com/fuzdev/fuz_blog", + "slug": "fuzdev/fuz_blog", + "commit": "7147f354fac7dcb535f70f241db1dd3543c0a370", + "subpath": "src" + } + }, + { + "path": "../fuz_code/src", + "files": 66, + "by_language": { + "svelte": 17, + "typescript": 46, + "css": 3 + }, + "repo": { + "url": "https://github.com/fuzdev/fuz_code", + "slug": "fuzdev/fuz_code", + "commit": "7b3664851737def05ac8d4b646b27643cfb4a39f", + "subpath": "src" + } + }, + { + "path": "../fuz_css/src", + "files": 146, + "by_language": { + "svelte": 39, + "typescript": 105, + "css": 2 + }, + "repo": { + "url": "https://github.com/fuzdev/fuz_css", + "slug": "fuzdev/fuz_css", + "commit": "b0b02e804d3db586c37851754401e3f57b449345", + "subpath": "src" + } + }, + { + "path": "../fuz_docs/src", + "files": 65, + "by_language": { + "svelte": 45, + "typescript": 19, + "css": 1 + }, + "repo": { + "url": "https://github.com/fuzdev/fuz_docs", + "slug": "fuzdev/fuz_docs", + "commit": "40b3d2ad892d4434088f6da83e54f7cbf69615e0", + "subpath": "src" + } + }, + { + "path": "../fuz_gitops/src", + "files": 99, + "by_language": { + "svelte": 28, + "typescript": 70, + "css": 1 + }, + "repo": { + "url": "https://github.com/fuzdev/fuz_gitops", + "slug": "fuzdev/fuz_gitops", + "commit": "6d1a90269e16559259d7660ba1b7b119da5967ac", + "subpath": "src" + } + }, + { + "path": "../fuz_mastodon/src", + "files": 25, + "by_language": { + "svelte": 15, + "typescript": 9, + "css": 1 + }, + "repo": { + "url": "https://github.com/fuzdev/fuz_mastodon", + "slug": "fuzdev/fuz_mastodon", + "commit": "6179d626e50dd1a53540a074fc23c354c2985321", + "subpath": "src" + } + }, + { + "path": "../fuz_template/src", + "files": 16, + "by_language": { + "svelte": 10, + "typescript": 5, + "css": 1 + }, + "repo": { + "url": "https://github.com/fuzdev/fuz_template", + "slug": "fuzdev/fuz_template", + "commit": "c5c0c0332b811c983d4e60031452f668d163d28d", + "subpath": "src" + } + }, + { + "path": "../fuz_ui/src", + "files": 216, + "by_language": { + "svelte": 121, + "typescript": 95, + "css": 0 + }, + "repo": { + "url": "https://github.com/fuzdev/fuz_ui", + "slug": "fuzdev/fuz_ui", + "commit": "311a8549508ec9c3d0c6ebc895c82d42321daeb3", + "subpath": "src" + } + }, + { + "path": "../fuz_util/src", + "files": 145, + "by_language": { + "svelte": 8, + "typescript": 136, + "css": 1 + }, + "repo": { + "url": "https://github.com/fuzdev/fuz_util", + "slug": "fuzdev/fuz_util", + "commit": "9a9a0f941b14481dc4b96becdacdf307e071af39", + "subpath": "src" + } + }, + { + "path": "../mdz/src", + "files": 71, + "by_language": { + "svelte": 22, + "typescript": 48, + "css": 1 + }, + "repo": { + "url": "https://github.com/fuzdev/mdz", + "slug": "fuzdev/mdz", + "commit": "f5dbce0755f74961da0636079714e4e00bf667f0", + "subpath": "src" + } + }, + { + "path": "../gro/src", + "files": 156, + "by_language": { + "svelte": 10, + "typescript": 146, + "css": 0 + }, + "repo": { + "url": "https://github.com/fuzdev/gro", + "slug": "fuzdev/gro", + "commit": "32af831867a0e73f98b4a6f2d35f435e21fea7db", + "subpath": "src" + } + }, + { + "path": "../svelte-docinfo/src", + "files": 99, + "by_language": { + "svelte": 22, + "typescript": 76, + "css": 1 + }, + "repo": { + "url": "https://github.com/fuzdev/svelte-docinfo", + "slug": "fuzdev/svelte-docinfo", + "commit": "39e10bdff2ca77a82a8c017bbff004cc2f809aa0", + "subpath": "src" + } + }, + { + "path": "../tsv.fuz.dev/src", + "files": 28, + "by_language": { + "svelte": 16, + "typescript": 11, + "css": 1 + }, + "repo": { + "url": "https://github.com/fuzdev/tsv.fuz.dev", + "slug": "fuzdev/tsv.fuz.dev", + "commit": "b4259d21b582908e9e68ad9c1f774b44d0315951", + "subpath": "src" + } + }, + { + "path": "../ryanatkn.com/src", + "files": 52, + "by_language": { + "svelte": 31, + "typescript": 20, + "css": 1 + }, + "repo": { + "url": "https://github.com/ryanatkn/ryanatkn.com", + "slug": "ryanatkn/ryanatkn.com", + "commit": "5e53453ae56ff1139f0bf1e49f7d4ccedc4994a7", + "subpath": "src" + } + }, + { + "path": "../webdevladder.net/src", + "files": 39, + "by_language": { + "svelte": 24, + "typescript": 14, + "css": 1 + }, + "repo": { + "url": "https://github.com/ryanatkn/webdevladder.net", + "slug": "ryanatkn/webdevladder.net", + "commit": "985945c986e651a6b0a5090cab1b7879d9143c41", + "subpath": "src" + } + }, + { + "path": "benches/js/.cache/svelte_styles", + "files": 18, + "by_language": { + "svelte": 0, + "typescript": 0, + "css": 18 + } + }, + { + "path": "../kit/packages/kit/src", + "files": 297, + "by_language": { + "svelte": 26, + "typescript": 271, + "css": 0 + }, + "repo": { + "url": "https://github.com/sveltejs/kit", + "slug": "sveltejs/kit", + "commit": "da5b08ea7f5182f505b5d1cb9b347effbcb35231", + "subpath": "packages/kit/src" + } + }, + { + "path": "../svelte/packages/svelte/src", + "files": 415, + "by_language": { + "svelte": 0, + "typescript": 415, + "css": 0 + }, + "repo": { + "url": "https://github.com/sveltejs/svelte", + "slug": "sveltejs/svelte", + "commit": "b4d1583ae20f3869a88a731d9a265c546c099f66", + "subpath": "packages/svelte/src" + } + }, + { + "path": "../svelte.dev/apps/svelte.dev/src", + "files": 145, + "by_language": { + "svelte": 60, + "typescript": 85, + "css": 0 + }, + "repo": { + "url": "https://github.com/sveltejs/svelte.dev", + "slug": "sveltejs/svelte.dev", + "commit": "c21c2d0f011915b2a783c612c978a348cec98a5c", + "subpath": "apps/svelte.dev/src" + } + }, + { + "path": "../svelte.dev/packages/repl/src", + "files": 53, + "by_language": { + "svelte": 17, + "typescript": 34, + "css": 2 + }, + "repo": { + "url": "https://github.com/sveltejs/svelte.dev", + "slug": "sveltejs/svelte.dev", + "commit": "c21c2d0f011915b2a783c612c978a348cec98a5c", + "subpath": "packages/repl/src" + } + }, + { + "path": "../svelte.dev/packages/site-kit/src", + "files": 70, + "by_language": { + "svelte": 26, + "typescript": 33, + "css": 11 + }, + "repo": { + "url": "https://github.com/sveltejs/svelte.dev", + "slug": "sveltejs/svelte.dev", + "commit": "c21c2d0f011915b2a783c612c978a348cec98a5c", + "subpath": "packages/site-kit/src" + } + } + ], "versions": { "tsv": "0.1.0", - "svelte": "5.56.1", + "svelte": "5.56.4", "acorn": "8.16.0", - "acorn_ts": "1.0.10", - "prettier": "3.8.3", - "prettier_svelte": "3.5.2", - "oxc_parser": "0.134.0", - "oxfmt": "0.53.0", - "biome": "2.4.16" + "acorn_ts": "1.0.11", + "prettier": "3.9.0", + "prettier_svelte": "4.1.1", + "oxc_parser": "0.140.0", + "oxfmt": "0.59.0", + "biome": "2.5.4" }, "binary_sizes": [ { - "label": "tsv (native)", - "bytes": 3861760, + "label": "tsv (ffi)", + "bytes": 3407520, "kind": "native", - "gzip_bytes": 1622771 + "gzip_bytes": 1473224 + }, + { + "label": "tsv format (ffi)", + "bytes": 3143024, + "kind": "native", + "gzip_bytes": 1366105 + }, + { + "label": "tsv parse (ffi)", + "bytes": 1602672, + "kind": "native", + "gzip_bytes": 704951 + }, + { + "label": "tsv (napi)", + "bytes": 3540848, + "kind": "native", + "gzip_bytes": 1517414 }, { "label": "tsv_format_wasm", - "bytes": 2196292, + "bytes": 2261397, "kind": "wasm", - "gzip_bytes": 715422 + "gzip_bytes": 794650 }, { "label": "tsv_parse_wasm", - "bytes": 1699625, + "bytes": 1057603, "kind": "wasm", - "gzip_bytes": 526578 + "gzip_bytes": 389582 }, { "label": "tsv_wasm", - "bytes": 2888918, + "bytes": 2491464, "kind": "wasm", - "gzip_bytes": 911323 + "gzip_bytes": 875399 }, { "label": "biome (wasm)", - "bytes": 34430368, + "bytes": 38584187, "kind": "wasm", - "gzip_bytes": 8187702 + "gzip_bytes": 9281487 }, { - "label": "oxc-parser (native)", - "bytes": 2658008, + "label": "oxc-parser (napi)", + "bytes": 2354904, "kind": "native", - "gzip_bytes": 1032774 + "gzip_bytes": 954754 }, { - "label": "oxfmt (native)", - "bytes": 8016464, + "label": "oxfmt (napi)", + "bytes": 8917776, "kind": "native", - "gzip_bytes": 3224684 + "gzip_bytes": 3586269 }, { "label": "oxc-parser (wasm)", - "bytes": 1866798, + "bytes": 1531455, "kind": "wasm", - "gzip_bytes": 518682 + "gzip_bytes": 495227 } ], "entries": [ { "name": "svelte/compiler", "group": "parse/svelte", - "mean_ns": 353647929.8666667, - "p50_ns": 351794280, - "p75_ns": 355710613.5, - "p90_ns": 358757843.8, - "p95_ns": 359832344.1, - "p99_ns": 361455435.21999997, - "min_ns": 349590498, - "max_ns": 361861208, - "std_dev_ns": 3732468.9277879535, - "cv": 0.010554194193064213, - "ops_per_second": 2.8276710127414653, - "sample_size": 15, - "files_processed": 1193, - "files_total": 1240, - "files_iterated": 1192 + "mean_ns": 454203272.7, + "p50_ns": 452307877, + "p75_ns": 458399053.5, + "p90_ns": 464037260, + "p95_ns": 466877383, + "p99_ns": 469149481.4, + "min_ns": 449538372, + "max_ns": 469717506, + "std_dev_ns": 4803639.789213478, + "cv": 0.01057596912646261, + "ops_per_second": 2.201657407828713, + "sample_size": 10, + "files_processed": 763, + "files_total": 763, + "files_iterated": 763, + "runtime": "node" }, { "name": "tsv-json", "group": "parse/svelte", - "mean_ns": 485025992.4444444, - "p50_ns": 485086813, - "p75_ns": 487644066, - "p90_ns": 491656222, - "p95_ns": 494258617.5, - "p99_ns": 496340533.9, - "min_ns": 481783564, - "max_ns": 496861013, - "std_dev_ns": 1892163.1540198317, - "cv": 0.0039011582543930627, - "ops_per_second": 2.061745175676418, - "sample_size": 9, - "files_processed": 1202, - "files_total": 1240, - "files_iterated": 1192 + "mean_ns": 215081564.7368421, + "p50_ns": 215189540, + "p75_ns": 216663830.5, + "p90_ns": 220507872.8, + "p95_ns": 221245016.95, + "p99_ns": 221476217.61, + "min_ns": 213977606, + "max_ns": 221507381, + "std_dev_ns": 948167.8303000326, + "cv": 0.004408410509102166, + "ops_per_second": 4.649398944179739, + "sample_size": 19, + "files_processed": 763, + "files_total": 763, + "files_iterated": 763, + "runtime": "node" + }, + { + "name": "tsv-json-no-locations", + "group": "parse/svelte", + "mean_ns": 136455370.6875, + "p50_ns": 136247751, + "p75_ns": 138650167, + "p90_ns": 140117388, + "p95_ns": 140433077.8, + "p99_ns": 140673837.16, + "min_ns": 134355882, + "max_ns": 140777218, + "std_dev_ns": 1333831.388734515, + "cv": 0.009774854459845023, + "ops_per_second": 7.328403381719038, + "sample_size": 32, + "files_processed": 763, + "files_total": 763, + "files_iterated": 763, + "runtime": "node" }, { "name": "tsv_wasm-json", "group": "parse/svelte", - "mean_ns": 515988848.71428573, - "p50_ns": 516072939.5, - "p75_ns": 517319991, - "p90_ns": 519264292.5, - "p95_ns": 520772636.25, - "p99_ns": 521979311.25, - "min_ns": 515560589, - "max_ns": 522280980, - "std_dev_ns": 253147.57598596797, - "cv": 0.0004906066800023837, - "ops_per_second": 1.9380263788485899, - "sample_size": 7, - "files_processed": 1202, - "files_total": 1240, - "files_iterated": 1192 + "mean_ns": 242380851.47619048, + "p50_ns": 241554679, + "p75_ns": 243217969, + "p90_ns": 246015818, + "p95_ns": 247299957, + "p99_ns": 247768187.4, + "min_ns": 239665776, + "max_ns": 247885245, + "std_dev_ns": 2512562.793305207, + "cv": 0.010366176940145046, + "ops_per_second": 4.125738456275007, + "sample_size": 21, + "files_processed": 763, + "files_total": 763, + "files_iterated": 763, + "runtime": "node" + }, + { + "name": "tsv_wasm-json-no-locations", + "group": "parse/svelte", + "mean_ns": 156798038.8, + "p50_ns": 156579356.5, + "p75_ns": 157972638, + "p90_ns": 160523542.9, + "p95_ns": 161067885.6, + "p99_ns": 161407787.75, + "min_ns": 154628462, + "max_ns": 161516032, + "std_dev_ns": 1713706.0912863356, + "cv": 0.010929384732115256, + "ops_per_second": 6.377630789601431, + "sample_size": 30, + "files_processed": 763, + "files_total": 763, + "files_iterated": 763, + "runtime": "node" }, { "name": "tsv-internal", "group": "parse/svelte", - "mean_ns": 37041735.949579835, - "p50_ns": 36817941, - "p75_ns": 38031003.5, - "p90_ns": 39745595, - "p95_ns": 49374707.5, - "p99_ns": 70563356.69999997, - "min_ns": 35490755, - "max_ns": 73560234, - "std_dev_ns": 1041838.039960544, - "cv": 0.02812605870790355, - "ops_per_second": 26.996574927297463, - "sample_size": 119, - "files_processed": 1202, - "files_total": 1240, - "files_iterated": 1192 + "mean_ns": 20890458.785714287, + "p50_ns": 20811285.5, + "p75_ns": 21412932, + "p90_ns": 21589212.6, + "p95_ns": 21645572.95, + "p99_ns": 21758236.37, + "min_ns": 20633297, + "max_ns": 21784005, + "std_dev_ns": 269196.59333167557, + "cv": 0.012886102507033629, + "ops_per_second": 47.86874286762142, + "sample_size": 196, + "files_processed": 763, + "files_total": 763, + "files_iterated": 763, + "runtime": "node" }, { "name": "tsv_wasm-internal", "group": "parse/svelte", - "mean_ns": 45254932.2745098, - "p50_ns": 45241227, - "p75_ns": 45348407.5, - "p90_ns": 45449227, - "p95_ns": 45624719, - "p99_ns": 45694463.7, - "min_ns": 45087137, - "max_ns": 45720491, - "std_dev_ns": 94029.59204496059, - "cv": 0.0020777755554817943, - "ops_per_second": 22.097038924600444, - "sample_size": 102, - "files_processed": 1202, - "files_total": 1240, - "files_iterated": 1192 + "mean_ns": 37349926.47014926, + "p50_ns": 38076093, + "p75_ns": 41060368.5, + "p90_ns": 41948649.5, + "p95_ns": 42197316.75, + "p99_ns": 43256069.93999999, + "min_ns": 29812236, + "max_ns": 44793850, + "std_dev_ns": 4259994.387091226, + "cv": 0.11405629916021097, + "ops_per_second": 26.773814422344802, + "sample_size": 134, + "files_processed": 763, + "files_total": 763, + "files_iterated": 763, + "runtime": "node" }, { "name": "prettier", "group": "format/svelte", - "mean_ns": 3156053205.2, - "p50_ns": 3153333425, - "p75_ns": 3161183958, - "p90_ns": 3191384359.8, - "p95_ns": 3201451160.4, - "p99_ns": 3209504600.88, - "min_ns": 3116677318, - "max_ns": 3211517961, - "std_dev_ns": 35365115.51584766, - "cv": 0.011205487745763958, - "ops_per_second": 0.316851439117811, - "sample_size": 5, - "files_processed": 1201, - "files_total": 1240, - "files_iterated": 1194 + "mean_ns": 4410834541, + "p50_ns": 4409030122, + "p75_ns": 4424887650.5, + "p90_ns": 4449574229, + "p95_ns": 4463048301.5, + "p99_ns": 4473827559.5, + "min_ns": 4398618080, + "max_ns": 4476522374, + "std_dev_ns": 12538592.212289287, + "cv": 0.002842680244688255, + "ops_per_second": 0.2267144665492906, + "sample_size": 6, + "files_processed": 763, + "files_total": 763, + "files_iterated": 763, + "runtime": "node" }, { "name": "tsv", "group": "format/svelte", - "mean_ns": 253589550.66666666, - "p50_ns": 250899714, - "p75_ns": 261502311.75, - "p90_ns": 270307677.1, - "p95_ns": 291692469.25, - "p99_ns": 299630277.84999996, - "min_ns": 247143299, - "max_ns": 301614730, - "std_dev_ns": 6469364.524097994, - "cv": 0.02551116363860637, - "ops_per_second": 3.9433801486342004, - "sample_size": 18, - "files_processed": 1202, - "files_total": 1240, - "files_iterated": 1194 + "mean_ns": 69682693.5, + "p50_ns": 69152510, + "p75_ns": 71057711.5, + "p90_ns": 71481026.5, + "p95_ns": 71580183.45, + "p99_ns": 71657562.64, + "min_ns": 68466829, + "max_ns": 71705144, + "std_dev_ns": 1150612.102643583, + "cv": 0.01651216456843166, + "ops_per_second": 14.35076558858908, + "sample_size": 72, + "files_processed": 763, + "files_total": 763, + "files_iterated": 763, + "runtime": "node" }, { "name": "tsv_wasm", "group": "format/svelte", - "mean_ns": 348305291.1666667, - "p50_ns": 348407825, - "p75_ns": 349005457.5, - "p90_ns": 351966927.4, - "p95_ns": 353359477.1, - "p99_ns": 355469057.02, - "min_ns": 347712508, - "max_ns": 355996452, - "std_dev_ns": 403574.7899962474, - "cv": 0.0011586811921359351, - "ops_per_second": 2.8710445272032703, - "sample_size": 12, - "files_processed": 1202, - "files_total": 1240, - "files_iterated": 1194 + "mean_ns": 126127615.175, + "p50_ns": 126569065, + "p75_ns": 128091580.75, + "p90_ns": 129881477.4, + "p95_ns": 130874369.55, + "p99_ns": 131097384.84, + "min_ns": 119589561, + "max_ns": 131122908, + "std_dev_ns": 3059562.225237535, + "cv": 0.024257671256151497, + "ops_per_second": 7.928477824721544, + "sample_size": 40, + "files_processed": 763, + "files_total": 763, + "files_iterated": 763, + "runtime": "node" }, { "name": "oxfmt", "group": "format/svelte", - "mean_ns": 3363123440.8, - "p50_ns": 3355465187, - "p75_ns": 3380464215, - "p90_ns": 3387858512.4, - "p95_ns": 3390323278.2, - "p99_ns": 3392295090.84, - "min_ns": 3336319057, - "max_ns": 3392788044, - "std_dev_ns": 22995300.045456335, - "cv": 0.006837483205786329, - "ops_per_second": 0.2973426392467253, - "sample_size": 5, - "files_processed": 1201, - "files_total": 1240, - "files_iterated": 1194 + "mean_ns": 4349035130.714286, + "p50_ns": 4378245513, + "p75_ns": 4397739938.5, + "p90_ns": 4399973164.6, + "p95_ns": 4400683105.3, + "p99_ns": 4401251057.86, + "min_ns": 4277748939, + "max_ns": 4401393046, + "std_dev_ns": 56604561.29704797, + "cv": 0.013015429766774783, + "ops_per_second": 0.22993605936582995, + "sample_size": 7, + "files_processed": 763, + "files_total": 763, + "files_iterated": 763, + "runtime": "node" }, { "name": "biome-wasm", "group": "format/svelte", - "mean_ns": 651628305.75, - "p50_ns": 651348032.5, - "p75_ns": 652776746.25, - "p90_ns": 655602311.7, - "p95_ns": 657068072.85, - "p99_ns": 658240681.77, - "min_ns": 646287594, - "max_ns": 658533834, - "std_dev_ns": 3695077.8107031505, - "cv": 0.005670529929558927, - "ops_per_second": 1.5346171907757093, - "sample_size": 8, - "files_processed": 1238, - "files_total": 1240, - "files_iterated": 1194 + "mean_ns": 922724214.3333334, + "p50_ns": 921258645, + "p75_ns": 925929470, + "p90_ns": 931597465, + "p95_ns": 933881452, + "p99_ns": 935708641.6, + "min_ns": 912930502, + "max_ns": 936165439, + "std_dev_ns": 8104189.841280956, + "cv": 0.008782894948883745, + "ops_per_second": 1.0837474344623093, + "sample_size": 6, + "files_processed": 763, + "files_total": 763, + "files_iterated": 763, + "runtime": "node" }, { "name": "acorn-typescript", "group": "parse/typescript", - "mean_ns": 2213405912.6, - "p50_ns": 2212095774, - "p75_ns": 2213405114, - "p90_ns": 2218802607.8, - "p95_ns": 2220601772.4, - "p99_ns": 2222041104.08, - "min_ns": 2209487030, - "max_ns": 2222400937, - "std_dev_ns": 5295312.414249306, - "cv": 0.0023923819775240017, - "ops_per_second": 0.4517924138123132, + "mean_ns": 3095283600.8, + "p50_ns": 3095095186, + "p75_ns": 3097767418, + "p90_ns": 3099323611.6, + "p95_ns": 3099842342.8, + "p99_ns": 3100257327.76, + "min_ns": 3091472519, + "max_ns": 3100361074, + "std_dev_ns": 3846956.6437835894, + "cv": 0.0012428446436343712, + "ops_per_second": 0.3230721733354392, "sample_size": 5, - "files_processed": 3693, - "files_total": 4025, - "files_iterated": 3603 + "files_processed": 2434, + "files_total": 2437, + "files_iterated": 2434, + "runtime": "node" }, { "name": "tsv-json", "group": "parse/typescript", - "mean_ns": 2049624429.8, - "p50_ns": 2046277896, - "p75_ns": 2052475955, - "p90_ns": 2062325034.8, - "p95_ns": 2065608061.4, - "p99_ns": 2068234482.68, - "min_ns": 2036381168, - "max_ns": 2068891088, - "std_dev_ns": 12211486.98364508, - "cv": 0.005957914438420635, - "ops_per_second": 0.4878942627052796, - "sample_size": 5, - "files_processed": 3743, - "files_total": 4025, - "files_iterated": 3603 + "mean_ns": 2055685813.75, + "p50_ns": 2054412961, + "p75_ns": 2062088510, + "p90_ns": 2064528234.2, + "p95_ns": 2065341475.6, + "p99_ns": 2065992068.72, + "min_ns": 2052635587, + "max_ns": 2066154717, + "std_dev_ns": 4329871.531821806, + "cv": 0.0021062905152432884, + "ops_per_second": 0.4864556603500567, + "sample_size": 4, + "files_processed": 2437, + "files_total": 2437, + "files_iterated": 2434, + "runtime": "node" + }, + { + "name": "tsv-json-no-locations", + "group": "parse/typescript", + "mean_ns": 998270334.8333334, + "p50_ns": 998682441, + "p75_ns": 999030412, + "p90_ns": 999361650.5, + "p95_ns": 999480538.25, + "p99_ns": 999575648.45, + "min_ns": 996556819, + "max_ns": 999599426, + "std_dev_ns": 1220872.2439011242, + "cv": 0.0012229876029572245, + "ops_per_second": 1.0017326620919327, + "sample_size": 6, + "files_processed": 2437, + "files_total": 2437, + "files_iterated": 2434, + "runtime": "node" }, { "name": "tsv_wasm-json", "group": "parse/typescript", - "mean_ns": 2223665077.4, - "p50_ns": 2223809451, - "p75_ns": 2224511693, - "p90_ns": 2224643608.4, - "p95_ns": 2224687580.2, - "p99_ns": 2224722757.64, - "min_ns": 2222334633, - "max_ns": 2224731552, - "std_dev_ns": 1021470.6667904371, - "cv": 0.0004593635422762416, - "ops_per_second": 0.44970801141026184, + "mean_ns": 2190385709.6, + "p50_ns": 2189048667, + "p75_ns": 2195488548, + "p90_ns": 2195623038.6, + "p95_ns": 2195667868.8, + "p99_ns": 2195703732.96, + "min_ns": 2183175920, + "max_ns": 2195712699, + "std_dev_ns": 5285125.287642885, + "cv": 0.0024128742551959193, + "ops_per_second": 0.4565405972186589, + "sample_size": 5, + "files_processed": 2437, + "files_total": 2437, + "files_iterated": 2434, + "runtime": "node" + }, + { + "name": "tsv_wasm-json-no-locations", + "group": "parse/typescript", + "mean_ns": 1098155893.4, + "p50_ns": 1098004307, + "p75_ns": 1098812393, + "p90_ns": 1099122653, + "p95_ns": 1099226073, + "p99_ns": 1099308809, + "min_ns": 1096818332, + "max_ns": 1099329493, + "std_dev_ns": 966517.5293957684, + "cv": 0.0008801277989806472, + "ops_per_second": 0.9106175234409573, "sample_size": 5, - "files_processed": 3743, - "files_total": 4025, - "files_iterated": 3603 + "files_processed": 2437, + "files_total": 2437, + "files_iterated": 2434, + "runtime": "node" }, { "name": "tsv-internal", "group": "parse/typescript", - "mean_ns": 236250428.1875, - "p50_ns": 235985787, - "p75_ns": 239292380, - "p90_ns": 262673433, - "p95_ns": 263116119, - "p99_ns": 263948169.4, - "min_ns": 234967322, - "max_ns": 264156182, - "std_dev_ns": 1336552.468610353, - "cv": 0.005657354692917632, - "ops_per_second": 4.232796561140413, - "sample_size": 16, - "files_processed": 3743, - "files_total": 4025, - "files_iterated": 3603 + "mean_ns": 137932086.30769232, + "p50_ns": 138073024.5, + "p75_ns": 140428980, + "p90_ns": 141767891.5, + "p95_ns": 142291971.75, + "p99_ns": 142733225.2, + "min_ns": 136940380, + "max_ns": 142926911, + "std_dev_ns": 546559.9950337947, + "cv": 0.003962529746809997, + "ops_per_second": 7.24994471387352, + "sample_size": 26, + "files_processed": 2437, + "files_total": 2437, + "files_iterated": 2434, + "runtime": "node" }, { "name": "tsv_wasm-internal", "group": "parse/typescript", - "mean_ns": 292595892.125, - "p50_ns": 292647736.5, - "p75_ns": 292773388.75, - "p90_ns": 293666035.6, - "p95_ns": 294709175.9, - "p99_ns": 294963799.18, - "min_ns": 292266142, - "max_ns": 295027455, - "std_dev_ns": 262236.36256371846, - "cv": 0.0008962407525929597, - "ops_per_second": 3.4176829781765687, - "sample_size": 16, - "files_processed": 3743, - "files_total": 4025, - "files_iterated": 3603 + "mean_ns": 183489849.5909091, + "p50_ns": 183663283.5, + "p75_ns": 184686170.25, + "p90_ns": 187722911.1, + "p95_ns": 187769910.1, + "p99_ns": 188076536.44, + "min_ns": 182091250, + "max_ns": 188185543, + "std_dev_ns": 725759.3611214367, + "cv": 0.003955310676527984, + "ops_per_second": 5.449892744636837, + "sample_size": 22, + "files_processed": 2437, + "files_total": 2437, + "files_iterated": 2434, + "runtime": "node" }, { "name": "oxc-parser", "group": "parse/typescript", - "mean_ns": 864942821.5, - "p50_ns": 863969686.5, - "p75_ns": 878826551, - "p90_ns": 884913960, - "p95_ns": 887896262.5, - "p99_ns": 890282104.5, - "min_ns": 845083325, - "max_ns": 890878565, - "std_dev_ns": 20069394.017162047, - "cv": 0.023203145362091483, - "ops_per_second": 1.1561457880716106, - "sample_size": 6, - "files_processed": 3833, - "files_total": 4025, - "files_iterated": 3603 + "mean_ns": 1328575979, + "p50_ns": 1327625911, + "p75_ns": 1336923813, + "p90_ns": 1338162009.6, + "p95_ns": 1338574741.8, + "p99_ns": 1338904927.56, + "min_ns": 1318900753, + "max_ns": 1338987474, + "std_dev_ns": 9202726.679679045, + "cv": 0.006926759797814352, + "ops_per_second": 0.7526855940543842, + "sample_size": 5, + "files_processed": 2435, + "files_total": 2437, + "files_iterated": 2434, + "runtime": "node" }, { "name": "oxc-parser-wasm", "group": "parse/typescript", - "mean_ns": 976785685.3333334, - "p50_ns": 975594108.5, - "p75_ns": 979113434.75, - "p90_ns": 982226200, - "p95_ns": 983362576, - "p99_ns": 984271676.8, - "min_ns": 971966753, - "max_ns": 984498952, - "std_dev_ns": 4714293.024074751, - "cv": 0.004826333037902754, - "ops_per_second": 1.023766026688592, - "sample_size": 6, - "files_processed": 4025, - "files_total": 4025, - "files_iterated": 3603 + "mean_ns": 1358577531.4, + "p50_ns": 1357675431, + "p75_ns": 1365281550, + "p90_ns": 1365570397.8, + "p95_ns": 1365666680.4, + "p99_ns": 1365743706.48, + "min_ns": 1349320017, + "max_ns": 1365762963, + "std_dev_ns": 7017857.984748716, + "cv": 0.005165592557324929, + "ops_per_second": 0.736063991113934, + "sample_size": 5, + "files_processed": 2435, + "files_total": 2437, + "files_iterated": 2434, + "runtime": "node" }, { "name": "prettier", "group": "format/typescript", - "mean_ns": 8773944013.666666, - "p50_ns": 8784008051, - "p75_ns": 8807150369, - "p90_ns": 8902301987.4, - "p95_ns": 8961594658.2, - "p99_ns": 9009028794.84, - "min_ns": 8712430383, - "max_ns": 9020887329, - "std_dev_ns": 36684367.115987, - "cv": 0.004181057806939032, - "ops_per_second": 0.1139738296075696, - "sample_size": 6, - "files_processed": 3855, - "files_total": 4025, - "files_iterated": 3701 + "mean_ns": 13390193976.8, + "p50_ns": 13397850712, + "p75_ns": 13480628592.5, + "p90_ns": 13560690379.6, + "p95_ns": 13575781262.8, + "p99_ns": 13587853969.36, + "min_ns": 13360491961, + "max_ns": 13590872146, + "std_dev_ns": 21843031.171797708, + "cv": 0.0016312707052372197, + "ops_per_second": 0.07468151706634057, + "sample_size": 5, + "files_processed": 2437, + "files_total": 2437, + "files_iterated": 2435, + "runtime": "node" }, { "name": "tsv", "group": "format/typescript", - "mean_ns": 854559033.5, - "p50_ns": 856988296.5, - "p75_ns": 860171191.5, - "p90_ns": 861313209, - "p95_ns": 861398115.5, - "p99_ns": 861466040.7, - "min_ns": 838541329, - "max_ns": 861483022, - "std_dev_ns": 8547810.99796243, - "cv": 0.01000259860685497, - "ops_per_second": 1.1701941712608437, - "sample_size": 6, - "files_processed": 3743, - "files_total": 4025, - "files_iterated": 3701 + "mean_ns": 510281312, + "p50_ns": 510621353.5, + "p75_ns": 511585299, + "p90_ns": 518547738.5, + "p95_ns": 523173736.25, + "p99_ns": 526874534.45, + "min_ns": 509042507, + "max_ns": 527799734, + "std_dev_ns": 953832.181018533, + "cv": 0.0018692281268935299, + "ops_per_second": 1.9597033567241435, + "sample_size": 8, + "files_processed": 2437, + "files_total": 2437, + "files_iterated": 2435, + "runtime": "node" }, { "name": "tsv_wasm", "group": "format/typescript", - "mean_ns": 1145280037.8, - "p50_ns": 1144938011, - "p75_ns": 1145815746, - "p90_ns": 1146670300.2, - "p95_ns": 1146955151.6, - "p99_ns": 1147183032.72, - "min_ns": 1144152898, - "max_ns": 1147240003, - "std_dev_ns": 1281560.5264546424, - "cv": 0.0011189931581418527, - "ops_per_second": 0.8731488954622204, - "sample_size": 5, - "files_processed": 3743, - "files_total": 4025, - "files_iterated": 3701 + "mean_ns": 712429650, + "p50_ns": 712594376, + "p75_ns": 713355899.5, + "p90_ns": 721656199, + "p95_ns": 727815761.5, + "p99_ns": 732743411.5, + "min_ns": 710764653, + "max_ns": 733975324, + "std_dev_ns": 959542.0750962407, + "cv": 0.001346858704008516, + "ops_per_second": 1.4036473636379396, + "sample_size": 6, + "files_processed": 2437, + "files_total": 2437, + "files_iterated": 2435, + "runtime": "node" }, { "name": "oxfmt", "group": "format/typescript", - "mean_ns": 802078434.8571428, - "p50_ns": 804423326, - "p75_ns": 806569662, - "p90_ns": 809150059.2, - "p95_ns": 809881109.1, - "p99_ns": 810465949.02, - "min_ns": 792012009, - "max_ns": 810612159, - "std_dev_ns": 7054022.950975906, - "cv": 0.008794679727590843, - "ops_per_second": 1.246760860960074, - "sample_size": 7, - "files_processed": 3836, - "files_total": 4025, - "files_iterated": 3701 + "mean_ns": 847114076.6666666, + "p50_ns": 847057173.5, + "p75_ns": 849430498, + "p90_ns": 850355305.5, + "p95_ns": 850425492.25, + "p99_ns": 850481641.65, + "min_ns": 842300354, + "max_ns": 850495679, + "std_dev_ns": 3054424.4941014773, + "cv": 0.003605682609029966, + "ops_per_second": 1.1804785536499742, + "sample_size": 6, + "files_processed": 2435, + "files_total": 2437, + "files_iterated": 2435, + "runtime": "node" }, { "name": "biome-wasm", "group": "format/typescript", - "mean_ns": 3651240436.25, - "p50_ns": 3651115766, - "p75_ns": 3652416674, - "p90_ns": 3654275795, - "p95_ns": 3654895502, - "p99_ns": 3655391267.6, - "min_ns": 3650446557, - "max_ns": 3655515209, - "std_dev_ns": 835808.0747653235, - "cv": 0.00022891071934548872, - "ops_per_second": 0.2738795260021409, - "sample_size": 4, - "files_processed": 4025, - "files_total": 4025, - "files_iterated": 3701 + "mean_ns": 4640659870.333333, + "p50_ns": 4664197859, + "p75_ns": 6680537998, + "p90_ns": 9261928652.8, + "p95_ns": 10122392204.4, + "p99_ns": 10810763045.68, + "min_ns": 4598996897, + "max_ns": 10982855756, + "std_dev_ns": 36182560.209395595, + "cv": 0.0077968567445984025, + "ops_per_second": 0.21548659629049075, + "sample_size": 3, + "files_processed": 2437, + "files_total": 2437, + "files_iterated": 2435, + "runtime": "node" }, { "name": "svelte/compiler", "group": "parse/css", - "mean_ns": 19403785.678294573, - "p50_ns": 19376518.5, - "p75_ns": 19515459.5, - "p90_ns": 19626101.3, - "p95_ns": 19749809.1, - "p99_ns": 19835973.34, - "min_ns": 19083462, - "max_ns": 19889772, - "std_dev_ns": 170231.79292084067, - "cv": 0.008773122716525623, - "ops_per_second": 51.53633505232013, - "sample_size": 258, - "files_processed": 163, - "files_total": 193, - "files_iterated": 159 + "mean_ns": 9038889.063829787, + "p50_ns": 8961776.5, + "p75_ns": 9307395, + "p90_ns": 9753521.9, + "p95_ns": 10124050.150000002, + "p99_ns": 13612548.960000051, + "min_ns": 8653794, + "max_ns": 18954687, + "std_dev_ns": 348025.76061064505, + "cv": 0.03850315654423865, + "ops_per_second": 110.63306485324856, + "sample_size": 517, + "files_processed": 49, + "files_total": 49, + "files_iterated": 49, + "runtime": "node" }, { "name": "tsv-json", "group": "parse/css", - "mean_ns": 176879819.27586207, - "p50_ns": 176417640, - "p75_ns": 178453737, - "p90_ns": 179335842.4, - "p95_ns": 179414368, - "p99_ns": 179869721.35999998, - "min_ns": 173847338, - "max_ns": 180029857, - "std_dev_ns": 1802916.9896241936, - "cv": 0.010192892535763852, - "ops_per_second": 5.653556206094932, - "sample_size": 29, - "files_processed": 165, - "files_total": 193, - "files_iterated": 159 + "mean_ns": 17398039.684410647, + "p50_ns": 17328426, + "p75_ns": 17832813, + "p90_ns": 17992721.2, + "p95_ns": 19576760.4, + "p99_ns": 20362145.840000004, + "min_ns": 16591514, + "max_ns": 20753913, + "std_dev_ns": 355601.88164198975, + "cv": 0.020439192466069817, + "ops_per_second": 57.47773991434453, + "sample_size": 263, + "files_processed": 49, + "files_total": 49, + "files_iterated": 49, + "runtime": "node" }, { "name": "tsv_wasm-json", "group": "parse/css", - "mean_ns": 223033824.05555555, - "p50_ns": 222987561, - "p75_ns": 224042748.5, - "p90_ns": 226002163.2, - "p95_ns": 226359314, - "p99_ns": 226689078.04, - "min_ns": 222564338, - "max_ns": 226779975, - "std_dev_ns": 476931.02029914665, - "cv": 0.002138379783060832, - "ops_per_second": 4.483624868266213, - "sample_size": 18, - "files_processed": 165, - "files_total": 193, - "files_iterated": 159 + "mean_ns": 18287059.184549358, + "p50_ns": 18176678, + "p75_ns": 18667889, + "p90_ns": 19460618.2, + "p95_ns": 20185583.2, + "p99_ns": 23133210.919999998, + "min_ns": 17916961, + "max_ns": 28337630, + "std_dev_ns": 319973.5214247198, + "cv": 0.017497265043854824, + "ops_per_second": 54.68347807639267, + "sample_size": 233, + "files_processed": 49, + "files_total": 49, + "files_iterated": 49, + "runtime": "node" }, { "name": "tsv-internal", "group": "parse/css", - "mean_ns": 23123759.831578948, - "p50_ns": 23081315.5, - "p75_ns": 23581269.25, - "p90_ns": 25285569.600000005, - "p95_ns": 26399632.149999995, - "p99_ns": 34455670.899999976, - "min_ns": 22528124, - "max_ns": 43861736, - "std_dev_ns": 454153.9601688399, - "cv": 0.019640143448844544, - "ops_per_second": 43.24556245539061, - "sample_size": 190, - "files_processed": 165, - "files_total": 193, - "files_iterated": 159 + "mean_ns": 3426007.1822466613, + "p50_ns": 3420016, + "p75_ns": 3464659.25, + "p90_ns": 3531872.4000000004, + "p95_ns": 3565861.45, + "p99_ns": 3719174.36, + "min_ns": 3366475, + "max_ns": 5318998, + "std_dev_ns": 30650.87400915242, + "cv": 0.008946529408339593, + "ops_per_second": 291.8849689463387, + "sample_size": 1273, + "files_processed": 49, + "files_total": 49, + "files_iterated": 49, + "runtime": "node" }, { "name": "tsv_wasm-internal", "group": "parse/css", - "mean_ns": 32072885.51879699, - "p50_ns": 32066954.5, - "p75_ns": 32148053.5, - "p90_ns": 32335215.5, - "p95_ns": 32349665.75, - "p99_ns": 32378088.75, - "min_ns": 31983936, - "max_ns": 32565198, - "std_dev_ns": 53586.24561517934, - "cv": 0.0016707647206789045, - "ops_per_second": 31.178984485631293, - "sample_size": 133, - "files_processed": 165, - "files_total": 193, - "files_iterated": 159 + "mean_ns": 4903393.332503113, + "p50_ns": 4899160, + "p75_ns": 4955431.5, + "p90_ns": 5024022.6, + "p95_ns": 5053242.6, + "p99_ns": 5101561.9, + "min_ns": 4853645, + "max_ns": 5268624, + "std_dev_ns": 24113.319974338137, + "cv": 0.004917680132755866, + "ops_per_second": 203.9404004919006, + "sample_size": 803, + "files_processed": 49, + "files_total": 49, + "files_iterated": 49, + "runtime": "node" }, { "name": "prettier", "group": "format/css", - "mean_ns": 731055145.8571428, - "p50_ns": 726866934, - "p75_ns": 737411474, - "p90_ns": 739494874, - "p95_ns": 740913023.5, - "p99_ns": 742047543.1, - "min_ns": 723869573, - "max_ns": 742331173, - "std_dev_ns": 7724050.9095097985, - "cv": 0.010565620053810789, - "ops_per_second": 1.3678858642428766, - "sample_size": 7, - "files_processed": 192, - "files_total": 193, - "files_iterated": 165 + "mean_ns": 551732011.6, + "p50_ns": 548811461, + "p75_ns": 561576720.25, + "p90_ns": 564489059.2, + "p95_ns": 565986579.1, + "p99_ns": 567184595.02, + "min_ns": 539045160, + "max_ns": 567484099, + "std_dev_ns": 10779738.139714208, + "cv": 0.019537996550994773, + "ops_per_second": 1.8124741341363197, + "sample_size": 10, + "files_processed": 49, + "files_total": 49, + "files_iterated": 49, + "runtime": "node" }, { "name": "tsv", "group": "format/css", - "mean_ns": 42276700.15044248, - "p50_ns": 42363994, - "p75_ns": 42798711, - "p90_ns": 43541791.3, - "p95_ns": 44634919.449999996, - "p99_ns": 50024498.75, - "min_ns": 40686670, - "max_ns": 51286794, - "std_dev_ns": 850647.2521532943, - "cv": 0.020120947215043962, - "ops_per_second": 23.65369095604624, - "sample_size": 113, - "files_processed": 165, - "files_total": 193, - "files_iterated": 165 + "mean_ns": 7032221.678899082, + "p50_ns": 7009244, + "p75_ns": 7098315.5, + "p90_ns": 7206890.5, + "p95_ns": 7244467.45, + "p99_ns": 7291832.67, + "min_ns": 6934839, + "max_ns": 10835306, + "std_dev_ns": 72742.76588589282, + "cv": 0.010344208303922657, + "ops_per_second": 142.20257063292027, + "sample_size": 654, + "files_processed": 49, + "files_total": 49, + "files_iterated": 49, + "runtime": "node" }, { "name": "tsv_wasm", "group": "format/css", - "mean_ns": 56842907.02898551, - "p50_ns": 56836126.5, - "p75_ns": 57051209.25, - "p90_ns": 57277215.5, - "p95_ns": 57411214.85, - "p99_ns": 57614455.839999996, - "min_ns": 56701796, - "max_ns": 57937459, - "std_dev_ns": 94515.15490540798, - "cv": 0.0016627431608522859, - "ops_per_second": 17.59234445011894, - "sample_size": 69, - "files_processed": 165, - "files_total": 193, - "files_iterated": 165 + "mean_ns": 10158745.69358178, + "p50_ns": 10110156, + "p75_ns": 10257106.5, + "p90_ns": 10362890, + "p95_ns": 10401973.5, + "p99_ns": 10501883.000000002, + "min_ns": 9975082, + "max_ns": 13912078, + "std_dev_ns": 120001.65250816409, + "cv": 0.011812644604735035, + "ops_per_second": 98.43734946842822, + "sample_size": 483, + "files_processed": 49, + "files_total": 49, + "files_iterated": 49, + "runtime": "node" }, { "name": "oxfmt", "group": "format/css", - "mean_ns": 759715951, - "p50_ns": 758504294, - "p75_ns": 764356670.5, - "p90_ns": 792120220.8000001, - "p95_ns": 810371960.4, - "p99_ns": 824973352.0799999, - "min_ns": 755827467, - "max_ns": 828623700, - "std_dev_ns": 4300372.980217599, - "cv": 0.005660501105126328, - "ops_per_second": 1.3162814321375227, - "sample_size": 6, - "files_processed": 192, - "files_total": 193, - "files_iterated": 165 + "mean_ns": 18091033.845588237, + "p50_ns": 18067378, + "p75_ns": 18433522.5, + "p90_ns": 18979253.5, + "p95_ns": 19192316.5, + "p99_ns": 20099620.25, + "min_ns": 16502367, + "max_ns": 20904626, + "std_dev_ns": 587884.7092626577, + "cv": 0.03249591561656505, + "ops_per_second": 55.27600072694931, + "sample_size": 272, + "files_processed": 49, + "files_total": 49, + "files_iterated": 49, + "runtime": "node" }, { "name": "biome-wasm", "group": "format/css", - "mean_ns": 207351523.875, - "p50_ns": 207159397, - "p75_ns": 208264123, - "p90_ns": 208754043.4, - "p95_ns": 209395575, - "p99_ns": 209795343.92, - "min_ns": 205889964, - "max_ns": 209888054, - "std_dev_ns": 897025.2129294197, - "cv": 0.004326108610950857, - "ops_per_second": 4.822727999832984, - "sample_size": 24, - "files_processed": 193, - "files_total": 193, - "files_iterated": 165 + "mean_ns": 126426328.38461539, + "p50_ns": 126839392, + "p75_ns": 138513503.5, + "p90_ns": 193632761, + "p95_ns": 202020801.25, + "p99_ns": 208481402.25, + "min_ns": 124102387, + "max_ns": 209127749, + "std_dev_ns": 1965652.0770388045, + "cv": 0.0155478063956653, + "ops_per_second": 7.9097448512290125, + "sample_size": 26, + "files_processed": 49, + "files_total": 49, + "files_iterated": 49, + "runtime": "node" } ], - "suppressed_noise": { - "oxfmt::textToDoc": 1, - "panicked at crates/biome_rowan": 1 - } + "suppressed_noise": {}, + "variant_parity": [] } diff --git a/src/routes/docs/benchmarks/benchmarks_cli.ts b/src/routes/docs/benchmarks/benchmarks_cli.ts new file mode 100644 index 0000000..25f7834 --- /dev/null +++ b/src/routes/docs/benchmarks/benchmarks_cli.ts @@ -0,0 +1,84 @@ +// A snapshot of the independent end-to-end CLI benchmark — a fork of Oxc's official +// `bench-formatter` that adds tsv on its `tsv` branch +// (https://github.com/ryanatkn/oxc-bench-formatter, forked from +// https://github.com/oxc-project/bench-formatter — the fork's `main` tracks +// upstream and carries neither tsv nor this analysis, so links target `tsv`). +// Unlike the +// in-process, single-threaded numbers elsewhere on this page, it measures the +// WHOLE CLI: process spawn, file discovery, I/O, and each tool's default +// multi-file parallelism. tsv, oxfmt, and biome parallelize across files while +// prettier is effectively serial, so the wall-clock ratios scale with core count +// and are machine-dependent — the parallelism-neutral view is CPU work (hyperfine +// `User` time). tsv runs only in the two JSX-free scenarios (it has no JSX/TSX +// parser). Hand-copied from the linked report; regenerate there with +// `pnpm run update-readme`, then update the numbers below. + +export const BENCHMARKS_CLI_SOURCE_URL = 'https://github.com/ryanatkn/oxc-bench-formatter'; +export const BENCHMARKS_CLI_UPSTREAM_URL = 'https://github.com/oxc-project/bench-formatter'; + +export interface CliFormatterResult { + /** Display label; `tsv` is the reference row every ratio is computed against. */ + label: string; + /** hyperfine wall-clock mean, in milliseconds — what you experience typing the command. */ + wall_ms: number; + /** hyperfine `User` time (total CPU across all threads), in ms — the parallelism-neutral view. */ + cpu_ms: number; + /** Peak resident set size (RSS), in megabytes. */ + memory_mb: number; +} + +export interface CliScenario { + key: string; + heading: string; + /** One-line description of the corpus and what makes the comparison fair. */ + description: string; + /** Whether every formatter is effectively single-threaded here (one input file). */ + single_threaded: boolean; + /** Results as measured; the component sorts and computes tsv-relative ratios. */ + results: Array; +} + +export interface BenchmarksCliReport { + source_url: string; + upstream_url: string; + machine: string; + versions: {prettier: string; biome: string; oxfmt: string; tsv: string}; + scenarios: Array; +} + +export const benchmarks_cli: BenchmarksCliReport = { + source_url: BENCHMARKS_CLI_SOURCE_URL, + upstream_url: BENCHMARKS_CLI_UPSTREAM_URL, + machine: 'AMD Ryzen 5 PRO 7530U · 12 threads · linux x64', + versions: {prettier: '3.9.1', biome: '2.5.1', oxfmt: '0.59.0', tsv: '0.2.0'}, + scenarios: [ + { + key: 'ts-repo', + heading: 'TypeScript repo (Outline, non-JSX subset)', + description: + "Outline's .ts/.js/.mjs files — the common set every formatter supports, each scoped to it, with a preflight parse check confirming none reject anything. The fair way to put tsv on a real multi-file repo.", + single_threaded: false, + results: [ + {label: 'tsv', wall_ms: 120.3, cpu_ms: 854.8, memory_mb: 45.2}, + {label: 'oxfmt', wall_ms: 379.8, cpu_ms: 1826.7, memory_mb: 215.6}, + {label: 'biome', wall_ms: 784.3, cpu_ms: 7285.0, memory_mb: 135.7}, + {label: 'prettier + oxc-parser', wall_ms: 8979, cpu_ms: 11237, memory_mb: 331.4}, + {label: 'prettier', wall_ms: 10911, cpu_ms: 17611, memory_mb: 447.5}, + ], + }, + { + key: 'large-single-file', + heading: 'Large single file (TypeScript compiler parser.ts, ~540KB)', + description: + 'One ~13.7K-line .ts file. With a single input every formatter is effectively single-threaded, so wall-clock is close to an engine comparison here — except oxfmt, which spins up a worker pool it cannot use, inflating its CPU time.', + single_threaded: true, + results: [ + {label: 'tsv', wall_ms: 28.8, cpu_ms: 15.2, memory_mb: 23.1}, + {label: 'biome', wall_ms: 138.8, cpu_ms: 107.3, memory_mb: 102.6}, + {label: 'oxfmt', wall_ms: 236.9, cpu_ms: 456.8, memory_mb: 118.0}, + {label: 'prettier + oxc-parser', wall_ms: 503.4, cpu_ms: 751.3, memory_mb: 184.4}, + {label: 'prettier', wall_ms: 1597, cpu_ms: 1583, memory_mb: 303.2}, + ], + }, + ], +}; diff --git a/src/routes/docs/benchmarks/benchmarks_conformance.json b/src/routes/docs/benchmarks/benchmarks_conformance.json new file mode 100644 index 0000000..f95e3ca --- /dev/null +++ b/src/routes/docs/benchmarks/benchmarks_conformance.json @@ -0,0 +1,643 @@ +{ + "version": 7, + "runtime": "node", + "corpus_kind": "conformance", + "timestamp": "2026-07-16T11:52:16.141Z", + "git_commit": "135b7b93", + "machine": { + "cpu_model": "AMD Ryzen 5 PRO 7530U with Radeon Graphics", + "os": "linux", + "arch": "x86_64", + "runtime_version": "24.14.1" + }, + "corpus": { + "svelte": 4539, + "typescript": 44224, + "css": 22641 + }, + "corpus_sources": [ + { + "path": "../prettier-plugin-svelte/test", + "files": 318, + "by_language": { + "svelte": 318, + "typescript": 0, + "css": 0 + }, + "repo": { + "url": "https://github.com/sveltejs/prettier-plugin-svelte", + "slug": "sveltejs/prettier-plugin-svelte", + "commit": "7809486a9716faa2234c8a45d88b601034de52d8", + "subpath": "test" + } + }, + { + "path": "../prettier/tests/format/typescript", + "files": 793, + "by_language": { + "svelte": 0, + "typescript": 793, + "css": 0 + }, + "repo": { + "url": "https://github.com/prettier/prettier", + "slug": "prettier/prettier", + "commit": "1dcd0b05d026356cce5bf538c6d425955b29903b", + "subpath": "tests/format/typescript" + } + }, + { + "path": "../prettier/tests/format/js", + "files": 1103, + "by_language": { + "svelte": 0, + "typescript": 1103, + "css": 0 + }, + "repo": { + "url": "https://github.com/prettier/prettier", + "slug": "prettier/prettier", + "commit": "1dcd0b05d026356cce5bf538c6d425955b29903b", + "subpath": "tests/format/js" + } + }, + { + "path": "../prettier/tests/format/css", + "files": 228, + "by_language": { + "svelte": 0, + "typescript": 78, + "css": 150 + }, + "repo": { + "url": "https://github.com/prettier/prettier", + "slug": "prettier/prettier", + "commit": "1dcd0b05d026356cce5bf538c6d425955b29903b", + "subpath": "tests/format/css" + } + }, + { + "path": "../prettier/tests/format/html", + "files": 84, + "by_language": { + "svelte": 84, + "typescript": 0, + "css": 0 + }, + "repo": { + "url": "https://github.com/prettier/prettier", + "slug": "prettier/prettier", + "commit": "1dcd0b05d026356cce5bf538c6d425955b29903b", + "subpath": "tests/format/html" + } + }, + { + "path": "../svelte/packages/svelte/tests", + "files": 4455, + "by_language": { + "svelte": 4137, + "typescript": 137, + "css": 181 + }, + "repo": { + "url": "https://github.com/sveltejs/svelte", + "slug": "sveltejs/svelte", + "commit": "b4d1583ae20f3869a88a731d9a265c546c099f66", + "subpath": "packages/svelte/tests" + } + }, + { + "path": "benches/js/.cache/wpt_css", + "files": 22310, + "by_language": { + "svelte": 0, + "typescript": 0, + "css": 22310 + }, + "repo": { + "url": "https://github.com/web-platform-tests/wpt", + "slug": "web-platform-tests/wpt", + "commit": "", + "subpath": "" + } + }, + { + "path": "benches/js/.cache/test262_files.json", + "files": 42113, + "by_language": { + "svelte": 0, + "typescript": 42113, + "css": 0 + }, + "repo": { + "url": "https://github.com/tc39/test262", + "slug": "tc39/test262", + "commit": "", + "subpath": "" + } + } + ], + "versions": { + "tsv": "0.1.0", + "svelte": "5.56.4", + "acorn": "8.16.0", + "acorn_ts": "1.0.11", + "prettier": "3.9.0", + "prettier_svelte": "4.1.1", + "oxc_parser": "0.140.0", + "oxfmt": "0.59.0", + "biome": "2.5.4" + }, + "binary_sizes": [ + { + "label": "tsv (ffi)", + "bytes": 3407520, + "kind": "native", + "gzip_bytes": 1473224 + }, + { + "label": "tsv format (ffi)", + "bytes": 3143024, + "kind": "native", + "gzip_bytes": 1366105 + }, + { + "label": "tsv parse (ffi)", + "bytes": 1602672, + "kind": "native", + "gzip_bytes": 704951 + }, + { + "label": "tsv (napi)", + "bytes": 3540848, + "kind": "native", + "gzip_bytes": 1517414 + }, + { + "label": "tsv_format_wasm", + "bytes": 2261397, + "kind": "wasm", + "gzip_bytes": 794650 + }, + { + "label": "tsv_parse_wasm", + "bytes": 1057603, + "kind": "wasm", + "gzip_bytes": 389582 + }, + { + "label": "tsv_wasm", + "bytes": 2491464, + "kind": "wasm", + "gzip_bytes": 875399 + }, + { + "label": "biome (wasm)", + "bytes": 38584187, + "kind": "wasm", + "gzip_bytes": 9281487 + }, + { + "label": "oxc-parser (napi)", + "bytes": 2354904, + "kind": "native", + "gzip_bytes": 954754 + }, + { + "label": "oxfmt (napi)", + "bytes": 8917776, + "kind": "native", + "gzip_bytes": 3586269 + }, + { + "label": "oxc-parser (wasm)", + "bytes": 1531455, + "kind": "wasm", + "gzip_bytes": 495227 + } + ], + "entries": [ + { + "name": "svelte/compiler", + "group": "parse/svelte", + "mean_ns": null, + "p50_ns": null, + "p75_ns": null, + "p90_ns": null, + "p95_ns": null, + "p99_ns": null, + "min_ns": null, + "max_ns": null, + "std_dev_ns": null, + "cv": null, + "ops_per_second": null, + "sample_size": null, + "files_processed": 4539, + "files_total": 4539, + "files_iterated": 4539, + "runtime": "node" + }, + { + "name": "tsv-json", + "group": "parse/svelte", + "mean_ns": null, + "p50_ns": null, + "p75_ns": null, + "p90_ns": null, + "p95_ns": null, + "p99_ns": null, + "min_ns": null, + "max_ns": null, + "std_dev_ns": null, + "cv": null, + "ops_per_second": null, + "sample_size": null, + "files_processed": 4539, + "files_total": 4539, + "files_iterated": 4539, + "runtime": "node" + }, + { + "name": "tsv-json-no-locations", + "group": "parse/svelte", + "mean_ns": null, + "p50_ns": null, + "p75_ns": null, + "p90_ns": null, + "p95_ns": null, + "p99_ns": null, + "min_ns": null, + "max_ns": null, + "std_dev_ns": null, + "cv": null, + "ops_per_second": null, + "sample_size": null, + "files_processed": 4539, + "files_total": 4539, + "files_iterated": 4539, + "runtime": "node" + }, + { + "name": "tsv_wasm-json", + "group": "parse/svelte", + "mean_ns": null, + "p50_ns": null, + "p75_ns": null, + "p90_ns": null, + "p95_ns": null, + "p99_ns": null, + "min_ns": null, + "max_ns": null, + "std_dev_ns": null, + "cv": null, + "ops_per_second": null, + "sample_size": null, + "files_processed": 4539, + "files_total": 4539, + "files_iterated": 4539, + "runtime": "node" + }, + { + "name": "tsv_wasm-json-no-locations", + "group": "parse/svelte", + "mean_ns": null, + "p50_ns": null, + "p75_ns": null, + "p90_ns": null, + "p95_ns": null, + "p99_ns": null, + "min_ns": null, + "max_ns": null, + "std_dev_ns": null, + "cv": null, + "ops_per_second": null, + "sample_size": null, + "files_processed": 4539, + "files_total": 4539, + "files_iterated": 4539, + "runtime": "node" + }, + { + "name": "tsv-internal", + "group": "parse/svelte", + "mean_ns": null, + "p50_ns": null, + "p75_ns": null, + "p90_ns": null, + "p95_ns": null, + "p99_ns": null, + "min_ns": null, + "max_ns": null, + "std_dev_ns": null, + "cv": null, + "ops_per_second": null, + "sample_size": null, + "files_processed": 4539, + "files_total": 4539, + "files_iterated": 4539, + "runtime": "node" + }, + { + "name": "tsv_wasm-internal", + "group": "parse/svelte", + "mean_ns": null, + "p50_ns": null, + "p75_ns": null, + "p90_ns": null, + "p95_ns": null, + "p99_ns": null, + "min_ns": null, + "max_ns": null, + "std_dev_ns": null, + "cv": null, + "ops_per_second": null, + "sample_size": null, + "files_processed": 4539, + "files_total": 4539, + "files_iterated": 4539, + "runtime": "node" + }, + { + "name": "acorn-typescript", + "group": "parse/typescript", + "mean_ns": null, + "p50_ns": null, + "p75_ns": null, + "p90_ns": null, + "p95_ns": null, + "p99_ns": null, + "min_ns": null, + "max_ns": null, + "std_dev_ns": null, + "cv": null, + "ops_per_second": null, + "sample_size": null, + "files_processed": 43647, + "files_total": 44224, + "files_iterated": 43619, + "runtime": "node" + }, + { + "name": "tsv-json", + "group": "parse/typescript", + "mean_ns": null, + "p50_ns": null, + "p75_ns": null, + "p90_ns": null, + "p95_ns": null, + "p99_ns": null, + "min_ns": null, + "max_ns": null, + "std_dev_ns": null, + "cv": null, + "ops_per_second": null, + "sample_size": null, + "files_processed": 44023, + "files_total": 44224, + "files_iterated": 43619, + "runtime": "node" + }, + { + "name": "tsv-json-no-locations", + "group": "parse/typescript", + "mean_ns": null, + "p50_ns": null, + "p75_ns": null, + "p90_ns": null, + "p95_ns": null, + "p99_ns": null, + "min_ns": null, + "max_ns": null, + "std_dev_ns": null, + "cv": null, + "ops_per_second": null, + "sample_size": null, + "files_processed": 44023, + "files_total": 44224, + "files_iterated": 43619, + "runtime": "node" + }, + { + "name": "tsv_wasm-json", + "group": "parse/typescript", + "mean_ns": null, + "p50_ns": null, + "p75_ns": null, + "p90_ns": null, + "p95_ns": null, + "p99_ns": null, + "min_ns": null, + "max_ns": null, + "std_dev_ns": null, + "cv": null, + "ops_per_second": null, + "sample_size": null, + "files_processed": 44023, + "files_total": 44224, + "files_iterated": 43619, + "runtime": "node" + }, + { + "name": "tsv_wasm-json-no-locations", + "group": "parse/typescript", + "mean_ns": null, + "p50_ns": null, + "p75_ns": null, + "p90_ns": null, + "p95_ns": null, + "p99_ns": null, + "min_ns": null, + "max_ns": null, + "std_dev_ns": null, + "cv": null, + "ops_per_second": null, + "sample_size": null, + "files_processed": 44023, + "files_total": 44224, + "files_iterated": 43619, + "runtime": "node" + }, + { + "name": "tsv-internal", + "group": "parse/typescript", + "mean_ns": null, + "p50_ns": null, + "p75_ns": null, + "p90_ns": null, + "p95_ns": null, + "p99_ns": null, + "min_ns": null, + "max_ns": null, + "std_dev_ns": null, + "cv": null, + "ops_per_second": null, + "sample_size": null, + "files_processed": 44023, + "files_total": 44224, + "files_iterated": 43619, + "runtime": "node" + }, + { + "name": "tsv_wasm-internal", + "group": "parse/typescript", + "mean_ns": null, + "p50_ns": null, + "p75_ns": null, + "p90_ns": null, + "p95_ns": null, + "p99_ns": null, + "min_ns": null, + "max_ns": null, + "std_dev_ns": null, + "cv": null, + "ops_per_second": null, + "sample_size": null, + "files_processed": 44023, + "files_total": 44224, + "files_iterated": 43619, + "runtime": "node" + }, + { + "name": "oxc-parser", + "group": "parse/typescript", + "mean_ns": null, + "p50_ns": null, + "p75_ns": null, + "p90_ns": null, + "p95_ns": null, + "p99_ns": null, + "min_ns": null, + "max_ns": null, + "std_dev_ns": null, + "cv": null, + "ops_per_second": null, + "sample_size": null, + "files_processed": 44014, + "files_total": 44224, + "files_iterated": 43619, + "runtime": "node" + }, + { + "name": "oxc-parser-wasm", + "group": "parse/typescript", + "mean_ns": null, + "p50_ns": null, + "p75_ns": null, + "p90_ns": null, + "p95_ns": null, + "p99_ns": null, + "min_ns": null, + "max_ns": null, + "std_dev_ns": null, + "cv": null, + "ops_per_second": null, + "sample_size": null, + "files_processed": 44014, + "files_total": 44224, + "files_iterated": 43619, + "runtime": "node" + }, + { + "name": "svelte/compiler", + "group": "parse/css", + "mean_ns": null, + "p50_ns": null, + "p75_ns": null, + "p90_ns": null, + "p95_ns": null, + "p99_ns": null, + "min_ns": null, + "max_ns": null, + "std_dev_ns": null, + "cv": null, + "ops_per_second": null, + "sample_size": null, + "files_processed": 22402, + "files_total": 22641, + "files_iterated": 22388, + "runtime": "node" + }, + { + "name": "tsv-json", + "group": "parse/css", + "mean_ns": null, + "p50_ns": null, + "p75_ns": null, + "p90_ns": null, + "p95_ns": null, + "p99_ns": null, + "min_ns": null, + "max_ns": null, + "std_dev_ns": null, + "cv": null, + "ops_per_second": null, + "sample_size": null, + "files_processed": 22457, + "files_total": 22641, + "files_iterated": 22388, + "runtime": "node" + }, + { + "name": "tsv_wasm-json", + "group": "parse/css", + "mean_ns": null, + "p50_ns": null, + "p75_ns": null, + "p90_ns": null, + "p95_ns": null, + "p99_ns": null, + "min_ns": null, + "max_ns": null, + "std_dev_ns": null, + "cv": null, + "ops_per_second": null, + "sample_size": null, + "files_processed": 22457, + "files_total": 22641, + "files_iterated": 22388, + "runtime": "node" + }, + { + "name": "tsv-internal", + "group": "parse/css", + "mean_ns": null, + "p50_ns": null, + "p75_ns": null, + "p90_ns": null, + "p95_ns": null, + "p99_ns": null, + "min_ns": null, + "max_ns": null, + "std_dev_ns": null, + "cv": null, + "ops_per_second": null, + "sample_size": null, + "files_processed": 22457, + "files_total": 22641, + "files_iterated": 22388, + "runtime": "node" + }, + { + "name": "tsv_wasm-internal", + "group": "parse/css", + "mean_ns": null, + "p50_ns": null, + "p75_ns": null, + "p90_ns": null, + "p95_ns": null, + "p99_ns": null, + "min_ns": null, + "max_ns": null, + "std_dev_ns": null, + "cv": null, + "ops_per_second": null, + "sample_size": null, + "files_processed": 22457, + "files_total": 22641, + "files_iterated": 22388, + "runtime": "node" + } + ], + "suppressed_noise": {}, + "variant_parity": [] +} diff --git a/src/routes/docs/benchmarks/benchmarks_conformance.ts b/src/routes/docs/benchmarks/benchmarks_conformance.ts new file mode 100644 index 0000000..6aee0cd --- /dev/null +++ b/src/routes/docs/benchmarks/benchmarks_conformance.ts @@ -0,0 +1,5 @@ +import type {BenchmarkBaseline} from './benchmark_data.ts'; + +import json from './benchmarks_conformance.json' with {type: 'json'}; + +export const benchmarks_conformance_json: BenchmarkBaseline = json as BenchmarkBaseline; diff --git a/src/routes/docs/benchmarks/benchmarks_cross_runtime.json b/src/routes/docs/benchmarks/benchmarks_cross_runtime.json new file mode 100644 index 0000000..ca995eb --- /dev/null +++ b/src/routes/docs/benchmarks/benchmarks_cross_runtime.json @@ -0,0 +1,724 @@ +{ + "version": 7, + "kind": "combined", + "generated": "2026-07-16T11:51:31.514Z", + "runtimes": [ + "deno", + "node", + "bun" + ], + "mixed_vintage": false, + "mixed_machine": false, + "sources": [ + { + "runtime": "deno", + "timestamp": "2026-07-16T11:34:23.572Z", + "git_commit": "135b7b93", + "tsv": "0.1.0", + "machine": { + "cpu_model": "AMD Ryzen 5 PRO 7530U with Radeon Graphics", + "os": "linux", + "arch": "x86_64", + "runtime_version": "2.8.3" + } + }, + { + "runtime": "node", + "timestamp": "2026-07-16T11:43:29.034Z", + "git_commit": "135b7b93", + "tsv": "0.1.0", + "machine": { + "cpu_model": "AMD Ryzen 5 PRO 7530U with Radeon Graphics", + "os": "linux", + "arch": "x86_64", + "runtime_version": "24.14.1" + } + }, + { + "runtime": "bun", + "timestamp": "2026-07-16T11:51:31.124Z", + "git_commit": "135b7b93", + "tsv": "0.1.0", + "machine": { + "cpu_model": "AMD Ryzen 5 PRO 7530U with Radeon Graphics", + "os": "linux", + "arch": "x86_64", + "runtime_version": "1.3.14" + } + } + ], + "rows": [ + { + "group": "parse/svelte", + "name": "svelte/compiler", + "ops_per_second": { + "deno": 2.32203309333886, + "node": 2.201657407828713, + "bun": 1.3515266516636215 + }, + "mean_ns": { + "deno": 430657083.6, + "node": 454203272.7, + "bun": 739904018 + }, + "files_iterated": { + "deno": 763, + "node": 763, + "bun": 763 + } + }, + { + "group": "parse/svelte", + "name": "tsv-json", + "ops_per_second": { + "deno": 5.0827943917957965, + "node": 4.649398944179739, + "bun": 6.007332747236763 + }, + "mean_ns": { + "deno": 196742170.33333334, + "node": 215081564.7368421, + "bun": 166463227.87096775 + }, + "files_iterated": { + "deno": 763, + "node": 763, + "bun": 763 + } + }, + { + "group": "parse/svelte", + "name": "tsv-json-no-locations", + "ops_per_second": { + "deno": 7.922378990905888, + "node": 7.328403381719038, + "bun": 8.520169721099231 + }, + "mean_ns": { + "deno": 126224711.18181819, + "node": 136455370.6875, + "bun": 117368554 + }, + "files_iterated": { + "deno": 763, + "node": 763, + "bun": 763 + } + }, + { + "group": "parse/svelte", + "name": "tsv_wasm-json", + "ops_per_second": { + "deno": 4.161210076022163, + "node": 4.125738456275007, + "bun": 5.562851328976341 + }, + "mean_ns": { + "deno": 240314711.76190478, + "node": 242380851.47619048, + "bun": 179763926.96153846 + }, + "files_iterated": { + "deno": 763, + "node": 763, + "bun": 763 + } + }, + { + "group": "parse/svelte", + "name": "tsv_wasm-json-no-locations", + "ops_per_second": { + "deno": 6.299825463509322, + "node": 6.377630789601431, + "bun": 7.655585761307142 + }, + "mean_ns": { + "deno": 158734556.3448276, + "node": 156798038.8, + "bun": 130623577.5 + }, + "files_iterated": { + "deno": 763, + "node": 763, + "bun": 763 + } + }, + { + "group": "parse/svelte", + "name": "tsv-internal", + "ops_per_second": { + "deno": 47.04746259219493, + "node": 47.86874286762142, + "bun": 51.06942307255545 + }, + "mean_ns": { + "deno": 21255131.412037037, + "node": 20890458.785714287, + "bun": 19581188.504504506 + }, + "files_iterated": { + "deno": 763, + "node": 763, + "bun": 763 + } + }, + { + "group": "parse/svelte", + "name": "tsv_wasm-internal", + "ops_per_second": { + "deno": 30.474656127239356, + "node": 26.773814422344802, + "bun": 15.85067482488642 + }, + "mean_ns": { + "deno": 32814152.055555556, + "node": 37349926.47014926, + "bun": 63088796.6 + }, + "files_iterated": { + "deno": 763, + "node": 763, + "bun": 763 + } + }, + { + "group": "format/svelte", + "name": "prettier", + "ops_per_second": { + "deno": 0.237793092279713, + "node": 0.2267144665492906, + "bun": 0.2524397368523206 + }, + "mean_ns": { + "deno": 4205336624.428571, + "node": 4410834541, + "bun": 3961341476.857143 + }, + "files_iterated": { + "deno": 763, + "node": 763, + "bun": 763 + } + }, + { + "group": "format/svelte", + "name": "tsv", + "ops_per_second": { + "deno": 14.136745274233984, + "node": 14.35076558858908, + "bun": 14.12111063381393 + }, + "mean_ns": { + "deno": 70737640.14285715, + "node": 69682693.5, + "bun": 70815959.58928572 + }, + "files_iterated": { + "deno": 763, + "node": 763, + "bun": 763 + } + }, + { + "group": "format/svelte", + "name": "tsv_wasm", + "ops_per_second": { + "deno": 9.056948494404766, + "node": 7.928477824721544, + "bun": 7.193862254275549 + }, + "mean_ns": { + "deno": 110412464.04545455, + "node": 126127615.175, + "bun": 139007387.7777778 + }, + "files_iterated": { + "deno": 763, + "node": 763, + "bun": 763 + } + }, + { + "group": "format/svelte", + "name": "oxfmt", + "ops_per_second": { + "deno": 0.23968786103725603, + "node": 0.22993605936582995, + "bun": 0.1902163231156394 + }, + "mean_ns": { + "deno": 4172092803, + "node": 4349035130.714286, + "bun": 5257172379.428572 + }, + "files_iterated": { + "deno": 763, + "node": 763, + "bun": 763 + } + }, + { + "group": "format/svelte", + "name": "biome-wasm", + "ops_per_second": { + "deno": 1.4318975821011795, + "node": 1.0837474344623093 + }, + "mean_ns": { + "deno": 698373970.6666666, + "node": 922724214.3333334 + }, + "files_iterated": { + "deno": 763, + "node": 763 + } + }, + { + "group": "parse/typescript", + "name": "acorn-typescript", + "ops_per_second": { + "deno": 0.35227894489602546, + "node": 0.3230721733354392, + "bun": 0.1519515615378463 + }, + "mean_ns": { + "deno": 2838659575, + "node": 3095283600.8, + "bun": 6581044576.833333 + }, + "files_iterated": { + "deno": 2434, + "node": 2434, + "bun": 2434 + } + }, + { + "group": "parse/typescript", + "name": "tsv-json", + "ops_per_second": { + "deno": 0.5528114711248244, + "node": 0.4864556603500567, + "bun": 0.7400072723918691 + }, + "mean_ns": { + "deno": 1808934966.5, + "node": 2055685813.75, + "bun": 1351338071 + }, + "files_iterated": { + "deno": 2434, + "node": 2434, + "bun": 2434 + } + }, + { + "group": "parse/typescript", + "name": "tsv-json-no-locations", + "ops_per_second": { + "deno": 1.145465982082649, + "node": 1.0017326620919327, + "bun": 1.283661154144797 + }, + "mean_ns": { + "deno": 873007156.6, + "node": 998270334.8333334, + "bun": 779021782.1666666 + }, + "files_iterated": { + "deno": 2434, + "node": 2434, + "bun": 2434 + } + }, + { + "group": "parse/typescript", + "name": "tsv_wasm-json", + "ops_per_second": { + "deno": 0.47385486766100227, + "node": 0.4565405972186589, + "bun": 0.6842257166812827 + }, + "mean_ns": { + "deno": 2110350802, + "node": 2190385709.6, + "bun": 1461506014.2 + }, + "files_iterated": { + "deno": 2434, + "node": 2434, + "bun": 2434 + } + }, + { + "group": "parse/typescript", + "name": "tsv_wasm-json-no-locations", + "ops_per_second": { + "deno": 0.9401759352694344, + "node": 0.9106175234409573, + "bun": 1.1666136412897596 + }, + "mean_ns": { + "deno": 1063630712.6, + "node": 1098155893.4, + "bun": 857181816.3333334 + }, + "files_iterated": { + "deno": 2434, + "node": 2434, + "bun": 2434 + } + }, + { + "group": "parse/typescript", + "name": "tsv-internal", + "ops_per_second": { + "deno": 7.593919350391545, + "node": 7.24994471387352, + "bun": 8.29899627514646 + }, + "mean_ns": { + "deno": 131684306.06896552, + "node": 137932086.30769232, + "bun": 120496499.4375 + }, + "files_iterated": { + "deno": 2434, + "node": 2434, + "bun": 2434 + } + }, + { + "group": "parse/typescript", + "name": "tsv_wasm-internal", + "ops_per_second": { + "deno": 4.844409174820336, + "node": 5.449892744636837, + "bun": 5.698779253363164 + }, + "mean_ns": { + "deno": 206423521.2, + "node": 183489849.5909091, + "bun": 175476177.5357143 + }, + "files_iterated": { + "deno": 2434, + "node": 2434, + "bun": 2434 + } + }, + { + "group": "parse/typescript", + "name": "oxc-parser", + "ops_per_second": { + "deno": 0.8624090264812094, + "node": 0.7526855940543842, + "bun": 1.047915908090529 + }, + "mean_ns": { + "deno": 1159542594.4, + "node": 1328575979, + "bun": 954275044.6666666 + }, + "files_iterated": { + "deno": 2434, + "node": 2434, + "bun": 2434 + } + }, + { + "group": "parse/typescript", + "name": "oxc-parser-wasm", + "ops_per_second": { + "deno": 0.768719704502768, + "node": 0.736063991113934 + }, + "mean_ns": { + "deno": 1300864273.6, + "node": 1358577531.4 + }, + "files_iterated": { + "deno": 2434, + "node": 2434 + } + }, + { + "group": "format/typescript", + "name": "prettier", + "ops_per_second": { + "deno": 0.08265464739794595, + "node": 0.07468151706634057, + "bun": 0.0626242936249232 + }, + "mean_ns": { + "deno": 12098533252.285715, + "node": 13390193976.8, + "bun": 15968243985.142857 + }, + "files_iterated": { + "deno": 2435, + "node": 2435, + "bun": 2435 + } + }, + { + "group": "format/typescript", + "name": "tsv", + "ops_per_second": { + "deno": 1.9912721494847867, + "node": 1.9597033567241435, + "bun": 1.9399248269817906 + }, + "mean_ns": { + "deno": 502191526.28571427, + "node": 510281312, + "bun": 515483892 + }, + "files_iterated": { + "deno": 2435, + "node": 2435, + "bun": 2435 + } + }, + { + "group": "format/typescript", + "name": "tsv_wasm", + "ops_per_second": { + "deno": 1.2720122080964626, + "node": 1.4036473636379396, + "bun": 1.4393877539677782 + }, + "mean_ns": { + "deno": 786155976.8333334, + "node": 712429650, + "bun": 694739827.5714285 + }, + "files_iterated": { + "deno": 2435, + "node": 2435, + "bun": 2435 + } + }, + { + "group": "format/typescript", + "name": "oxfmt", + "ops_per_second": { + "deno": 1.2039315321874253, + "node": 1.1804785536499742, + "bun": 0.9945970863022248 + }, + "mean_ns": { + "deno": 830612018.4285715, + "node": 847114076.6666666, + "bun": 1005432263.75 + }, + "files_iterated": { + "deno": 2435, + "node": 2435, + "bun": 2435 + } + }, + { + "group": "format/typescript", + "name": "biome-wasm", + "ops_per_second": { + "deno": 0.24400636365570735, + "node": 0.21548659629049075 + }, + "mean_ns": { + "deno": 4098253771, + "node": 4640659870.333333 + }, + "files_iterated": { + "deno": 2435, + "node": 2435 + } + }, + { + "group": "parse/css", + "name": "svelte/compiler", + "ops_per_second": { + "deno": 99.27831750991159, + "node": 110.63306485324856, + "bun": 68.18371992928869 + }, + "mean_ns": { + "deno": 10072692.860655738, + "node": 9038889.063829787, + "bun": 14666257.591065291 + }, + "files_iterated": { + "deno": 49, + "node": 49, + "bun": 49 + } + }, + { + "group": "parse/css", + "name": "tsv-json", + "ops_per_second": { + "deno": 66.5873934340344, + "node": 57.47773991434453, + "bun": 73.63477107334026 + }, + "mean_ns": { + "deno": 15017857.71192053, + "node": 17398039.684410647, + "bun": 13580540.625352113 + }, + "files_iterated": { + "deno": 49, + "node": 49, + "bun": 49 + } + }, + { + "group": "parse/css", + "name": "tsv_wasm-json", + "ops_per_second": { + "deno": 52.32935697865012, + "node": 54.68347807639267, + "bun": 68.46106080515385 + }, + "mean_ns": { + "deno": 19109732.237068966, + "node": 18287059.184549358, + "bun": 14606843.48502994 + }, + "files_iterated": { + "deno": 49, + "node": 49, + "bun": 49 + } + }, + { + "group": "parse/css", + "name": "tsv-internal", + "ops_per_second": { + "deno": 304.7757571691321, + "node": 291.8849689463387, + "bun": 325.19575279279934 + }, + "mean_ns": { + "deno": 3281100.8634294383, + "node": 3426007.1822466613, + "bun": 3075070.9116338207 + }, + "files_iterated": { + "deno": 49, + "node": 49, + "bun": 49 + } + }, + { + "group": "parse/css", + "name": "tsv_wasm-internal", + "ops_per_second": { + "deno": 184.7561962029366, + "node": 203.9404004919006, + "bun": 217.84005813175878 + }, + "mean_ns": { + "deno": 5412538.364351245, + "node": 4903393.332503113, + "bun": 4590523.93107221 + }, + "files_iterated": { + "deno": 49, + "node": 49, + "bun": 49 + } + }, + { + "group": "format/css", + "name": "prettier", + "ops_per_second": { + "deno": 1.9335173437011668, + "node": 1.8124741341363197, + "bun": 1.912133053851122 + }, + "mean_ns": { + "deno": 517192154.1111111, + "node": 551732011.6, + "bun": 522976159 + }, + "files_iterated": { + "deno": 49, + "node": 49, + "bun": 49 + } + }, + { + "group": "format/css", + "name": "tsv", + "ops_per_second": { + "deno": 148.60107416363323, + "node": 142.20257063292027, + "bun": 147.4625555531239 + }, + "mean_ns": { + "deno": 6729426.456896552, + "node": 7032221.678899082, + "bun": 6781382.543175488 + }, + "files_iterated": { + "deno": 49, + "node": 49, + "bun": 49 + } + }, + { + "group": "format/css", + "name": "tsv_wasm", + "ops_per_second": { + "deno": 88.44974187950501, + "node": 98.43734946842822, + "bun": 101.39867650427693 + }, + "mean_ns": { + "deno": 11305855.492063493, + "node": 10158745.69358178, + "bun": 9862061.660714285 + }, + "files_iterated": { + "deno": 49, + "node": 49, + "bun": 49 + } + }, + { + "group": "format/css", + "name": "oxfmt", + "ops_per_second": { + "deno": 56.355769067169135, + "node": 55.27600072694931, + "bun": 47.442921582011735 + }, + "mean_ns": { + "deno": 17744412.26785714, + "node": 18091033.845588237, + "bun": 21077959.92857143 + }, + "files_iterated": { + "deno": 49, + "node": 49, + "bun": 49 + } + }, + { + "group": "format/css", + "name": "biome-wasm", + "ops_per_second": { + "deno": 10.267159807066765, + "node": 7.9097448512290125 + }, + "mean_ns": { + "deno": 97397919.07317074, + "node": 126426328.38461539 + }, + "files_iterated": { + "deno": 49, + "node": 49 + } + } + ] +} diff --git a/src/routes/docs/benchmarks/benchmarks_cross_runtime.ts b/src/routes/docs/benchmarks/benchmarks_cross_runtime.ts new file mode 100644 index 0000000..00d2a45 --- /dev/null +++ b/src/routes/docs/benchmarks/benchmarks_cross_runtime.ts @@ -0,0 +1,5 @@ +import type {CrossRuntimeReport} from './benchmark_data.ts'; + +import json from './benchmarks_cross_runtime.json' with {type: 'json'}; + +export const benchmarks_cross_runtime_json: CrossRuntimeReport = json as CrossRuntimeReport; diff --git a/src/routes/docs/introduction/+page.svelte b/src/routes/docs/introduction/+page.svelte index dfefa5f..478eab2 100644 --- a/src/routes/docs/introduction/+page.svelte +++ b/src/routes/docs/introduction/+page.svelte @@ -47,10 +47,9 @@ reconstruct_locations(ast, 'const x = 1;');`;

Compared to Oxc, Biome, and SWC, tsv is a set of focused tools, not a generic language - platform, so the focus is web standards and there's no support for JSX/SCSS/etc, beyond Svelte - as the only JS framework. The extensibility story is currently limited to using its Rust - crates as libraries; bridging to JS or WASM plugins is an open question, but may not be - supported. + platform, so the focus is Web standards + Svelte and there's no support for JSX/SCSS/etc. The + extensibility story is currently limited to using its Rust crates as libraries (or forking); + bridging to JS or WASM plugins is an open question (leaning against).

tsv prioritizes, in order:

    @@ -60,12 +59,11 @@ reconstruct_locations(ast, 'const x = 1;');`;
  1. extensibility (valued but deprioritized)

- See the benchmarks for stats. Compared to Oxc - and Biome, tsv is significantly faster, smaller, and uses less memory to parse and format its supported - languages + See the for stats. Compared to Oxc and Biome, tsv is faster, + smaller, and uses less memory to parse and format its supported languages.

- This is an early release, and reports and feedback are appreciated - see the + This is an early release, and reports and feedback are appreciated — see the issues and discussions.

@@ -95,7 +93,7 @@ reconstruct_locations(ast, 'const x = 1;');`; See the for size and performance details.

- Native builds are not yet available but are coming in v0.2, see + Native builds are not yet published — follow issue 139.

@@ -120,14 +118,14 @@ reconstruct_locations(ast, 'const x = 1;');`; For TypeScript and Svelte, the parsers also emit a span-only AST that drops the per-node loc (line/column) object — Svelte also drops name_loc — for a ~46% smaller, faster-to-materialize result, mirroring acorn's locations: false. Line - and column stay derivable from the start/end offsets plus your source, - so nothing is lost when you have the source. + and column stay derivable from the start/end offsets plus your + source, so nothing is lost when you have the source.

reconstruct_locations(ast, source) walks the tree and adds loc - back, mutating in place — exact for TypeScript, approximate for Svelte (it skips the parser's - own + back, mutating in place — exact for TypeScript, approximate for Svelte (it skips the + parser's own name_loc and a couple of position quirks). For sparse lookups, create_locator(source) reuses one line table across calls. CSS has no loc, so there's no span-only variant for it. @@ -137,11 +135,12 @@ reconstruct_locations(ast, 'const x = 1;');`;

diff --git a/src/routes/docs/playground/+page.svelte b/src/routes/docs/playground/+page.svelte index f9c38ed..5f1af8b 100644 --- a/src/routes/docs/playground/+page.svelte +++ b/src/routes/docs/playground/+page.svelte @@ -10,7 +10,7 @@ const tome = tome_get_by_slug('playground'); // The `/docs` index renders every tome's component via `DocsContent`, which would - // mount `Playground` and pull in its ~900KB WASM. Guard like fuz_css's api tome: + // mount `Playground` and pull in its ~900KB-gzipped WASM. Guard like fuz_css's api tome: // show only a link at the root, the full playground at `/docs/playground`. const at_root = $derived(page.url.pathname === DOCS_PATH); @@ -31,7 +31,8 @@

See the for setup and API usage. The textarea above uses fuz_code's - CodeTextarea component. + CodeTextarea component.

{/if} diff --git a/src/routes/docs/playground/Playground.svelte b/src/routes/docs/playground/Playground.svelte index ba20a09..ca2825b 100644 --- a/src/routes/docs/playground/Playground.svelte +++ b/src/routes/docs/playground/Playground.svelte @@ -6,12 +6,13 @@ import Code from '@fuzdev/fuz_code/Code.svelte'; import CodeTextarea from '@fuzdev/fuz_code/CodeTextarea.svelte'; import {supports_css_highlight_api} from '@fuzdev/fuz_code/highlight_manager.ts'; + import CopyToClipboard from '@fuzdev/fuz_ui/CopyToClipboard.svelte'; import {to_error_message} from '@fuzdev/fuz_util/error.ts'; import {playground_example} from './playground_example.ts'; // `@fuzdev/tsv_wasm` is loaded lazily, in the browser only — a dynamic import - // so the ~900KB WASM lands in its own chunk, fetched the first time this + // so the ~900KB-gzipped WASM lands in its own chunk, fetched the first time this // component mounts and never pulled into `/docs` or the prerendered HTML. let tsv: typeof import('@fuzdev/tsv_wasm') | null = $state(null); let load_error: string | null = $state(null); @@ -72,6 +73,8 @@ }; }); + const is_base = $derived(source === playground_example); + const reset = (): void => { source = playground_example; }; @@ -84,8 +87,8 @@ }; -
- +
+ @@ -112,7 +115,10 @@

formatted

AST

- +
+ + +
{/if} @@ -132,6 +138,24 @@ section :global(.ast) { max-height: 500px; } + /* float a copy button over the AST pane's top-right corner; it stays pinned as + the pane scrolls since it's absolute to this wrapper, not inside the scroller */ + .ast_output { + position: relative; + } + .ast_output :global(.ast_copy) { + --font_size: var(--font_size_lg); + position: absolute; + top: var(--space_xs); + /* clear the pane's scrollbar */ + right: var(--space_md); + z-index: 1; + background: var(--shade_00); + } + /* TODO hacky */ + .ast_output :global(.ast_copy > div) { + width: auto !important; + } .error { color: var(--color_e_40); white-space: pre-wrap; diff --git a/src/test/benchmark_data.test.ts b/src/test/benchmark_data.test.ts index 0f6e8d0..e55cc03 100644 --- a/src/test/benchmark_data.test.ts +++ b/src/test/benchmark_data.test.ts @@ -1,9 +1,23 @@ import {assert, describe, test} from 'vitest'; import {benchmarks_json} from '$routes/docs/benchmarks/benchmarks.ts'; +import {benchmarks_conformance_json} from '$routes/docs/benchmarks/benchmarks_conformance.ts'; +import {benchmarks_cross_runtime_json} from '$routes/docs/benchmarks/benchmarks_cross_runtime.ts'; +import {benchmarks_cli} from '$routes/docs/benchmarks/benchmarks_cli.ts'; import { + categorize_size_capability, + compute_baseline_ratio, derive_benchmark_groups, + derive_conformance_groups, + derive_corpus_repos, + derive_cross_runtime_groups, + derive_size_groups, derive_speedup_summary, + format_coverage_percent, + format_speedup_signed, + order_cross_runtime_runtimes, + OXC_FULL_LABEL, + OXFMT_WASM_LABEL, } from '$routes/docs/benchmarks/benchmark_data.ts'; // Shape gate for the committed benchmarks.json: the bench report format drifts @@ -12,13 +26,14 @@ import { // When `npm run update-benchmarks` pulls in a new shape, these fail loudly. describe('benchmarks.json shape', () => { test('baseline version is current', () => { - assert.isAtLeast(benchmarks_json.version, 4); + // version 5 is the first per-runtime report shape (carries `runtime`) + assert.isAtLeast(benchmarks_json.version, 5); }); - test('binary sizes include the ratio anchors', () => { + test('binary sizes include the flagship tsv builds', () => { const labels = benchmarks_json.binary_sizes.map((s) => s.label); - assert.include(labels, 'tsv (native)'); // native anchor - assert.include(labels, 'tsv_wasm'); // wasm anchor (the full build) + assert.include(labels, 'tsv (napi)'); // flagship N-API build (perf report anchor) + assert.include(labels, 'tsv_wasm'); // the full wasm build — smallest full-toolchain, size baseline }); test('versions carries the keys the meta component renders', () => { @@ -35,13 +50,105 @@ describe('benchmarks.json shape', () => { } }); - test('every group derives a canonical entry and a timed-set count', () => { + test('every group leads with its canonical entry (the default anchor) and a timed-set count', () => { const groups = derive_benchmark_groups(benchmarks_json); assert.isAtLeast(groups.length, 6); // format+parse × svelte/typescript/css for (const group of groups) { const key = `${group.operation}/${group.language}`; assert.ok(group.canonical_entry, `${key} has no canonical entry`); + // the canonical reference sorts first, so it's the default 1.0x anchor the + // shared component reads off the leading row + assert.strictEqual(group.entries[0]?.category, 'canonical', `${key} canonical leads`); assert.isNotNull(group.files_iterated, `${key} has no files_iterated`); + for (const entry of group.entries) { + if (entry.disabled) continue; + // measured entries render the whole-sweep mean (total corpus time); a + // zero would misread as an instantaneous run + assert.isAbove(entry.mean_ns, 0, `${key}/${entry.name} has no mean`); + } + } + }); + + test('svelte/css parse groups get disabled oxc placeholders, typescript keeps real ones', () => { + const groups = derive_benchmark_groups(benchmarks_json); + const parse = (language: string) => + groups.find((g) => g.operation === 'parse' && g.language === language); + + // typescript actually runs oxc-parser — its oxc entries are real, not placeholders + const ts = parse('typescript'); + const ts_oxc = ts?.entries.filter((e) => e.category === 'oxc') ?? []; + assert.isNotEmpty(ts_oxc); + for (const e of ts_oxc) assert.isNotOk(e.disabled, `${e.name} should be a real entry`); + + // svelte and css mirror those oxc entries in, disabled, in the same fixed slot: + // directly after the biome placeholder (both lead the cross-tool comparisons, + // right after the canonical row) and before tsv's json wires + for (const language of ['svelte', 'css']) { + const group = parse(language); + assert.ok(group, `${language} parse group missing`); + const oxc = group.entries.filter((e) => e.category === 'oxc'); + assert.strictEqual(oxc.length, ts_oxc.length, `${language} oxc placeholder count`); + for (const e of oxc) { + assert.ok(e.disabled, `${language} ${e.name} should be disabled`); + assert.strictEqual(e.bar_fraction, 0, `${language} ${e.name} bar`); + } + const names = group.entries.map((e) => e.name); + const first_biome = names.findIndex((n) => n.includes('biome')); + const first_oxc = names.findIndex((n) => n.includes('oxc')); + const first_json = names.findIndex((n) => n.endsWith('-json') || n.endsWith('-no-locations')); + assert.strictEqual(first_oxc, first_biome + 1, `${language} oxc directly after biome`); + assert.isBelow(first_oxc, first_json, `${language} oxc before the tsv json entries`); + } + }); + + test('every parse group gets a disabled biome placeholder, since biome never exposes a parser to JS', () => { + const groups = derive_benchmark_groups(benchmarks_json); + for (const language of ['svelte', 'typescript', 'css']) { + const group = groups.find((g) => g.operation === 'parse' && g.language === language); + assert.ok(group, `${language} parse group missing`); + const biome = group.entries.filter((e) => e.category === 'biome'); + assert.strictEqual(biome.length, 1, `${language} biome placeholder count`); + const [entry] = biome; + assert.ok(entry, `${language} biome entry missing`); + assert.ok(entry.disabled, `${language} biome entry should be disabled`); + assert.strictEqual(entry.bar_fraction, 0, `${language} biome bar`); + + // biome leads the cross-tool comparisons: directly after the single canonical + // row (index 0), before tsv's json wires + const names = group.entries.map((e) => e.name); + const first_biome = names.findIndex((n) => n.includes('biome')); + const first_json = names.findIndex((n) => n.endsWith('-json') || n.endsWith('-no-locations')); + assert.strictEqual(first_biome, 1, `${language} biome directly after the canonical row`); + assert.isBelow(first_biome, first_json, `${language} biome before the tsv json entries`); + } + }); + + test('format/parse rows follow the fixed canonical → biome → oxc → json → internal order', () => { + // independently mirrors the requested row order so a regression in the + // derivation's comparator fails here + const tier = (name: string, category: string): number => { + if (category === 'canonical') return 0; + if (category === 'biome') return 1; + if (category === 'oxc') return 2; + if (name.endsWith('-no-locations')) return 3; // tsv json, span-only wire + if (name.endsWith('-json')) return 4; // tsv json, loc-carrying wire + return 5; // tsv internal engine + }; + for (const group of derive_benchmark_groups(benchmarks_json)) { + const key = `${group.operation}/${group.language}`; + const tiers = group.entries.map((e) => tier(e.name, e.category)); + assert.deepEqual( + tiers, + [...tiers].toSorted((a, b) => a - b), + `${key} tier order`, + ); + // wasm precedes native within a tier + for (let i = 1; i < group.entries.length; i++) { + if (tiers[i] !== tiers[i - 1]) continue; + const prev_wasm = group.entries[i - 1]!.name.includes('wasm'); + const curr_wasm = group.entries[i]!.name.includes('wasm'); + assert.isFalse(!prev_wasm && curr_wasm, `${key} native precedes wasm within a tier`); + } } }); @@ -54,4 +161,289 @@ describe('benchmarks.json shape', () => { assert.isDefined(row.format_css, row.variant); } }); + + test('binary sizes group by capability with one smallest-build ratio anchor', () => { + const groups = derive_size_groups(benchmarks_json.binary_sizes); + // full / formatter / parser, in that order, all present in the current data + assert.deepStrictEqual( + groups.map((g) => g.capability), + ['full', 'formatter', 'parser'], + ); + for (const group of groups) { + assert.isNotEmpty(group.entries); + // every entry lands in the group its capability names + for (const e of group.entries) { + assert.strictEqual(categorize_size_capability(e.label), group.capability); + } + // the group's default ratio anchor (its 1.0x row) is the single smallest real + // build, and it leads the group — the shared component reads every ratio off + // the first row. Disabled placeholders (e.g. oxfmt's absent wasm build) never + // sort first. + const real = group.entries.filter((e) => !e.disabled); + const smallest = real.reduce((a, b) => (a.bytes <= b.bytes ? a : b)); + assert.strictEqual( + group.entries[0]?.label, + smallest.label, + `${group.capability} smallest leads`, + ); + } + // the parser group pits tsv against oxc-parser in both kinds + const parser = groups.find((g) => g.capability === 'parser'); + const parser_labels = parser?.entries.map((e) => e.label) ?? []; + assert.include(parser_labels, 'oxc-parser (wasm)'); + assert.include(parser_labels, 'oxc-parser (napi)'); + }); + + test('full toolchain group carries the combined oxc-parser + oxfmt entry', () => { + const sizes = benchmarks_json.binary_sizes; + const groups = derive_size_groups(sizes); + const full = groups.find((g) => g.capability === 'full'); + const combined = full?.entries.find((e) => e.label === OXC_FULL_LABEL); + assert.ok(combined, 'combined oxc entry missing from full toolchain group'); + + // its bytes and gzip are the sum of oxc's separate parser and formatter builds + const oxc_parser = sizes.find((s) => s.label === 'oxc-parser (napi)'); + const oxfmt = sizes.find((s) => s.label === 'oxfmt (napi)'); + assert.ok(oxc_parser && oxfmt, 'source oxc builds missing'); + assert.strictEqual(combined.bytes, oxc_parser.bytes + oxfmt.bytes); + assert.strictEqual(combined.gzip_bytes, oxc_parser.gzip_bytes! + oxfmt.gzip_bytes!); + + // native build, colored as oxc + assert.strictEqual(combined.kind, 'native'); + assert.strictEqual(combined.category, 'oxc'); + }); + + test('formatter group gets a disabled oxfmt (wasm) placeholder just above oxfmt (napi), since oxfmt has no wasm build', () => { + const groups = derive_size_groups(benchmarks_json.binary_sizes); + const formatter = groups.find((g) => g.capability === 'formatter'); + assert.ok(formatter, 'formatter group missing'); + + const placeholder = formatter.entries.find((e) => e.label === OXFMT_WASM_LABEL); + assert.ok(placeholder, 'oxfmt (wasm) placeholder missing'); + assert.ok(placeholder.disabled, 'oxfmt (wasm) should be disabled'); + assert.strictEqual(placeholder.kind, 'wasm'); + assert.strictEqual(placeholder.category, 'oxc'); + assert.strictEqual(placeholder.bar_fraction, 0); + + const labels = formatter.entries.map((e) => e.label); + const placeholder_index = labels.indexOf(OXFMT_WASM_LABEL); + const native_index = labels.indexOf('oxfmt (napi)'); + assert.isAbove(native_index, -1, 'oxfmt (napi) missing from formatter group'); + assert.strictEqual( + placeholder_index, + native_index - 1, + 'placeholder should sit just above oxfmt (napi)', + ); + }); + + test('flagship report is the node runtime', () => { + // the headline detailed view switched to N-API under Node; guards against an + // `update-benchmarks` that pulls the wrong runtime's sibling report + assert.strictEqual(benchmarks_json.runtime, 'node'); + }); +}); + +// Shape gate for the committed conformance report `benchmarks_conformance.json` +// (tsv's `report.conformance.node.json` — the parse-coverage surface over the +// deliberately-hard fixture suites, disjoint from the perf corpus, Svelte set minus +// canonical-rejects), consumed by the Parse conformance section. +describe('benchmarks_conformance.json shape', () => { + test('report is the conformance surface at the current version', () => { + assert.isAtLeast(benchmarks_conformance_json.version, 6); + assert.strictEqual(benchmarks_conformance_json.corpus_kind, 'conformance'); + assert.strictEqual(benchmarks_conformance_json.runtime, 'node'); + }); + + test('conformance report is parse-only', () => { + for (const entry of benchmarks_conformance_json.entries) { + assert.match(entry.group, /^parse\//, `${entry.group}/${entry.name}`); + } + }); + + test('corpus sources disclose the composition', () => { + assert.isNotEmpty(benchmarks_conformance_json.corpus_sources ?? []); + }); + + test('derives one coverage group per language, each with a tsv row and full coverage data', () => { + const groups = derive_conformance_groups(benchmarks_conformance_json); + assert.strictEqual(groups.length, 3); // svelte / typescript / css + assert.deepEqual( + groups.map((g) => g.language), + ['svelte', 'typescript', 'css'], + ); + for (const group of groups) { + assert.ok( + group.rows.some((r) => r.name === 'tsv'), + `${group.language} has a tsv row`, + ); + // rows are ordered by coverage, highest first + const fractions = group.rows.map((r) => r.coverage_fraction); + assert.deepEqual( + fractions, + [...fractions].toSorted((a, b) => b - a), + `${group.language} rows descend by coverage`, + ); + for (const row of group.rows) { + assert.isAbove(row.files_total, 0, `${group.language}/${row.name} total`); + assert.isAtLeast(row.coverage_fraction, 0); + assert.isAtMost(row.coverage_fraction, 1); + // engine-level rows only — binding/materialization variants are folded + assert.notMatch(row.name, /-internal|wasm-|-wasm/, `${group.language}/${row.name}`); + } + } + }); + + test('coverage percent floors — only exact totality reads 100%', () => { + // 44219/44220 rounds to 100.00% but must not display as it: floor, so a + // visibly non-total count never sits beside a "100.00%" label. + assert.strictEqual(format_coverage_percent(44_219 / 44_220), '99.99%'); + assert.strictEqual(format_coverage_percent(1), '100.00%'); + assert.strictEqual(format_coverage_percent(0.998549), '99.85%'); + assert.strictEqual(format_coverage_percent(0), '0.00%'); + }); +}); + +describe('derive_corpus_repos', () => { + test('maps the committed corpus sources to deduped org/name repo links', () => { + const repos = derive_corpus_repos(benchmarks_json.corpus_sources); + assert.isNotEmpty(repos); + // one entry per URL — no repo appears twice even though svelte.dev contributes + // several source subpaths + const urls = repos.map((r) => r.url); + assert.strictEqual(new Set(urls).size, urls.length, 'urls are distinct'); + const svelte_dev = repos.filter((r) => r.url === 'https://github.com/sveltejs/svelte.dev'); + assert.strictEqual(svelte_dev.length, 1, 'svelte.dev collapses to one entry'); + // each label is the linkified `org/name`, derived from (and ending) its URL + for (const repo of repos) { + assert.match(repo.label, /^[^/]+\/[^/]+$/, repo.url); + assert.isTrue(repo.url.endsWith(repo.label), `${repo.url} ends with ${repo.label}`); + } + }); + + test('collapses shared repos and drops sources with no mapped repo', () => { + const repos = derive_corpus_repos([ + {path: '../zzz/src', files: 1}, + {path: '../svelte.dev/apps/svelte.dev/src', files: 1}, + {path: '../svelte.dev/packages/repl/src', files: 1}, // same repo → collapsed + {path: 'benches/js/.cache/svelte_styles', files: 1}, // not a repo → dropped + ]); + assert.deepStrictEqual(repos, [ + {url: 'https://github.com/fuzdev/zzz', label: 'fuzdev/zzz'}, + { + url: 'https://github.com/sveltejs/svelte.dev', + label: 'sveltejs/svelte.dev', + }, + ]); + }); + + test('handles a missing corpus_sources field', () => { + assert.deepStrictEqual(derive_corpus_repos(undefined), []); + }); +}); + +describe('format_speedup_signed', () => { + test('anchor-and-faster entries read as a plain multiple', () => { + assert.strictEqual(format_speedup_signed(1), '1.00x'); + assert.strictEqual(format_speedup_signed(2.5), '2.50x'); + assert.strictEqual(format_speedup_signed(12.3), '12.3x'); // >= 10 drops to one decimal + }); + + test('slower entries negate the reciprocal so the factor is directly legible', () => { + assert.strictEqual(format_speedup_signed(0.15), '-6.67x'); + assert.strictEqual(format_speedup_signed(0.05), '-20.0x'); // >= 10 magnitude → one decimal + assert.strictEqual(format_speedup_signed(0.98), '-1.02x'); // near-parity sign flip + }); +}); + +describe('compute_baseline_ratio', () => { + test('speed reads the anchor as the reference — faster entries exceed 1', () => { + // anchor 100ns; a 50ns entry is 2x faster, a 200ns entry is half the speed + assert.strictEqual(compute_baseline_ratio('speed', 50, 100), 2); + assert.strictEqual(compute_baseline_ratio('speed', 200, 100), 0.5); + }); + + test('size reads the anchor as the reference — bigger entries exceed 1', () => { + // anchor 100 bytes; a 300-byte build is 3x bigger, a 50-byte build is half + assert.strictEqual(compute_baseline_ratio('size', 300, 100), 3); + assert.strictEqual(compute_baseline_ratio('size', 50, 100), 0.5); + }); +}); + +describe('order_cross_runtime_runtimes', () => { + test('reorders the report storage order to node-first display order', () => { + assert.deepStrictEqual(order_cross_runtime_runtimes(['deno', 'node', 'bun']), [ + 'node', + 'deno', + 'bun', + ]); + }); + + test('anchors on the next runtime in display order when node is absent', () => { + assert.deepStrictEqual(order_cross_runtime_runtimes(['bun', 'deno']), ['deno', 'bun']); + assert.deepStrictEqual(order_cross_runtime_runtimes([]), []); + }); +}); + +// Shape gate for the committed cross-runtime `benchmarks_cross_runtime.json` (the +// bench composer's combined `report.json`) — a different, slimmer shape than the +// per-runtime baseline, consumed by the Cross-runtime section. +describe('benchmarks_cross_runtime.json shape', () => { + test('combined report carries the current version and kind', () => { + assert.isAtLeast(benchmarks_cross_runtime_json.version, 5); + assert.strictEqual(benchmarks_cross_runtime_json.kind, 'combined'); + // the committed fixture must be same-vintage — if this trips, re-run every + // runtime and recompose rather than committing a mixed set (the site would + // show the unreliable-ratios warning banner) + assert.notStrictEqual(benchmarks_cross_runtime_json.mixed_vintage, true); + }); + + test('runtimes include the flagship and its cross-runtime peers', () => { + const {runtimes} = benchmarks_cross_runtime_json; + assert.include(runtimes, 'node'); // the flagship the headline view leads with + assert.include(runtimes, 'deno'); + assert.include(runtimes, 'bun'); + }); + + test('every group derives rows with the flagship runtime populated', () => { + const groups = derive_cross_runtime_groups(benchmarks_cross_runtime_json); + assert.isAtLeast(groups.length, 6); // format+parse × svelte/typescript/css + for (const group of groups) { + assert.isAbove(group.rows.length, 0, group.group); + for (const row of group.rows) { + assert.isNumber(row.ops_per_second.node, `${group.group}/${row.name} missing node ops`); + // ratios anchor on node (the display-order base, not the report's + // deno-first storage order), so node's own ratio is exactly 1 + assert.strictEqual(row.ratio_vs_base.node, 1, `${group.group}/${row.name} node anchor`); + } + } + }); +}); + +// Shape gate for the hand-maintained CLI snapshot `benchmarks_cli.ts` (copied from +// the linked oxc-bench-formatter fork). Unlike the JSON reports it isn't generated, +// so a bad hand-edit (a scenario missing its `tsv` reference row, a zeroed metric) +// would silently render an empty table rather than fail to typecheck. +describe('benchmarks_cli shape', () => { + test('every scenario carries a tsv reference row with positive metrics', () => { + assert.isAtLeast(benchmarks_cli.scenarios.length, 1); + for (const scenario of benchmarks_cli.scenarios) { + // BenchmarksCli anchors every ratio on the tsv row; without it the table is empty + assert.ok( + scenario.results.some((r) => r.label === 'tsv'), + `${scenario.key} is missing its tsv row`, + ); + for (const r of scenario.results) { + assert.isAbove(r.wall_ms, 0, `${scenario.key}/${r.label} wall_ms`); + assert.isAbove(r.cpu_ms, 0, `${scenario.key}/${r.label} cpu_ms`); + assert.isAbove(r.memory_mb, 0, `${scenario.key}/${r.label} memory_mb`); + } + } + }); + + test('source links point at the fork (on its tsv branch) and its upstream', () => { + // the fork's `main` tracks upstream and carries neither tsv nor the analysis, + // so every fork link must target the `tsv` branch + assert.match(benchmarks_cli.source_url, /ryanatkn\/oxc-bench-formatter\/tree\/tsv$/); + assert.match(benchmarks_cli.upstream_url, /oxc-project\/bench-formatter/); + }); }); diff --git a/svelte.config.js b/svelte.config.js index 1fd7b37..fec5a80 100644 --- a/svelte.config.js +++ b/svelte.config.js @@ -16,6 +16,8 @@ export default { paths: {relative: false}, // use root-absolute paths for SSR path comparison: https://svelte.dev/docs/kit/configuration#paths alias: {$routes: 'src/routes'}, version: {name: execSync('git rev-parse HEAD').toString().trim()}, - csp: {directives: create_csp_directives({extend: [csp_directives_of_fuzdev]})}, + csp: { + directives: create_csp_directives({extend: [csp_directives_of_fuzdev]}), + }, }, }; diff --git a/tsconfig.json b/tsconfig.json index 0300902..6e255c0 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,7 +1,9 @@ { "extends": "./.svelte-kit/tsconfig.json", "compilerOptions": { - "types": ["@sveltejs/kit"], + "types": [ + "@sveltejs/kit" + ], "module": "nodenext", "moduleResolution": "nodenext", "strict": true,