Tokens
An AI reads and writes code by the token, so Wisp measures an app's cost in tokens and keeps it low (Design and files). Run cargo run -q -p wisp-tokens --release for the numbers.
Five Features, Counted by wisp-tokens
bench/tokens/apps holds the same five features as a complete app in each stack:
- a list page loading its data
- a contact form (name 1 to 50 characters, a valid email)
- a JSON endpoint of the list
- a layout with a nav
- a live search filtered in the browser
An invalid form gets a 422 with each error shown and the typed text kept. A valid form gets a redirect. Every stack uses Wisp's messages word for word: a name of 1 to 50 characters ("must have at least 1 character", "must have at most 50 characters"), an email valid by the HTML spec ("must be an email address"), and a missing field is a 400. Wisp has these rules built in and the other stacks write them by hand. Where a stack cannot match, it differs: Next.js server actions answer 200 instead of 422, and a missing field is a 500 there. Axum's form extractor answers a missing field with 422. Wisp also takes an email domain with non-ASCII letters or a label longer than 63 characters, as Firefox sends them, which the HTML spec's pattern refuses.
cargo run -p wisp-tokens counts the files tagged with a @feature NAME comment (method below, in bench/tokens/src/main.rs, with characters / 4 beside it). Generated files are not counted.
| Stack | list | form | api | layout | search | data | setup | total | chars / 4 | files |
|---|---|---|---|---|---|---|---|---|---|---|
| Wisp | 59 | 79 | 31 | 60 | 96 | 108 | 44 | 477 | 300 | 7 |
| SvelteKit | 128 | 602 | 57 | 83 | 192 | 72 | 0 | 1,134 | 696 | 9 |
| Next.js | 107 | 575 | 44 | 108 | 241 | 71 | 0 | 1,146 | 782 | 8 |
| Nuxt | 96 | 600 | 22 | 96 | 158 | 71 | 0 | 1,043 | 670 | 8 |
| React | 108 | 696 | 32 | 268 | 191 | 161 | 143 | 1,599 | 1,098 | 9 |
| Express | 128 | 602 | 34 | 116 | 198 | 69 | 150 | 1,297 | 742 | 8 |
| Axum | 145 | 894 | 29 | 104 | 217 | 123 | 291 | 1,803 | 1,239 | 7 |
| Actix Web | 164 | 906 | 46 | 104 | 237 | 123 | 298 | 1,878 | 1,279 | 7 |
Wisp's data (108) is longer than SvelteKit's, Next.js's, Nuxt's and Express's (69 to 72) because it is a Rust type with typed fields. Most other rows are shorter, the form most of all. There are two exceptions. In api, Nuxt (22) and Axum (29) beat Wisp (31). In setup, Wisp counts 44 and SvelteKit, Next.js and Nuxt count 0. Against Wisp's total, Nuxt is 2.2 times, SvelteKit 2.4, Next.js 2.4, Express 2.7, React 3.4, Axum 3.8 and Actix 3.9. Of the rest, 108 tokens are the Rust type and data. The remainder is markup that every stack writes.
A Real App: Auth, CRUD, Upload, Live, a Component
bench/tokens/real has sign up and sign in, a posts table with validation, edit, delete, pages, live refresh, an avatar upload and a toggle component: 11 files, 1,014 tokens in Wisp, 3.6x less than SvelteKit and 3.4x less than Next.js.
The Wisp app has tests for every feature (bench/tokens/real/wisp/src/tests.rs). The SvelteKit and Next.js apps have none and were checked by reading. They are minimal versions and weaker where Wisp checks more: they accept any image/* type the client claims, set session cookies without explicit sameSite/secure, and do not check a missing password. A secure version would cost them more tokens, so the ratio favours the rivals.
Method
Counted: every hand-written file beyond the framework's generator, plus its path. Manifests and configs count as setup by the lines added beyond the generator's. cargo run -p wisp-tokens lists each stack's counted and skipped files.
This favors Wisp, which ships the form rules, messages and markup (<form fields />). Read the totals as what an app author writes, not as equal work by each framework. Only the Wisp apps have tests.
There is no offline tokenizer, so the count estimates a BPE code tokenizer (cl100k-like):
- a newline with its indentation is 1 token; spaces join the word after them
- identifiers split at
_and camelCase humps, each part 1 token up to 8 letters and 1 per 6 after that - digits 1 per 3
- common operators (
::->=>==<//>{{{%...) are 1 token each; other punctuation is 1 token per character
Characters / 4 ranks the frameworks the same way.
Versions: SvelteKit 2 with Svelte 5 runes and use:enhance, Next.js 15 app router with server actions and useActionState, Nuxt 4 with useFetch, Express 5 with EJS, React 19 with Vite and an Express API, Axum 0.8 with askama, Actix Web 4 with tera. The Wisp versions are built by a script, so every counted line compiles. Competitor sources are in bench/tokens/apps.
The Node apps: Nuxt uses file routes, server routes in server/api, useFetch, and $fetch for the form post. Express renders EJS views with a shared header and footer and a small script for the search. React is a Vite app with React Router, useState and fetch, over a small Express API. The tool does not count their generated package.json, nuxt.config.ts and index.html, but it counts the proxy line added to vite.config.js.
Is This Page Useful?