Skip to content

Commit a42251c

Browse files
Improve performance of upgrade tool (#18068)
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: ```diff - 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: ```diff - Before: ~5m 39s + After: ~9s ``` Much better! --------- Co-authored-by: Jordan Pittman <jordan@cryptica.me>
1 parent 71fb9cd commit a42251c

File tree

2 files changed

+45
-10
lines changed

2 files changed

+45
-10
lines changed

CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
99

1010
### Fixed
1111

12-
- Upgrade: Do not migrate declarations that look like candidates in `<style>` blocks ([#18057](https://github.com/tailwindlabs/tailwindcss/pull/18057))
12+
- Upgrade: Do not migrate declarations that look like candidates in `<style>` blocks ([#18057](https://github.com/tailwindlabs/tailwindcss/pull/18057), [18068](https://github.com/tailwindlabs/tailwindcss/pull/18068))
1313
- Upgrade: Improve `pnpm` workspaces support ([#18065](https://github.com/tailwindlabs/tailwindcss/pull/18065))
1414

1515
## [4.1.7] - 2025-05-15

packages/@tailwindcss-upgrade/src/codemods/template/is-safe-migration.ts

Lines changed: 44 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { parseCandidate } from '../../../../tailwindcss/src/candidate'
22
import type { DesignSystem } from '../../../../tailwindcss/src/design-system'
3+
import { DefaultMap } from '../../../../tailwindcss/src/utils/default-map'
34
import * as version from '../../utils/version'
45

56
const QUOTES = ['"', "'", '`']
@@ -44,16 +45,23 @@ export function isSafeMigration(
4445
// Whitespace before the candidate
4546
location.contents[location.start - 1]?.match(/\s/) &&
4647
// A colon followed by whitespace after the candidate
47-
location.contents.slice(location.end, location.end + 2)?.match(/^:\s/) &&
48-
// A `<style` block is present before the candidate
49-
location.contents.slice(0, location.start).includes('<style') &&
50-
// `</style>` is present after the candidate
51-
location.contents.slice(location.end).includes('</style>')
48+
location.contents.slice(location.end, location.end + 2)?.match(/^:\s/)
5249
) {
53-
return false
50+
// Compute all `<style>` ranges once and cache it for the current files
51+
let ranges = styleBlockRanges.get(location.contents)
52+
53+
for (let i = 0; i < ranges.length; i += 2) {
54+
let start = ranges[i]
55+
let end = ranges[i + 1]
56+
57+
// Check if the candidate is inside a `<style>` block
58+
if (location.start >= start && location.end <= end) {
59+
return false
60+
}
61+
}
5462
}
5563

56-
let [candidate] = Array.from(parseCandidate(rawCandidate, designSystem))
64+
let [candidate] = parseCandidate(rawCandidate, designSystem)
5765

5866
// If we can't parse the candidate, then it's not a candidate at all. However,
5967
// we could be dealing with legacy classes like `tw__flex` in Tailwind CSS v3
@@ -62,10 +70,10 @@ export function isSafeMigration(
6270
// So let's only skip if we couldn't parse and we are not in Tailwind CSS v3.
6371
//
6472
if (!candidate && version.isGreaterThan(3)) {
65-
return true
73+
return false
6674
}
6775

68-
// Parsed a candidate succesfully, verify if it's a valid candidate
76+
// Parsed a candidate successfully, verify if it's a valid candidate
6977
else if (candidate) {
7078
// When we have variants, we can assume that the candidate is safe to migrate
7179
// because that requires colons.
@@ -168,3 +176,30 @@ export function isSafeMigration(
168176

169177
return true
170178
}
179+
180+
// Assumptions:
181+
// - All `<style` tags appear before the next `</style>` tag
182+
// - All `<style` tags are closed with `</style>`
183+
// - No nested `<style>` tags
184+
const styleBlockRanges = new DefaultMap((source: string) => {
185+
let ranges: number[] = []
186+
let offset = 0
187+
188+
while (true) {
189+
let startTag = source.indexOf('<style', offset)
190+
if (startTag === -1) return ranges
191+
192+
offset = startTag + 1
193+
194+
// Ensure the style looks like:
195+
// - `<style>` (closed)
196+
// - `<style …>` (with attributes)
197+
if (!source[startTag + 6].match(/[>\s]/)) continue
198+
199+
let endTag = source.indexOf('</style>', offset)
200+
if (endTag === -1) return ranges
201+
offset = endTag + 1
202+
203+
ranges.push(startTag, endTag)
204+
}
205+
})

0 commit comments

Comments
 (0)