Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
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 <jordan@cryptica.me>
- Loading branch information