31 Commits

Author SHA1 Message Date
Robin Malfait
430836f651
Ensure layer(…) on @import is only removed when @utility is present (#14783)
This PR fixes an issue where `layer(…)` next to imports were removed
where they shouldn't have been removed.

The issue exists if _any_ of the `@import` nodes in a file contains
`@utility`, if that's the case then we removed the `layer(…)` next to
_all_ `@import` nodes.

Before we were checking if the current sheet contained `@utility` or in
any of its children (sub-`@import` nodes).

This fixes that by looping over the `@import` nodes in the current
sheet, and looking for the `@utility` in the associated/imported file.
This way we update each node individually.

Test plan:
---

Added a dedicated integration test to make sure all codemods together
result in the correct result. Input:

96e8908378/integrations/upgrade/index.test.ts (L2076-L2108)

Output:

96e8908378/integrations/upgrade/index.test.ts (L2116-L2126)
2024-10-24 14:33:10 -04:00
Robin Malfait
5a1c2e7480
Only generate Preflight compatibility styles when Preflight is used (#14773)
This PR improves where we inject the border compatibility CSS. Before
this change we injected it if it was necessary in one of these spots:

- Above the first `@layer base` to group it together with existing
`@layer base` at-rules.
- If not present, after the last `@import`, to make sure that we emit
valid CSS because `@import` should be at the top (with a few
exceptions).

However, if you are working with multiple CSS files, then it could be
that we injected the border compatibility CSS multiple times if those
files met one of the above conditions.

To solve this, we now inject the border compatibility CSS with the same
rules as above, but we also have another condition:

The border compatibility CSS is only injected if the file also has a
`@import "tailwindcss";` _or_ `@import "tailwindcss/preflight";` in the
current file.

---

Added integration tests to make sure that we are generating what we
expect in a real environment. Some of the integration tests also use the
old `@tailwind` directives to make sure that the order of migrations is
correct (first migrate to `@import` syntax, then inject the border
compatibility CSS).

---------

Co-authored-by: Adam Wathan <adam.wathan@gmail.com>
2024-10-24 13:19:56 -04:00
Philipp Spiess
a06245b3fe
Upgrade: Rewrite imports of relative files to use relative file paths (#14755)
When we implemented the CSS import resolution system, we found out a
detail about CSS imports in that files without a relative path prefix
would still be relative to the source file. E.g.:

```css
@import 'foo.css';
```

Should first look for the file `foo.css` in the same directory. To make
this cost as cheap as possible, we limited this by a heuristics to only
apply the auto-relative imports for files with a file extension.

Naturally, while testing v4 on more templates, we found that it's common
for people to omit the file extension when loading css file. The above
could also be written as such:

```css
@import 'foo';
```

To improve this, we have two options:

- We either remove the heuristics, making every `@import` more expensive
because we have to check for relative files.
- We upgrade our codemods to rewrite `@import` statements to be
explicitly relative.

Because we really care about performance, we opted to go with the latter
option. This PR adds the codemod and removes the heuristics so we
resolve CSS files similar to how you would resolve JS files.

---------

Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
Co-authored-by: Adam Wathan <adam.wathan@gmail.com>
2024-10-22 16:30:41 -04:00
RobinMalfait
c6572ab929 Add codemod for border style compatibility (#14746)
This PR adds a codemod that ensures that the border styles from Tailwind CSS v3 work as expected once your project is migrated to Tailwind CSS v4.

In Tailwind CSS v3, the default border color is `colors.gray.200` and in Tailwind CSS v4 the default border color is `currentColor`.

Similarly in Tailwind CSS v3, DOM elements such as `input`, `select`, and `textarea` have a border width of `0px`, in Tailwind CSS v4, we don't change the border width of these elements and keep them as `1px`.

If your project happens to already use the same value for the default border color (`currentColor`) as we use in Tailwind CSS v4, then nothing happens. But this is very unlikely, so we will make sure that we honor your `borderColor.DEFAULT` value.

If you didn't change the default values in your `tailwind.config.js`, then we will inject compatibility CSS using the default Tailwind CSS v3 values to ensure the default color and width are applied correctly.
2024-10-22 17:41:50 +00:00
RobinMalfait
02cb52ad38 Add codemod for migrating the @screen directive (#14749)
This PR adds a codemod for migrating the old `@screen` directive from Tailwind
CSS v2 that also worked in Tailwind CSS v3 but wasn't documented anymore.

Internally, this first migrates `@screen md` to `@media screen(md)`, then we rely on the existing migration that migrates the `screen(…)` function.

Input:
```css
@screen md {
  .foo {
    color: red;
  }
}
```

Output (IR):
```css
@media screen(md) {
  .foo {
    color: red;
  }
}
```

Output:
```css
@media theme(--breakpoint-md) {
  .foo {
    color: red;
  }
}
```
2024-10-22 16:09:56 +00:00
RobinMalfait
5bf2efb521 Add codemod for migrating @variants and @responsive directives (#14748)
This PR migrates the `@variants` and `@responsive` directives.

In Tailwind CSS v2, these were used to generate certain variants of responsive variants for the give classes. In Tailwind CSS v3, these still worked but were implemented as a no-op such that these directives don't end up in your final CSS.

In Tailwind CSS v4, these don't exist at all anymore, so we can safely get rid of them by replacing them with their contents.

Input:
```css
@variants hover, focus {
  .foo {
    color: red;
  }
}

@responsive {
  .bar {
    color: blue;
  }
}
```

Output:
```css
.foo {
  color: red;
}

.bar {
  color: blue;
}
```
2024-10-22 16:09:54 +00:00
Philipp Spiess
3da49f9837
Migrate static plugins with options to CSS (#14700)
This PR extends our JS configuration to CSS migration by also allowing `plugins` with options.  

An example of such config would be:

```js
import { type Config } from 'tailwindcss'
import myPlugin from "./myPlugin";

export default {
  plugins: [
    myPlugin({
      class: "tw",
    }),
  ],
} satisfies Config;
```

If the option object contains only values allowed in our CSS API, we can convert this to CSS entirely:

```css
@plugin './myPlugin' {
  class: 'tw';
}
```
2024-10-18 15:16:27 +02:00
Robin Malfait
4a4be27dc1
Migrate theme(…) to var(…) in CSS (#14695)
This PR is a follow up from https://github.com/tailwindlabs/tailwindcss/pull/14664 migrates all the `theme(…)` calls in your CSS to `var(…)` if we can.

In at-rules, like `@media` we can't use `var(…)` so we have to use the modern version of `theme(…)`.

In declarations, we can convert to `var(…)` unless there is a modifier used in the `theme(…)` function, then we can only convert to the new `theme(…)` syntax without dot notation.

Input:
```css
@media theme(spacing.4) {
  .foo {
    background-color: theme(colors.red.900);
    color: theme(colors.red.900 / 75%); /* With spaces around the `/` */
    border-color: theme(colors.red.200/75%); /* Without spaces around the `/` */
  }
}
```

Output:
```css
@media theme(--spacing-4) {
/*     ^^^^^^^^^^^^^^^^^^     Use `theme(…)` since `var(…)` is invalid in this position*/
  .foo {
    background-color: var(--color-red-900); /* Converted to var(…) */
    color: theme(--color-red-900 / 75%); /* Convert to modern theme(…) */
    border-color: theme(--color-red-200 / 75%); /* Convert to modern theme(…) — pretty printed*/
  }
}
```
2024-10-17 11:50:27 +02:00
Philipp Spiess
bf179916bf
Reset default @theme values for non extend JS theme config (#14672)
Imagine the following setup:

```css
/* src/input.css */
@import "tailwindcss";
@config "../tailwind.config.ts";
@theme {
  --color-red-500: #ef4444;
}
```

```ts
/* tailwind.config.ts */
export default {
  theme: {
    colors: {
      red: {
        600: '#dc2626'
      } 
    },
    extend: {
      colors: {
        400: '#f87171'
      }
    }
  }
}
```

Since the theme object in the JS config contains `colors` in the
non-`extends` block, you would expect this to _not pull in all the
default colors imported via `@import "tailwindcss";`_. This, however,
wasn't the case right now since all theme options were purely _additive_
to the CSS.

This PR makes it so that non-`extend` theme keys _overwrite default CSS
theme values_. The emphasis is on `default` here since you still want to
be able to overwrite your options via `@theme {}` in user space.

This now generates the same CSS that our upgrade codemods would also
generate as this would apply the new CSS right after the `@import
"tailwindcss";` rule resulting in:

```css
@import "tailwindcss";
@theme {
  --color-*: initial;
  --color-red-400: #f87171;
  --color-red-600: #dc2626;
}
@theme {
  --color-red-500: #ef4444;
}
```

## Keyframes

This PR also adds a new core API to unset keyframes the same way. We
previously had no option of doing that but while working on the above
codemods we noticed that keyframes should behave the same way:

```css
@import "tailwindcss";
@theme {
  --keyframes-*: initial;
  @keyframes spin {
    to {
      transform: rotate(361deg);
    }
  }
}
```

To do this, the keyframes bookeeping was moved from the main Tailwind
CSS v4 file into the `Theme` class.


_I’m not sure super of the API yet but we would need a way for the
codemods to behave the same as out interop layer here. Option B is that
we don't reset keyframes the same way we reset other theme variables_.
2024-10-16 16:06:09 +02:00
Philipp Spiess
782bc26135
Migrate keyframes from JS to CSS (#14666)
This PR adds support for rewriting JS theme config `keyframes` to CSS as
part of the JS config to CSS migration.

Example:

```ts
import { type Config } from 'tailwindcss'

module.exports = {
  theme: {
    extend: {
      keyframes: {
        'spin-clockwise': {
          '0%': { transform: 'rotate(0deg)' },
          '100%': { transform: 'rotate(360deg)' },
        },
        'spin-counterclockwise': {
          '0%': { transform: 'rotate(0deg)' },
          '100%': { transform: 'rotate(-360deg)' },
        },
      },
      animation: {
        'spin-clockwise': 'spin-clockwise 1s linear infinite',
        'spin-counterclockwise': 'spin-counterclockwise 1s linear infinite',
      },
    },
  },
} satisfies Config
```

Will be printed as:

```css
@theme {
  --animate-spin-clockwise: spin-clockwise 1s linear infinite;
  --animate-spin-counterclockwise: spin-counterclockwise 1s linear infinite;

  @keyframes spin-clockwise {
    0% {
      transform: rotate(0deg);
    }
    100% {
      transform: rotate(360deg);
    }
  }
  @keyframes spin-counterclockwise {
    0% {
      transform: rotate(0deg);
    }
    100% {
      transform: rotate(-360deg);
    }
  }
}
```
2024-10-15 11:21:19 +02:00
Philipp Spiess
468cb5e99e
Detect and migrate static plugin usages (#14648)
This PR builds on top of the new [JS config to CSS
migration](https://github.com/tailwindlabs/tailwindcss/pull/14651) and
extends it to support migrating _static_ plugins.

What are _static_ plugins you might ask? Static plugins are plugins
where we can statically determine that these are coming from a different
file (so there is nothing inside the JS config that creates them). An
example for this is this config file:

```js
import typographyPlugin from '@tailwindcss/typography'
import { type Config } from 'tailwindcss'

export default {
  content: ['./src/**/*.{js,jsx,ts,tsx}'],
  darkMode: 'selector',
  plugins: [typographyPlugin],
} satisfies Config
```

Here, the `plugins` array only has one element and it is a static import
from the `@tailwindcss/typography` module. In this PR we attempt to
parse the config file via Tree-sitter to extract the following
information from this file:

- What are the contents of the `plugins` array
- What are statically imported resources from the file

We then check if _all_ entries in the `plugins` array are either static
resources or _strings_ (something I saw working in some tests but I’m
not sure it still does). We migrate the JS config file to CSS if all
plugins are static and we can migrate them to CSS `@plugin` calls.

## Todo

This will need to be rebased after the updated tests in #14648
2024-10-14 17:45:36 +02:00
Philipp Spiess
4b19de3a45
Address follow-up work for #14639 (#14650)
This PR adds a few more test cases to #14639 and updates the
documentation.

---------

Co-authored-by: Jordan Pittman <jordan@cryptica.me>
2024-10-14 14:33:14 +00:00
Philipp Spiess
0cfb98484b
Add simple JS config migration (#14639)
This PR implements the first version of JS config file migration to CSS.
It is based on the most simple config setups we are using in the
Tailwind UI templates Commit, Primer, Radiant, and Studio.

The example we use in the integration test is a config that looks like
this:

```js
import { type Config } from 'tailwindcss'
import defaultTheme from 'tailwindcss/defaultTheme'

module.exports = {
  darkMode: 'selector',
  content: ['./src/**/*.{html,js}'],
  theme: {
    boxShadow: {
      sm: '0 2px 6px rgb(15 23 42 / 0.08)',
    },
    colors: {
      red: {
        500: '#ef4444',
      },
    },
    fontSize: {
      xs: ['0.75rem', { lineHeight: '1rem' }],
      sm: ['0.875rem', { lineHeight: '1.5rem' }],
      base: ['1rem', { lineHeight: '2rem' }],
    },
    extend: {
      colors: {
        red: {
          600: '#dc2626',
        },
      },
      fontFamily: {
        sans: 'Inter, system-ui, sans-serif',
        display: ['Cabinet Grotesk', ...defaultTheme.fontFamily.sans],
      },
      borderRadius: {
        '4xl': '2rem',
      },
    },
  },
  plugins: [],
} satisfies Config
```

As you can see, this file only has a `darkMode` selector, custom
`content` globs, a `theme` (with some theme keys being overwriting the
default theme and some others extending the defaults). Note that it does
not support `plugins` and/or `presets` yet.

In the case above, we will find the CSS file containing the existing
`@tailwind` directives and are migrating it to the following:

```css
@import 'tailwindcss';

@source './**/*.{html,js}';

@variant dark (&:where(.dark, .dark *));

@theme {
  --box-shadow-*: initial;
  --box-shadow-sm: 0 2px 6px rgb(15 23 42 / 0.08);

  --color-*: initial;
  --color-red-500: #ef4444;

  --font-size-*: initial;
  --font-size-xs: 0.75rem;
  --font-size-xs--line-height: 1rem;
  --font-size-sm: 0.875rem;
  --font-size-sm--line-height: 1.5rem;
  --font-size-base: 1rem;
  --font-size-base--line-height: 2rem;

  --color-red-600: #dc2626;

  --font-family-sans: Inter, system-ui, sans-serif;
  --font-family-display: Cabinet Grotesk, ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";

  --border-radius-4xl: 2rem;
} 
```

This replicates all features of the JS config so we can even delete the
existing JS config in this case.

---------

Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
2024-10-11 15:27:53 +02:00
Robin Malfait
a3812942ac
Use consistent quotes (#14640)
Small improvement, we noticed that some quotes were not consistent with
others. Let's make them consistent!
2024-10-10 14:29:36 +00:00
Robin Malfait
fb1731a2de
Inject @config "..." when a tailwind.config.{js,ts,...} is detected (#14635)
This PR injects a `@config "…"` in the CSS file if a JS based config has
been found.

We will try to inject the `@config` in a sensible place:
1. Above the very first `@theme`
2. If that doesn't work, below the last `@import`
3. If that doesn't work, at the top of the file (as a last resort)
2024-10-10 14:02:42 +00:00
Jordan Pittman
4d1becd2f9
Migrate utilities in CSS files imported into layers (#14617)
When a stylesheet is imported with `@import “…” layer(utilities)` that
means that all classes in that stylesheet and any of its imported
stylesheets become candidates for `@utility` conversion.

Doing this correctly requires us to place `@utility` rules into separate
stylesheets (usually) and replicate the import tree without layers as
`@utility` MUST be root-level. If a file consists of only utilities we
won't create a separate file for it and instead place the `@utility`
rules in the same stylesheet.

Been doing a LOT of pairing with @RobinMalfait on this one but I think
this is finally ready to be looked at

---------

Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
2024-10-10 15:44:04 +02:00
Robin Malfait
3ae22f1f9d
Migrate @media screen(…) (#14603)
This PR adds a codemod that migrates the `@media screen(…)` to the
properly expanded `@media (…)` syntax.

```css
@media screen(md) {
  .foo {
    color: red;
  }
}
```

Will be converted to:
```css
@media (width >= 48rem) {
  .foo {
    color: red;
  }
}
```

If you happen to have custom screens (even complex ones), the screen
will be converted to a custom media query.

```css
@media screen(foo) {
  .foo {
    color: red;
  }
}
```
With a custom `tailwind.config.js` file with a config like this:
```js
module.exports = {
  // …
  theme: {
    screens: {
      foo: { min: '100px', max: '123px' },
    },
  }
}
```

Then the codemod will convert it to:
```css
@media (123px >= width >= 100px) {
  .foo {
    color: red;
  }
}
```
2024-10-10 15:06:53 +02:00
Philipp Spiess
7be5346e2e
Ensure upgrade tool has access to a JS config (#14597)
In order to properly migrate your Tailwind CSS v3 project to v4, we need
access to the JavaScript configuration object. This was previously only
required for template migrations, but in this PR we're making it so that
this is also a prerequisite of the CSS migrations. This is because some
migrations, like `@apply`, also need to convert candidates that to the
v4 syntax and we need the full config in order to properly validate
them.

In addition to requiring a JS config, we also now attempt to
automatically find the right configuration file inside the current
working directory. This is now matching the behavior of the Tailwind CSS
v3 CLI where it will find the config automatically if it's in the
current directory and called `tailwind.conf.js`.

---------

Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
2024-10-07 18:02:28 +02:00
Robin Malfait
e3764ac843
Ensure CSS before a layer stays unlayered (#14596)
This PR fixes an issue where CSS that existed before a layer:

```css
.foo {
  color: red;
}

@layer components {
  .bar {
    color: blue;
  }
}
```

Was turned into an `@layer` without a name:
```css
@layer {
  .foo {
    color: red;
  }
}

@utility bar {
  color: blue;
}
```

But in this case, it should stay as-is:
```css
.foo {
  color: red;
}

@utility bar {
  color: blue;
}
```
2024-10-04 12:47:05 +02:00
Philipp Spiess
2e87288526
Migrate at-apply utilites with template migrations (#14574)
This PR extracts all _candidate migrations_ from the existing _template
migrations_ and reuses these in the `@apply` CSS migration. Seems like
this _JustWorks_.

---------

Co-authored-by: Adam Wathan <adam.wathan@gmail.com>
2024-10-03 09:35:28 -04:00
Philipp Spiess
65240c9240
Template migrations: Migrate v3 prefixes to v4 (#14557)
This PR adds a new migration that can migrate Tailwind CSS v3 style
prefixes into Tailwind CSS v4.

The migration is split into three separate pieces of work:

1. Firstly, we need to read the full JavaScript config to get the _old_
prefix option. This is necessary because in v4, we will not allow things
like custom-separators for the prefix. From this option we will then try
and compute a new prefix (in 90% of the cases this is going to just
remove the trailing `-` but it can also work in more complex cases).
2. Then we migrate all Candidates. The important thing here is that we
need to operate on the raw candidate string because by relying on
`parseCandidate` (which we do for all other migrations) would not work,
as the candidates are not valid in v4 syntax. More on that in a bit.
3. Lastly we also make sure to update the CSS config to include the new
prefix. This is done by prepending the prefix option like so:
    
    ```css
    @import "tailwindcss" prefix(tw);
    ```

### Migrating candidates

The main difference between v3 prefixes and v4 prefixes is that in v3,
the prefix was _part of the utility_ where as in v4 it is _always in
front of the CSS class.

So, for example, this candidate in v3: 

```
hover:-tw-mr-4
```

Would be converted to the following in v4:

```
tw:hover:-mr-4
```

Since the first example _won't parse as a valid Candidate in v4, as the
`tw-mr` utility does not exist, we have to operate on the raw candidate
string first. To do this I created a fork of the `parseCandidate`
function _without any validation of utilities or variants_. This is used
to identify part of the candidate that is the `base` and then ensuring
the `base` starts with the old prefix. We then remove this to create an
"unprefixed" candidate that we validate against a version of the
DesignSystem _with no prefixes configured_. If the variant is valid this
way, we can then print it again with the `DesignSystem` that has the new
prefix to get the migrated version.

Since we set up the `DesignSystem` to include the new prefix, we can
also be certain that migrations that happen afterwards would still
disqualify candidates that aren't valid according to the new prefix
policy. This does mean we need to have the prefix fixup be the first
step in our pipeline.

One interesting bit is that in v3, arbitrary properties did not require
prefixes where as in v4 they do. So the following candidate:

```
[color:red]
```

Will be converted to:

```
tw:[color:red]
```
2024-10-01 18:04:08 +02:00
Robin Malfait
4f8ca556cf
CSS codemod: inject @import in a more expected location (#14536)
This PR inserts the `@import` in a more sensible location when running
codemods.

The idea is that we replace `@tailwind base; @tailwind components;
@tailwind utilities;` with the much simple `@import "tailwindcss";`. We
did this by adding the `@import` to the top of the file.

While this is correct, this means that the diff might not be as clear.
For example, if you have a situation where you have a license comment:
```css
/**! My license comment */
@tailwind base;
@tailwind components;
@tailwind utilities;
```

This resulted in:
```css
@import "tailwindcss";
/**! My license comment */
```

While it is not wrong, it feels weird that this behaves like this. In
this commit we make sure that it is injected in-place (the first
`@tailwind` at-rule we find) and fixup the position if we can't inject
it in-place.

The above example results in this:
```css
/**! My license comment */
@import "tailwindcss";
```

However, there are scenario's where you can't replace the `@tailwind`
directives directly. E.g.:
```css
/**! My license comment */
html {
  color: red;
}
@tailwind base;
@tailwind components;
@tailwind utilities;
```

If we replace the `@tailwind` directives in-place, it would look like
this:
```css
/**! My license comment */
html {
  color: red;
}
@import "tailwindcss";
```

But this is invalid CSS, because you can't have CSS above an `@import`
at-rule. There are some exceptions like:
- `@charset`
- `@import`
- `@layer foo, bar;` (just the order, without a body)
- comments

In this scenario, we inject the import in the nearest place where it is
allowed to. In this case:

```css
/**! My license comment */
@import "tailwindcss";
@layer base {
  html {
     color: red;
  }
}
```

Additionally, we will wrap the existing CSS in an `@layer` of the first
Tailwind directive we saw. In this case an `@layer base`. This ensures
that utilities still win from the default styles.

Also note that the (license) comment is allowed to exist before the
`@import`, therefore we do not put the `@import` above it. This also
means that the diff doesn't touch the license header at all, which makes
the diffs cleaner and easier to reason about.

---------

Co-authored-by: Philipp Spiess <hello@philippspiess.com>
2024-09-30 13:32:30 +00:00
Robin Malfait
8bbdb57457
CSS codemod: ensure we don't lose selectors (#14518)
This PR fixes an issue where a CSS rule with a selector that contains
multiple selectors lost everything but the last selector.

While testing the `npx @tailwindcss/upgrade` codemods on real world
projects, I noticed that we lost one of the selectors in the
`docker/docs` repository.

```diff
diff --git a/assets/css/toc.css b/assets/css/toc.css
index 91ff92d7cd..3b2432e913 100644
--- a/assets/css/toc.css
+++ b/assets/css/toc.css
@@ -2,7 +2,7 @@
   #TableOfContents {
     .toc a {
       @apply block max-w-full truncate py-1 pl-2 hover:font-medium hover:no-underline;
-      &[aria-current="true"],
+      
       &:hover {
         @apply border-l-2 border-l-gray-light bg-gradient-to-r from-gray-light-100 font-medium text-black dark:border-l-gray-dark dark:from-gray-dark-200 dark:text-white;
       }
```

This PR fixes the issue by not overriding the `node.selector` internally
with the last selector we handled. Instead, we let the selector parser
handle it entirely.
2024-09-25 18:39:00 +02:00
Robin Malfait
ceae1dbdfb
CSS codemod: do not wrap comment nodes in @layer (#14517)
When CSS exists between two tailwind directives, then the CSS will be
wrapped in an `@layer` to ensure it all ends up in the correct location
in the final CSS file.

However, if the only thing between two tailwind directives is a comment,
then the comment will also be wrapped in an `@layer` directive.

E.g.:
```css
@tailwind base;
/* This is a comment */
@tailwind components;
/* This is another comment */
@tailwind utilities;
```

Results in:
```css
@import "tailwindcss";
@layer base {
  /* This is a comment */
}
@layer components {
  /* This is another comment */
}
```

In this case, the layers don't matter, so this can be simplified to:
```css
@import "tailwindcss";
/* This is a comment */
/* This is another comment */
```
2024-09-25 12:58:05 +00:00
Robin Malfait
e770c0da30
CSS codemod: Migrate @import "tailwindcss/tailwind.css" to @import "tailwindcss" (#14514)
This PR adds an additional step to ensure we migrate:
```css
@import "tailwindcss/tailwind.css";
```

To:
```css
@import "tailwindcss";
```
2024-09-25 14:48:22 +02:00
Robin Malfait
723f5db38b
CSS codemod: Fix incorrect empty layer() at the end of @import at-rules (#14513)
This PR fixes an issue where some `@import` at-rules had an empty
`layer()` attached at the end of the `@import` string.

We should only add that if a Tailwind directive or Tailwind import such
as `@tailwind base` or `@import "tailwindcss/base"` preceded the current
`@import` at-rule. If there was no Tailwind directive, the `lastLayer`
will be empty and we don't need to attach it to the `@import` string.
2024-09-25 13:48:14 +02:00
Robin Malfait
951f6448fe
Improve missing layer codemod (#14512)
This PR improves the missing layers codemod where everything after the
last Tailwind directive can stay as-is without wrapping it in a `@layer`
directive.

The `@layer` at-rules are only important for CSS that exists between
Tailwind directives.

E.g.:
```css
@tailwind base;

html {}

@tailwind components;

.btn {}

@tailwind utilities;

.foo {}

.bar {}
```

Was transformed into:
```css
@import "tailwindcss";

@layer base {
  html {}
}

@layer components {
  .btn {}
}

@layer utilities {
  .foo {}
  
  .bar {}
}
```

But the last `@layer utilities` is already in the correct spot, so we
can simplify this to just this instead:
```css
@import "tailwindcss";

@layer base {
  html {}
}

@layer components {
  .btn {}
}

.foo {}

.bar {}
```
2024-09-25 08:42:19 +00:00
Robin Malfait
d869442a54
Add CSS codemod for missing @layer (#14504)
This PR adds a codemod that ensures that some parts of your stylesheet
are wrapped in an `@layer`.

This is a follow-up PR of #14411, in that PR we migrate `@tailwind`
directives to imports.

As a quick summary, that will turn this:
```css
@tailwind base;
@tailwind components;
@tailwind utilities;
```

Into:
```css
@import 'tailwindcss';
```

But there are a few issues with that _if_ we have additional CSS on the
page. For example let's imagine we had this:
```css
@tailwind base;

body {
  background-color: red;
}

@tailwind components;

.btn {}

@tailwind utilities;
```

This will now be turned into:
```css
@import 'tailwindcss';

body {
  background-color: red;
}

.btn {}
```

But in v4 we use real layers, in v3 we used to replace the directive
with the result of that layer. This means that now the `body` and `.btn`
styles are in the incorrect spot.

To solve this, we have to wrap them in a layer. The `body` should go in
an `@layer base`, and the `.btn` should be in an `@layer components` to
make sure it's in the same spot as it was before.

That's what this PR does, the original input will now be turned into:

```css
@import 'tailwindcss';

@layer base {
  body {
    background-color: red;
  }
}

@layer components {
  .btn {
  }
}
```

There are a few internal refactors going on as well, but those are less
important.
2024-09-24 16:32:50 +00:00
Robin Malfait
d14249ddc2
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>
2024-09-24 18:17:09 +02:00
Robin Malfait
67d1849f34
Add CSS codemods for migrating @tailwind directives (#14411)
This PR adds CSS codemods for migrating existing `@tailwind` directives
to the new alternatives.

This PR has the ability to migrate the following cases:

---

Typical default usage of `@tailwind` directives in v3.

Input:
```css
@tailwind base;
@tailwind components;
@tailwind utilities;
```

Output:
```css
@import 'tailwindcss';
```

---

Similar as above, but always using `@import` instead of `@import`
directly.

Input:
```css
@import 'tailwindcss/base';
@import 'tailwindcss/components';
@import 'tailwindcss/utilities';
```

Output:
```css
@import 'tailwindcss';
```

---

When you are _only_ using `@tailwind base`:

Input:
```css
@tailwind base;
```

Output:
```css
@import 'tailwindcss/theme' layer(theme);
@import 'tailwindcss/preflight' layer(base);
```

---

When you are _only_ using `@tailwind utilities`:

Input:
```css
@tailwind utilities;
```

Output:
```css
@import 'tailwindcss/utilities' layer(utilities);
```

---

If the default order changes (aka, `@tailwind utilities` was defined
_before_ `@tailwind base`), then an additional `@layer` will be added to
the top to re-define the default order.

Input:
```css
@tailwind utilities;
@tailwind base;
```

Output:
```css
@layer theme, components, utilities, base;
@import 'tailwindcss';
```

---

When you are _only_ using `@tailwind base; @tailwind utilities;`:

Input:
```css
@tailwind base;
@tailwind utilities;
```

Output:
```css
@import 'tailwindcss';
```

We currently don't have a concept of `@tailwind components` in v4, so if
you are not using `@tailwind components`, we can expand to the default
`@import 'tailwindcss';` instead of the individual imports.

---

`@tailwind screens` and `@tailwind variants` are not supported/necessary
in v4, so we can safely remove them.

Input:
```css
@tailwind screens;
@tailwind variants;
```

Output:
```css
```
2024-09-18 22:40:23 +02:00
Robin Malfait
ee7e02b1f3
Add initial codemod tooling (#14434)
This PR adds some initial tooling for codemods. We are currently only
interested in migrating CSS files, so we will be using PostCSS under the
hood to do this. This PR also implements the "migrate `@apply`" codemod
from #14412.

The usage will look like this:

```sh
npx @tailwindcss/upgrade
```

You can pass in CSS files to transform as arguments:

```sh
npx @tailwindcss/upgrade src/**/*.css
```

But, if none are provided, it will search for CSS files in the current
directory and its subdirectories.

```
≈ tailwindcss v4.0.0-alpha.24

│ No files provided. Searching for CSS files in the current
│ directory and its subdirectories…

│ Migration complete. Verify the changes and commit them to
│ your repository.
```

The tooling also requires the Git repository to be in a clean state.
This is a common convention to ensure that everything is undo-able. If
we detect that the git repository is dirty, we will abort the migration.

```
≈ tailwindcss v4.0.0-alpha.24

│ Git directory is not clean. Please stash or commit your
│ changes before migrating.

│ You may use the `--force` flag to override this safety
│ check.
```


---

This PR alsoo adds CSS codemods for migrating existing `@apply`
directives to the new version.

This PR has the ability to migrate the following cases:

---

In v4, the convention is to put the important modifier `!` at the end of
the utility class instead of right before it. This makes it easier to
reason about, especially when you are variants.

Input:
```css
.foo {
  @apply !flex flex-col! hover:!items-start items-center;
}
```

Output:
```css
.foo {
  @apply flex! flex-col! hover:items-start! items-center;
}
```


---

In v4 we don't support `!important` as a marker at the end of `@apply`
directives. Instead, you can append the `!` to each utility class to
make it `!important`.

Input:
```css
.foo {
  @apply flex flex-col !important;
}
```

Output:
```css
.foo {
  @apply flex! flex-col!;
}
```
2024-09-18 16:45:43 +02:00