class-recipe performance
- 9×as fast as class-variance-authority
- 13×as fast as tailwind-variants
- 4×as fast as either, with tailwind-merge
- 10×as fast as tailwind-variants, for slot recipes
Performance is the first goal of this library, and it is measured on every change to it. A recipe compiles its config once, when it is created, and caches the class name of each selection, so most calls are a lookup (see How it works).
Recipes
Section titled “Recipes”Each benchmark calls the same recipe, written for each library, with six
selections in turn, which cover default variants, compound variants, boolean
variants, and class overrides. Every library returns the same classes.
Without tailwind-merge, tailwind-variants runs from its lite entry
point. With it, class-variance-authority calls twMerge on every result,
as its documentation recommends.
Slot recipes
Section titled “Slot recipes”The same, for a slot recipe with six selections. class-variance-authority has no slots.
cx is a drop-in replacement for clsx, and about as fast. On every input, it runs
at least 100% as fast as clsx, and it is faster on
6 of the 6.
Calls per second on each input, in millions
| Input | class-recipe | clsx | classnames |
|---|---|---|---|
| Strings | 16.9 | 16.3 | 15.8 |
| An object | 14.9 | 14.8 | 14.0 |
| An array | 16.0 | 14.8 | 13.7 |
| Nested arrays | 9.7 | 9.1 | 8.4 |
| Mixed values | 7.9 | 7.5 | 6.9 |
| A component's classes | 16.1 | 14.2 | 15.1 |
The cache
Section titled “The cache”The first call for a selection builds its class name by concatenating precomputed strings; every later call reads it from the cache.
Method
Section titled “Method”- The benchmarks first check that each call returns the expected classes, and that every library compared returns the same ones.
- They import the package by its name, so they run the built bundle, not the sources. The test runner reads module exports through getters, so each benchmark holds the function under test in a local binding to keep that cost out of the measurement.
- Each benchmark adds up the length of the class names it gets, so the measurement adds no work of its own, such as hashing strings.
- The benchmarks assert that a recipe and a slot recipe are faster with their cache than without it.
Run them with pnpm bench, which builds the package first, or with
pnpm measure, which also saves the results shown here. The comparisons
with other libraries are in the *.compare.bench.ts files. The benchmarks
are not part of CI, because shared runners are too noisy for timing
assertions.