Skip to content

Feat: Performance enhancements#281

Open
fveracoechea wants to merge 13 commits into
joe-bell:mainfrom
fveracoechea:feat/performance-enhancements
Open

Feat: Performance enhancements#281
fveracoechea wants to merge 13 commits into
joe-bell:mainfrom
fveracoechea:feat/performance-enhancements

Conversation

@fveracoechea

@fveracoechea fveracoechea commented Apr 19, 2024

Copy link
Copy Markdown

Description

Implement code optimizations on CVA to increase execution speed and reduce the number of operations, such as iterations and object copying.

Additional context

Let's say we have a web page made of React components, where most components utilize CVA.
Since React components render and re-render often, it's important to keep CVA's runtime overhead as low as possible. This optimization will enhance scalability, especially on large projects.

Benchmark results

pnpx ts-node ./packages/cva/src/benchmark.ts
(index) branch name ops/sec average (ms) samples
0 feat/performance-enhancement 440297 0.002 2201487
1 cva/main 125161 0.008 625810 3.52 x slower

image

All tests passing with no problems:

image

What is the purpose of this pull request?

  • Bug fix
  • New Feature
  • Documentation update
  • Other

Before submitting the PR, please make sure you do the following

  • Read the Contributing Guidelines.
  • Follow the Style Guide.
  • Check that there isn't already a PR that solves the problem the same way to avoid creating a duplicate.
  • Provide a description in this PR that addresses what the PR is solving, or reference the issue that it solves (e.g. fixes #123).

@vercel

vercel Bot commented Apr 19, 2024

Copy link
Copy Markdown

@fveracoechea is attempting to deploy a commit to the cva Team on Vercel.

A member of the Team first needs to authorize it.

@fveracoechea fveracoechea changed the title Feat/performance enhancements Feat: Performance enhancements Apr 19, 2024
@fveracoechea fveracoechea marked this pull request as draft April 19, 2024 19:07
@fveracoechea fveracoechea marked this pull request as ready for review April 19, 2024 19:07
@joe-bell

Copy link
Copy Markdown
Owner

This is super thoughtful of you, thanks @fveracoechea 🙏❤️

Fairly busy at the moment and this will take some time to review, so don't be too disheartened if it takes some time to get around to this! Really appreciate it

@fveracoechea

Copy link
Copy Markdown
Author

Thanks @joe-bell 🙏 let me know if you have any suggestions or concerns, I’d be happy to help!

Comment thread packages/cva/src/index.ts Outdated
@gperdomor

This comment was marked as spam.

@joe-bell

This comment was marked as off-topic.

@gperdomor

This comment was marked as off-topic.

@joe-bell

Copy link
Copy Markdown
Owner

Hey @fveracoechea, just wanted to give you a quick update rather than leave you in the dark here!

I'm starting to pick up cva@1.0 tasks again at the moment, which means there's quite a few moving parts in flight

For now, I'm going to hold off merging this simply because it's a little easier to work with the code I'm familiar with

That said, I'm keeping this PR open as I don't want to discourage you from introducing your improvements – which are incredibly valuable! I'll keep you posted on cva@1.0s progress and let you know when's a good time to revisit this 🙏

@fveracoechea

fveracoechea commented Feb 9, 2025

Copy link
Copy Markdown
Author

Thanks @joe-bell, I appreciate the update, it totally makes sense, I'll be happy to revisit it when the timing works better.

@socket-security

Copy link
Copy Markdown

New, updated, and removed dependencies detected. Learn more about Socket for GitHub ↗︎

Package New capabilities Transitives Size Publisher

🚮 Removed packages: npm/react@18.2.0, npm/sharp@0.33.5

View full report↗︎

@fveracoechea fveracoechea Feb 9, 2025

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd like to mention, this file is just for benchmarking purposes, basically making sure these changes are actually faster, could be removed before merging.

@rvion

rvion commented Nov 13, 2025

Copy link
Copy Markdown

cva is really really slow in large codebase...
where I work, cva self-time (+ clsx) are > than 50% of total frontend execution in some pages... it's just absurd.
so many object spreads, so many un-necessary array allocations , etc. really hope this gets fixed some day.

(kudos for making this library nonetheless ❤️ 🙌 )

image

@rvion

rvion commented Nov 13, 2025

Copy link
Copy Markdown

@fveracoechea are you using your own fork ? is it published somewhere ? up-to-date ?

@fveracoechea

Copy link
Copy Markdown
Author

@fveracoechea are you using your own fork ? is it published somewhere ? up-to-date ?

Hey @rvion, not at the moment, although you made a good point, the current implementation relies too much on object spread and array allocations that can add up pretty quickly on react apps.

My main use case for CVA is when I use shadcn components which already implement CVA, I guess that's why I have not thought about doing my own fork, also I've been able to mitigate some of these problems by using react-compiler.

@rafaell-lycan

rafaell-lycan commented Jan 30, 2026

Copy link
Copy Markdown

Hopefully on a newer version (v1.0) will land with these perf changes applied.

I really like the project and just like you @fveracoechea I'm also using shadcn/ui as the main design system base, but noticed on the Profiler (render) that it takes a small hit on screens that are too complex.

Anywho, thanks for this amazing project @joe-bell!

Comment thread packages/cva/src/index.ts
Comment on lines +184 to +198
function mergeDefaultsAndProps<
V extends CVAVariantShape,
P extends Record<PropertyKey, unknown>,
D extends CVAVariantSchema<V>,
>(props: P = {} as P, defaults: D = {} as D) {
const result: Record<PropertyKey, unknown> = { ...defaults };

for (const key in props) {
if (!isKeyOf(props, key)) continue;
const value = props[key];
if (typeof value !== "undefined") result[key] = value;
}

return result as Record<keyof V, NonNullable<ClassValue>>;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

for...in already gives you the object’s enumerable keys, I might be missing something but I believe you can safely skip the if (!isKeyOf(props, key)) continue; statement

I ran a benchmark without it and this function ends up 11.8% Faster

┌───┬────────────────────────┬─────────────┬────────┬──────────┐
│   │ name                   │ ops/sec     │ margin │ samples  │
├───┼────────────────────────┼─────────────┼────────┼──────────┤
│ 0 │ original               │ 13375270.00 │ 0.46%  │ 13375270 │
│ 1 │ withoutIsKeyOf         │ 15148843.49 │ 0.54%  │ 15148845 │
└───┴────────────────────────┴─────────────┴────────┴──────────┘

@fveracoechea fveracoechea Mar 18, 2026

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interesting observation @thadeucity I've always checked keys when doing for-in loops, it has something to do with the object prototype, here is what I found on google

In this case, it looks like Object.keys() could be used and still get better performance from the loop, it'd be interesting to see if this is faster:

 for (const key in Object.keys(props)) {
    const value = props[key];
    if (typeof value !== "undefined") result[key] = value;
  }

@thadeucity thadeucity Mar 18, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the clarification @fveracoechea , that makes sense!

I honestly did not know that detail about for...in iterating over enumerable properties from the prototype chain as well.

I’ll update the implementation and benchmark the overall impact a bit more broadly across the project, instead of looking at this function in isolation.

I also have another idea, in this very specific scenario, manually cloning the object might shave off a few extra nanoseconds.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@fveracoechea
I just tested both scenarios, but both are slower than the solution you proposed.

The funny part is that, when tested in isolation, both the manual clone and the for (const key in Object.keys(props)) were slightly faster. However, when tested within the full library, both ended up being slightly slower.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

image

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants