New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Performance improvements #2171
Performance improvements #2171
Conversation
857cd9a
to
051419f
Compare
Yeah - especially the new code path for @apply in Vue components can make use of some nice caching as it’s always the same. |
I'm not totally sure how to cache it but would be good if we could. Vue runs Tailwind from scratch for each component so it's a whole new instance of everything. If we're lucky maybe we take advantage of modules acting like singletons but we can't really rely on scoped runtime level caching, only global. Have to be very careful to avoid incorrect cache hits for example when the config object has changed we need to expire the cache, so probably need to derive a cache key from the config somehow. |
8c5ef92
to
0591eb0
Compare
0591eb0
to
9ef8359
Compare
There is no need in re-compiling tailwind or building expensive lookupt tables when it turns out that we don't even need it. We have a small overhead by walking the tree to check if `@apply` exists. However this outweighs the slowness of re-generating tailwind + expensive lookup tables.
We were re-creating the classNameParser inside the loop. Since that code is all pretty pure we can hoist it so that we don't have to recreate that parser all the time.
Currently we will walk the tree for every single rule to see if an `@apply` exists somewhere in that tree. However we don't use the `containsApply` anymore so this is a quick win!
We create a big lookup table so that we can lookup the nodes by its utilityName. This is used inside the recursive `@apply` code. This big lookup table will clone every single rule and put it in, however we don't need to clone everything! We are only interested in the rules that have been actually applied. This way we make the cloning of the rule lazy and only when we use this exact rule. There is an additional performace "issue" though: When we read the same rule multiple times, it will clone every time you read from that object. We could add additional memoization stuff, but so far it doesn't seem to be the bottleneck. Therefore I've added a perf todo just to leave a mark when this becomes the bottleneck.
We alreayd know that we have an `@apply` otherwise we would not have called that function in the first place. Moving to a `do {} while ()` allows us to skip 1 call to `hasAtRule(css, 'apply')`. Which is nice because that skips a possible full traversal.
We don't require this reversed map since we can already sort by the index on the node directly. Therefore this can be dropped.
No need to re-create the selector parser in the loop for each selector.
We used to clone the full tree and then remove all the children, this was a bit too slow so therefore we will now create a new tree based on the old information.
Naming is hard so I took this name from the React hook 😎 Also use this useMemoy utility to make sure that the extractUtilityNames is cached. There is no need to re-compute the utility names all the time.
No need to re-create the selectorParser in every call.
Otherwise every time we read this value it will be re-cloned
Same idea, but prepend will internally reverse all nodes.
9ef8359
to
33ee646
Compare
Perf
Tailwind is currently not super fast. Especially now with the newly added functionality for the recursive
@apply
rules.This PR is an attempt to optimise some low hanging fruit and some hot paths. Let's see how this goes... 🤞
The results are performed using the following config files inside the
./perf
folder. Relevant files:@apply
's in there and also some@apply
's of responsive utilities).I am testing this locally on a
16" MacBook Pro, 2,3 GHz 8-Core Intel Core i9, 32GB of RAM, AMD Radeon Pro 5500M 4GB
machine.Before:
After (so far):