Skip to content

Commit d14249d

Browse files
Add CSS codemods for migrating @layer utilities (#14455)
This PR adds CSS codemods for migrating existing `@layer utilities` to `@utility` directives. This PR has the ability to migrate the following cases: --- The most basic case is when you want to migrate a simple class to a utility directive. Input: ```css @layer utilities { .foo { color: red; } .bar { color: blue; } } ``` Output: ```css @Utility foo { color: red; } @Utility bar { color: blue; } ``` You'll notice that the class `foo` will be used as the utility name, the declarations (and the rest of the body of the rule) will become the body of the `@utility` definition. --- In v3, every class in a selector will become a utility. To correctly migrate this to `@utility` directives, we have to register each class in the selector and generate `n` utilities. We can use nesting syntax, and replace the current class with `&` to ensure that the final result behaves the same. Input: ```css @layer utilities { .foo .bar .baz { color: red; } } ``` Output: ```css @Utility foo { & .bar .baz { color: red; } } @Utility bar { .foo & .baz { color: red; } } @Utility .baz { .foo .bar & { color: red; } } ``` In this case, it could be that you know that some of them will never be used as a utility (e.g.: `hover:bar`), but then you can safely remove them. --- Even classes inside of `:has(…)` will become a utility. The only exception to the rule is that we don't do it for `:not(…)`. Input: ```css @layer utilities { .foo .bar:not(.qux):has(.baz) { display: none; } } ``` Output: ```css @Utility foo { & .bar:not(.qux):has(.baz) { display: none; } } @Utility bar { .foo &:not(.qux):has(.baz) { display: none; } } @Utility baz { .foo .bar:not(.qux):has(&) { display: none; } } ``` Notice that there is no `@utility qux` because it was used inside of `:not(…)`. --- When classes are nested inside at-rules, then these classes will also become utilities. However, the `@utility <name>` will be at the top and the at-rules will live inside of it. If there are multiple classes inside a shared at-rule, then the at-rule will be duplicated for each class. Let's look at an example to make it more clear: Input: ```css @layer utilities { @media (min-width: 640px) { .foo { color: red; } .bar { color: blue; } @media (min-width: 1024px) { .baz { color: green; } @media (min-width: 1280px) { .qux { color: yellow; } } } } } ``` Output: ```css @Utility foo { @media (min-width: 640px) { color: red; } } @Utility bar { @media (min-width: 640px) { color: blue; } } @Utility baz { @media (min-width: 640px) { @media (min-width: 1024px) { color: green; } } } @Utility qux { @media (min-width: 640px) { @media (min-width: 1024px) { @media (min-width: 1280px) { color: yellow; } } } } ``` --- When classes result in multiple `@utility` directives with the same name, then the definitions will be merged together. Input: ```css @layer utilities { .no-scrollbar::-webkit-scrollbar { display: none; } .no-scrollbar { -ms-overflow-style: none; scrollbar-width: none; } } ``` Intermediate representation: ```css @Utility no-scrollbar { &::-webkit-scrollbar { display: none; } } @Utility no-scrollbar { -ms-overflow-style: none; scrollbar-width: none; } ``` Output: ```css @Utility no-scrollbar { &::-webkit-scrollbar { display: none; } -ms-overflow-style: none; scrollbar-width: none } ``` --------- Co-authored-by: Jordan Pittman <[email protected]>
1 parent abde4c9 commit d14249d

File tree

8 files changed

+1234
-3
lines changed

8 files changed

+1234
-3
lines changed

CHANGELOG.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1111

1212
- Add support for `aria`, `supports`, and `data` variants defined in JS config files ([#14407](https://github.com/tailwindlabs/tailwindcss/pull/14407))
1313
- Add `@tailwindcss/upgrade` tooling ([#14434](https://github.com/tailwindlabs/tailwindcss/pull/14434))
14-
- Add CSS codemods for migrating `@tailwind` directives ([#14411](https://github.com/tailwindlabs/tailwindcss/pull/14411))
1514
- Support `screens` in JS config files ([#14415](https://github.com/tailwindlabs/tailwindcss/pull/14415))
1615
- Add `bg-radial-*` and `bg-conic-*` utilities for radial and conic gradients ([#14467](https://github.com/tailwindlabs/tailwindcss/pull/14467))
1716
- Add new `shadow-initial` and `inset-shadow-initial` utilities for resetting shadow colors ([#14468](https://github.com/tailwindlabs/tailwindcss/pull/14468))
1817
- Add `field-sizing-*` utilities ([#14469](https://github.com/tailwindlabs/tailwindcss/pull/14469))
1918
- Include gradient color properties in color transitions ([#14489](https://github.com/tailwindlabs/tailwindcss/pull/14489))
19+
- _Experimental_: Add CSS codemods for migrating `@tailwind` directives ([#14411](https://github.com/tailwindlabs/tailwindcss/pull/14411))
20+
- _Experimental_: Add CSS codemods for migrating `@layer utilities` and `@layer components` ([#14455](https://github.com/tailwindlabs/tailwindcss/pull/14455))
2021

2122
### Fixed
2223

integrations/cli/upgrade.test.ts

Lines changed: 57 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ test(
5252
)
5353

5454
test(
55-
'migrate @tailwind directives',
55+
'migrate `@tailwind` directives',
5656
{
5757
fs: {
5858
'package.json': json`
@@ -76,3 +76,59 @@ test(
7676
await fs.expectFileToContain('src/index.css', css` @import 'tailwindcss'; `)
7777
},
7878
)
79+
80+
test(
81+
'migrate `@layer utilities` and `@layer components`',
82+
{
83+
fs: {
84+
'package.json': json`
85+
{
86+
"dependencies": {
87+
"tailwindcss": "workspace:^",
88+
"@tailwindcss/upgrade": "workspace:^"
89+
}
90+
}
91+
`,
92+
'src/index.css': css`
93+
@import 'tailwindcss';
94+
95+
@layer components {
96+
.btn {
97+
@apply rounded-md px-2 py-1 bg-blue-500 text-white;
98+
}
99+
}
100+
101+
@layer utilities {
102+
.no-scrollbar::-webkit-scrollbar {
103+
display: none;
104+
}
105+
106+
.no-scrollbar {
107+
-ms-overflow-style: none;
108+
scrollbar-width: none;
109+
}
110+
}
111+
`,
112+
},
113+
},
114+
async ({ fs, exec }) => {
115+
await exec('npx @tailwindcss/upgrade')
116+
117+
await fs.expectFileToContain(
118+
'src/index.css',
119+
css`
120+
@utility btn {
121+
@apply rounded-md px-2 py-1 bg-blue-500 text-white;
122+
}
123+
124+
@utility no-scrollbar {
125+
&::-webkit-scrollbar {
126+
display: none;
127+
}
128+
-ms-overflow-style: none;
129+
scrollbar-width: none;
130+
}
131+
`,
132+
)
133+
},
134+
)

packages/@tailwindcss-upgrade/package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@
3333
"picocolors": "^1.0.1",
3434
"postcss": "^8.4.41",
3535
"postcss-import": "^16.1.0",
36+
"postcss-selector-parser": "^6.1.2",
37+
"prettier": "^3.3.3",
3638
"tailwindcss": "workspace:^"
3739
},
3840
"devDependencies": {

0 commit comments

Comments
 (0)