Skip to content

Improve performance of upgrade tool #18068

New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Open
wants to merge 6 commits into
base: main
Choose a base branch
from

Conversation

RobinMalfait
Copy link
Member

This PR improves the performance of the upgrade tool due to a regression introduced by #18057

Essentially, we had to make sure that we are not in <style>…</style> tags because we don't want to migrate declarations in there such as flex-shrink: 0;

The issue with this approach is that we checked before the candidate if a <style cold be found and if we found an </style> tag after the candidate.

We would basically do this check for every candidate that matches.

Running this on our Tailwind UI codebase, this resulted in a bit of a slowdown:

- Before: ~13s
+  After: ~5m 39s

... quite the difference.

This is because we have a snapshot file that contains ~650k lines of code. Looking for <style> and </style> tags in a file that large is expensive, especially if we do it a lot.

I ran some numbers and that file contains ~1.8 million candidates.

Anyway, this PR fixes that by doing a few things:

  1. We will compute the <style> and </style> tag positions only once per file and cache it. This allows us to re-use this work for every candidate that needs it.
  2. We track the positions, which means that we can simply check if a candidate's location is within any of 2 start and end tags. If so, we skip it.

Running the numbers now gets us to:

- Before: ~5m 39s
+  After: ~9s

Much better!

We already know we are in v4 and up and it didn't compile. All
migrations will fail but will still run, so now we can bail early.
This allows us to compute all the positions once and cache it per file
contents. This is useful because otherwise we have to check the exact
same file over and over and over again, but just for different
positions.

This also just computes the ranges of start and end positions. This will
allow us to simply check if the candidate is between any of 2 pair
points to know if we are in a `<style>…</style>` or not.
There is no need to convert the iterator to an array first. We can just
destructure the first value immediately.
@RobinMalfait RobinMalfait requested a review from a team as a code owner May 16, 2025 23:16
}

let [candidate] = Array.from(parseCandidate(rawCandidate, designSystem))
let [candidate] = parseCandidate(rawCandidate, designSystem)
Copy link
Member Author

Choose a reason for hiding this comment

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

Small improvement, no need to convert to an array first...

@@ -62,10 +70,10 @@ export function isSafeMigration(
// So let's only skip if we couldn't parse and we are not in Tailwind CSS v3.
//
if (!candidate && version.isGreaterThan(3)) {
return true
return false
Copy link
Member Author

Choose a reason for hiding this comment

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

I'm a bit of a dumb dumb here. When we know that a candidate doesn't parse, and we know that we are in v4 and up, then we know that we can't migrate the candidate anyway so we can bail.

That makes it an unsafe migration (which skips all the migrations for this candidate). We were returning true which means that we were migrating the candidate. This still worked because eventually the migrations were no-ops because they all bailed.

But let's not do that work in the first place, yeah?

Copy link
Member Author

Choose a reason for hiding this comment

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

This change alone makes the upgrade tool on Tailwind UI's product folder go from ~12s to ~9s

Copy link
Member Author

Choose a reason for hiding this comment

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

Oh and that's because this branch was hit about 1 041 458 times in that codebase.

Comment on lines +192 to +197
// Ensure the style looks like:
// - `<style>` (closed)
// - `<style …>` (with attributes)
if (!source[startTag + 6].match(/[>\s]/)) return ranges

offset = startTag + 1
Copy link
Contributor

Choose a reason for hiding this comment

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

This will stop scanning as soon as it sees a tag that starts with <style but isn't <style>. e.g. <styleless>. How about this?

Suggested change
// Ensure the style looks like:
// - `<style>` (closed)
// - `<style …>` (with attributes)
if (!source[startTag + 6].match(/[>\s]/)) return ranges
offset = startTag + 1
offset = startTag + 1
// Ensure the style looks like:
// - `<style>` (closed)
// - `<style …>` (with attributes)
if (!source[startTag + 6].match(/[>\s]/)) continue

Copy link
Contributor

@thecrypticace thecrypticace left a comment

Choose a reason for hiding this comment

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

Found a bug in styleBlockRanges

# for free to join this conversation on GitHub. Already have an account? # to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants