Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ jobs:
- name: Install pnpm
uses: pnpm/action-setup@v4
with:
version: 10
version: 11

- name: Setup Node.js
uses: actions/setup-node@v6
Expand All @@ -25,7 +25,7 @@ jobs:
cache: pnpm

- name: Install dependencies
run: pnpm install
run: pnpm install --frozen-lockfile

- name: Lint
run: pnpm lint
Expand Down
24 changes: 24 additions & 0 deletions packages/benchmakrs/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*

node_modules
dist
dist-ssr
*.local

# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
3 changes: 3 additions & 0 deletions packages/benchmakrs/.vscode/extensions.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"recommendations": ["Vue.volar"]
}
43 changes: 43 additions & 0 deletions packages/benchmakrs/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# Varden Benchmarks

Performance benchmarks comparing [varden](../varden) against [vee-validate](https://vee-validate.logaretm.com/) (v4 stable and v5 beta) for Vue 3 form handling.

## What's measured

- **Form creation** — cost of initializing a `useForm` instance (scoped per run, then stopped)
- **setValue** — cost of setting field values one character at a time, simulating user typing

Both benchmarks run against two Valibot schemas:

- `logInSchema` — small form (email + password)
- `signUpSchema` — moderate form (mixed types, nested objects, arrays)

## Libraries under test

| Library | Import | Notes |
|---|---|---|
| varden | `varden` | Workspace package |
| vee-validate@4 | `vee-validate4` | Latest v4 stable, wrapped with `@vee-validate/valibot` |
| vee-validate@5 | `vee-validate5` | v5 beta (native Valibot support) |

## Usage

```sh
# Run all benchmarks
pnpm bench
```

## Structure

- `bench/schemas.ts` — Valibot schemas used across benchmarks
- `bench/creation.bench.ts` — form creation benchmarks
- `bench/operations.bench.ts` — setValue benchmarks
- `bench/setup.ts` — suppresses console warnings during bench runs
- `pages/` — interactive demo pages for visual comparison (`vee-validate/`, `varden/`)

## Demo pages

```sh
# Start dev server, then navigate to /pages/vee-validate or /pages/varden
pnpm dev
```
66 changes: 66 additions & 0 deletions packages/benchmakrs/bench/creation.bench.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import { describe, bench } from 'vitest';
import { effectScope, type EffectScope } from 'vue';
import { useForm } from 'varden';
import { useForm as useVV4Form } from 'vee-validate4';
import { useForm as useVV5Form } from 'vee-validate5';
import { toTypedSchema } from '@vee-validate/valibot';

import {
logInSchema,
signUpSchema,
} from './schemas';

const logInVv4Schema = toTypedSchema(logInSchema);
const signUpVv4Schema = toTypedSchema(signUpSchema);

describe('form creation — logInSchema', () => {
bench('varden', () => {
const scope: EffectScope = effectScope();
scope.run(() => {
useForm({ schema: logInSchema, onSubmit() {} });
});
scope.stop();
});

bench('vee-validate@4', () => {
const scope: EffectScope = effectScope();
scope.run(() => {
useVV4Form({ validationSchema: logInVv4Schema });
});
scope.stop();
});

bench('vee-validate@5', () => {
const scope: EffectScope = effectScope();
scope.run(() => {
useVV5Form({ validationSchema: logInSchema });
});
scope.stop();
});
});

describe('form creation — signUpSchema', () => {
bench('varden', () => {
const scope: EffectScope = effectScope();
scope.run(() => {
useForm({ schema: signUpSchema, onSubmit() {} });
});
scope.stop();
});

bench('vee-validate@4', () => {
const scope: EffectScope = effectScope();
scope.run(() => {
useVV4Form({ validationSchema: signUpVv4Schema });
});
scope.stop();
});

bench('vee-validate@5', () => {
const scope: EffectScope = effectScope();
scope.run(() => {
useVV5Form({ validationSchema: signUpSchema });
});
scope.stop();
});
});
74 changes: 74 additions & 0 deletions packages/benchmakrs/bench/operations.bench.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import { describe, bench, afterAll } from 'vitest';
import { effectScope, type EffectScope } from 'vue';
import { useForm } from 'varden';
import { useForm as useVV4Form } from 'vee-validate4';
import { useForm as useVV5Form } from 'vee-validate5';
import { toTypedSchema } from '@vee-validate/valibot';

import {
logInSchema,
} from './schemas';

const logInVvSchema = toTypedSchema(logInSchema);

describe('setValue — logInSchema', () => {
const username = 'johndoe@example.com';
const userNameInput: Array<string> = [];
for (let i = 1; i < username.length; i += 1) {
userNameInput.push(username[i].substring(0, i));
}

const password = 'password123';
const passwordInput: Array<string> = [];
for (let i = 1; i < password.length; i += 1) {
passwordInput.push(password[i].substring(0, i));
}

const { warn } = console;
// eslint-disable-next-line no-console
console.warn = () => {};

const vardenScope: EffectScope = effectScope();
const vardenForm = vardenScope.run(() => useForm({ schema: logInSchema, onSubmit() {} }))!;

const vv4Scope: EffectScope = effectScope();
const vv4Ctx = vv4Scope.run(() => useVV4Form({ validationSchema: logInVvSchema }))!;

const vv5Scope: EffectScope = effectScope();
const vv5Ctx = vv5Scope.run(() => useVV5Form({ validationSchema: logInSchema }))!;

afterAll(() => {
vardenScope.stop();
vv4Scope.stop();
vv5Scope.stop();
// eslint-disable-next-line no-console
console.warn = warn;
});

bench('varden', () => {
for (const v of userNameInput) {
vardenForm.setValue(['username'], v);
}
for (const v of passwordInput) {
vardenForm.setValue(['password'], v);
}
});

bench('vee-validate@4', () => {
for (const v of userNameInput) {
vv4Ctx.setFieldValue('username', v);
}
for (const v of passwordInput) {
vv4Ctx.setFieldValue('password', v);
}
});

bench('vee-validate@5', () => {
for (const v of userNameInput) {
vv5Ctx.setFieldValue('username', v);
}
for (const v of passwordInput) {
vv5Ctx.setFieldValue('password', v);
}
});
});
27 changes: 27 additions & 0 deletions packages/benchmakrs/bench/schemas.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import * as v from 'valibot';

// small form, only strings
export const logInSchema = v.object({
username: v.pipe(v.string(), v.email()),
password: v.pipe(v.string(), v.minLength(8)),
});

// moderate form, different types
export const signUpSchema = v.object({
firstName: v.string(),
lastName: v.string(),
age: v.number(),
agreeToTerms: v.boolean(),
email: v.pipe(v.string(), v.email()),
password: v.pipe(v.string(), v.minLength(8)),
questions: v.array(v.object({
question: v.string(),
answer: v.string(),
})),
address: v.object({
street: v.string(),
city: v.string(),
state: v.string(),
zip: v.string(),
}),
});
11 changes: 11 additions & 0 deletions packages/benchmakrs/bench/setup.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { beforeAll, afterAll } from 'vitest';

const { warn } = console;
beforeAll(() => {
// eslint-disable-next-line no-console
console.warn = () => {};
});
afterAll(() => {
// eslint-disable-next-line no-console
console.warn = warn;
});
54 changes: 54 additions & 0 deletions packages/benchmakrs/eslint.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import airbnb from 'eslint-stylistic-airbnb';
import pluginVue from 'eslint-plugin-vue';
import tseslint from 'typescript-eslint';
import globals from 'globals';
import pluginImport from 'eslint-plugin-import-x';

export default [
{
ignores: [
'bundle-report',
'coverage',
'dist',
],
},

...tseslint.configs.recommended,
...pluginVue.configs['flat/recommended'],

airbnb.configs['flat/recommended'],
airbnb.configs['flat/addon-iterators'],
airbnb.configs['flat/addon-typescript'],
airbnb.configs['flat/addon-vue'],
airbnb.configs['flat/addon-vue-ts'],
{
plugins: {
'import-x': pluginImport,
},
rules: {
'import-x/order': airbnb.configs['flat/addon-import'].rules['import-x/order'],
},
},
{
rules: {
'no-underscore-dangle': ['error', { allow: ['__meta'] }],
},
},

{
languageOptions: {
globals: {
...globals.browser,
},
},
},

{
files: ['vitest.config.ts'],
languageOptions: {
globals: {
...globals.node,
},
},
},
];
13 changes: 13 additions & 0 deletions packages/benchmakrs/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>benchmakrs</title>
</head>
<body>
<a href="/pages/varden/">varden</a>
<a href="/pages/vee-validate/">vee-validate</a>
</body>
</html>
30 changes: 30 additions & 0 deletions packages/benchmakrs/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
{
"name": "benchmakrs",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"bench": "vitest bench",
"build": "vue-tsc -b && vite build",
"lint": "eslint .",
"preview": "vite preview"
},
"dependencies": {
"@vee-validate/valibot": "^4.15.0",
"valibot": "^1.3.1",
"varden": "workspace:*",
"vee-validate4": "npm:vee-validate@^4.15.1",
"vee-validate5": "npm:vee-validate@beta",
"vitest": "catalog:",
"vue": "catalog:"
},
"devDependencies": {
"@types/node": "catalog:",
"@vitejs/plugin-vue": "catalog:",
"@vue/tsconfig": "catalog:",
"typescript": "catalog:",
"vite": "catalog:",
"vue-tsc": "catalog:"
}
}
Loading
Loading