-
Notifications
You must be signed in to change notification settings - Fork 4.5k
Ensure @tailwindcss/upgrade
runs on Tailwind CSS v4 projects and is idempotent
#17717
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
Merged
Conversation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Once we are in v4, it could be that you have `@config` still, and maybe we are able to migrate this stylesheet. But if no `@config` exists, then there is no need to migrate the JS configuration file This is just for printing the header.
This swaps the "migrate stylesheets" and "migrate source files" steps. This will make things easier _if_ we don't even need to migrate JS config files and can rely on the CSS file itself. This will happen if you run the upgrade tool in Tailwind CSS v4 projects.
We will improve this in a future commit so we can still migrate `@apply` for example in Tailwind CSS v4 -> v4 migrations.
We will be using the `Scanner` from `@tailwindcss/oxide` instead. Since we already migrated the JS config files and linked the potential diverging `content` array to `@source` directives it means we can rely on the scanner.
The `DesignSystem` was already nullable (or well, optional) but that wasn't well reflected in the types. This would otherwise break this test: - `tailwindcss/integrations/upgrade/index.test.ts` - `migrate utility files imported by multiple roots`
- `bg-{left,right}-{top,bottom}` in favor of `bg-{top,bottom}-{left,right}` - `object-{left,right}-{top,bottom}` in favor of `object-{top,bottom}-{left,right}`
This will replace variants such as `[@media(pointer:fine)]:flex` to `pointer-fine:flex`
When printing a candidate, we do some optimizations already, such as: - `bg-[var(--foo)]` -> `bg-(--foo)` - `bg-[rgb(0,_0,_0)]` -> `bg-[rgb(0,0,0)]` Consistency in your project will reduce the file size. We parse and reprint the candidate if nothing changed during migrations because we don't have dedicated migrations for them. So (a lot) of these classes weren't fully updated to the v4 flavor of the classes. Luckily re-parsing the candidate is fast because we are re-using the design system which means that we have a cached version of the candidate.
And actually applies changes where it can. It will also ignore unsafe migrations such as variant order and `rounded` -> `rounded-sm`.
0c7682d
to
d20902f
Compare
packages/@tailwindcss-upgrade/src/codemods/template/migrate-modernize-arbitrary-values.ts
Outdated
Show resolved
Hide resolved
packages/@tailwindcss-upgrade/src/codemods/template/migrate-simple-legacy-classes.ts
Show resolved
Hide resolved
thecrypticace
approved these changes
Apr 22, 2025
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
This PR ensures that the
@tailwindcss/upgrade
tool works on existing Tailwind CSS v4 projects. This PR also ensures that the upgrade tool is idempotent, meaning that it can be run multiple times and it should result in the same output.One awesome feature this unlocks is that you can run the upgrade tool on your codebase at any time and upgrade classes if you still have some legacy syntaxes, such as
bg-[var(--my-color)]
, in your muscle memory.One small note: If something changed in the first run, re-running will not work immediately because your git repository will not be clean and the upgrade tool requires your git repo to be clean. But once you verified and committed your changes, the upgrade tool will be idempotent.
Idempotency is guaranteed by ensuring that some migrations are skipped by checking what version of Tailwind CSS you are on before the version is upgraded.
For the Tailwind CSS version: We will resolve
tailwindcss
itself to know the actual version that is installed (the one resolved fromnode_modules
). Not the one available in your package.json. Yourpackage.json
could be out of sync if you reverted changes but didn't runnpm install
yet.Back to Idempotency:
For example, we have migrations where we change the variant order of stacked variants. If we would run these migrations every time you run the upgrade tool then we would be flip-flopping the order every run.
See: https://tailwindcss.com/docs/upgrade-guide#variant-stacking-order
Another example is where we rename some utilities. For example, we rename:
shadow
shadow-sm
shadow-sm
shadow-xs
Notice how we have
shadow-sm
in both thebefore
andafter
column.If we would run the upgrade tool again, then we would eventually migrate your original
shadow
toshadow-sm
(first run) and then toshadow-xs
(second run). Which would result in the wrong shadow.See: https://tailwindcss.com/docs/upgrade-guide#renamed-utilities
The order of upgrade steps changed a bit as well to make the internals are easier to work with and reason about.
This is done so that step 5 and 6 will always operate on a Tailwind CSS v4 project and we don't need to check the version number again. This is also necessary because your CSS file will now very likely contain
@import "tailwindcss";
which doesn't exist in Tailwind CSS v3.This also means that we can rely on the same internals that Tailwind CSS actually uses for locating the source files. We will use
@tailwindcss/oxide
's scanner to find the source files (and it also keeps your custom@source
directives into account).This PR also introduces a few actual migrations related to recent features and changes we shipped.
We migrate deprecated classes to their new names:
bg-left-top
bg-top-left
bg-left-bottom
bg-bottom-left
bg-right-top
bg-top-right
bg-right-bottom
bg-bottom-right
object-left-top
object-top-left
object-left-bottom
object-bottom-left
object-right-top
object-top-right
object-right-bottom
object-bottom-right
Introduced in:
bg-{top,bottom}-{left,right}
utilities #17378object-{top,bottom}-{left,right}
utilities #17437We migrate simple arbitrary variants to their dedicated variant:
[&:user-valid]:flex
user-valid:flex
[&:user-invalid]:flex
user-invalid:flex
Introduced in:
:user-valid
&:user-invalid
variants #12370We migrate
@media
variants to their dedicated variant:[@media_print]:flex
print:flex
[@media(prefers-reduced-motion:no-preference)]:flex
motion-safe:flex
[@media(prefers-reduced-motion:reduce)]:flex
motion-reduce:flex
[@media(prefers-contrast:more)]:flex
contrast-more:flex
[@media(prefers-contrast:less)]:flex
contrast-less:flex
[@media(orientation:portrait)]:flex
portrait:flex
[@media(orientation:landscape)]:flex
landscape:flex
[@media(forced-colors:active)]:flex
forced-colors:flex
[@media(inverted-colors:inverted)]:flex
inverted-colors:flex
[@media(pointer:none)]:flex
pointer-none:flex
[@media(pointer:coarse)]:flex
pointer-coarse:flex
[@media(pointer:fine)]:flex
pointer-fine:flex
[@media(any-pointer:none)]:flex
any-pointer-none:flex
[@media(any-pointer:coarse)]:flex
any-pointer-coarse:flex
[@media(any-pointer:fine)]:flex
any-pointer-fine:flex
[@media(scripting:none)]:flex
noscript:flex
The new variants related to
inverted-colors
,pointer
,any-pointer
andscripting
were introduced in:inverted-colors
variant #11693pointer-*
variants #16946scripting
variants #11929This also applies to the
not
case, e.g.:[@media_not_print]:flex
not-print:flex
[@media_not(prefers-reduced-motion:no-preference)]:flex
not-motion-safe:flex
[@media_not(prefers-reduced-motion:reduce)]:flex
not-motion-reduce:flex
[@media_not(prefers-contrast:more)]:flex
not-contrast-more:flex
[@media_not(prefers-contrast:less)]:flex
not-contrast-less:flex
[@media_not(orientation:portrait)]:flex
not-portrait:flex
[@media_not(orientation:landscape)]:flex
not-landscape:flex
[@media_not(forced-colors:active)]:flex
not-forced-colors:flex
[@media_not(inverted-colors:inverted)]:flex
not-inverted-colors:flex
[@media_not(pointer:none)]:flex
not-pointer-none:flex
[@media_not(pointer:coarse)]:flex
not-pointer-coarse:flex
[@media_not(pointer:fine)]:flex
not-pointer-fine:flex
[@media_not(any-pointer:none)]:flex
not-any-pointer-none:flex
[@media_not(any-pointer:coarse)]:flex
not-any-pointer-coarse:flex
[@media_not(any-pointer:fine)]:flex
not-any-pointer-fine:flex
[@media_not(scripting:none)]:flex
not-noscript:flex
For each candidate, we run a set of upgrade migrations. If at the end of the migrations the original candidate is still the same as the new candidate, then we will parse & print the candidate one more time to pretty print into consistent classes. Luckily parsing is cached so there is no real downside overhead.
Consistency (especially with arbitrary variants and values) will reduce your CSS file because there will be fewer "versions" of your class.
Concretely, the pretty printing will apply changes such as:
bg-[var(--my-color)]
bg-(--my-color)
bg-[rgb(0,_0,_0)]
bg-[rgb(0,0,0)]
Another big important reason for this change is that these classes on their own
would have been migrated if another migration was relevant for this candidate.
This means that there are were some inconsistencies. E.g.:
!bg-[var(--my-color)]
bg-(--my-color)!
!
is in the wrong spotbg-[var(--my-color)]
bg-[var(--my-color)]
As you can see, the way the
--my-color
variable is used, is different. Thischanges will make sure it will now always be consistent:
!bg-[var(--my-color)]
bg-(--my-color)!
bg-[var(--my-color)]
bg-(--my-color)
Yay!
Of course, if you don't want these more cosmetic changes, you can always ignore the upgrade and revert these changes and only commit the changes you want.
Test plan
version.isMajor
call to ensure we run the individual migration tests correctly.[ci-all]