Skip to content
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

Migrate theme(…) calls to var(…) or the modern theme(…) syntax #14664

Merged
merged 6 commits into from
Oct 16, 2024

Conversation

RobinMalfait
Copy link
Member

This PR adds a codemod to convert theme(…) calls to var(…) calls. If we can't safely do this, then we try to convert the theme(…) syntax (dot notation) to the modern theme(…) syntax (with CSS variable-like syntax).

Let's look at some examples:

Simple example:

Input:

<div class="bg-[theme(colors.red.500)]"></div>

Output:

<div class="bg-[var(--color-red-500)]"></div>

With fallback:

Input:

<div class="bg-[theme(colors.red.500,theme(colors.blue.500))]"></div>

Output:

<div class="bg-[var(--color-red-500,var(--color-blue-500))]"></div>

With modifiers:

Input:

<div class="bg-[theme(colors.red.500/75%)]"></div>

Output:

<div class="bg-[var(--color-red-500)]/75"></div>

We can special case this, because if you are using that modifier syntax we assume it's being used in a theme(…) call referencing a color. This means that we can also convert it to a modifier on the actual candidate.


With modifier, if a modifier is already present:

Input:

<div class="bg-[theme(colors.red.500/75%)]/50"></div>

Output:

<div class="bg-[theme(--color-red-500/75%)]/50"></div>

In this case we can't use the var(…) syntax because that requires us to move the opacity modifier to the candidate itself. In this case we could use math to figure out the expected modifier, but that might be too confusing. Instead, we convert to the modern theme(…) syntax.


Multiple theme(…) calls with modifiers:

Input:

<div class="bg-[theme(colors.red.500/75%,theme(colors.blue.500/50%))]"></div>

Output:

<div class="bg-[theme(--color-red-500/75%,theme(--color-blue-500/50%))]"></div>

In this case we can't convert to var(…) syntax because then we lose the opacity modifier. We also can't move the opacity modifier to the candidate itself e.g.: /50 because we have 2 different variables to worry about.

In this situation we convert to the modern theme(…) syntax itself.


Inside variants:

Input:

<div class="max-[theme(spacing.20)]:flex"></div>

Output:

<div class="max-[theme(--spacing-20)]:flex"></div>

Unfortunately we can't convert to var(…) syntax reliably because in some cases (like the one above) the value will be used inside of an @media (…) query and CSS doesn't support that at the time of writing this PR.

So to be safe, we will not try to convert theme(…) to var(…) in variants, but we will only upgrade the theme(…) call itself to modern syntax.

@RobinMalfait RobinMalfait force-pushed the feat/migrate-theme-to-var branch 2 times, most recently from ac4d760 to cf037a8 Compare October 14, 2024 15:51
@RobinMalfait RobinMalfait force-pushed the feat/migrate-theme-to-var branch from cf037a8 to 6c10608 Compare October 15, 2024 09:29
CHANGELOG.md Outdated
@@ -20,6 +20,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- _Upgrade (experimental)_: Migrate `@media screen(…)` when running codemods ([#14603](https://github.com/tailwindlabs/tailwindcss/pull/14603))
- _Upgrade (experimental)_: Inject `@config "…"` when a `tailwind.config.{js,ts,…}` is detected ([#14635](https://github.com/tailwindlabs/tailwindcss/pull/14635))
- _Upgrade (experimental)_: Migrate `aria-*`, `data-*`, and `supports-*` variants from arbitrary values to bare values ([#14644](https://github.com/tailwindlabs/tailwindcss/pull/14644))
- _Upgrade (experimental)_: Migrate `theme(…)` calls to `var(…)` or the modern `theme(…)` syntax ([#14664](https://github.com/tailwindlabs/tailwindcss/pull/14664))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The change log entry is a bit misleading since I would assume all theme() functions are migrated when it's only theme() function inside candidates.

Might want to clarify this or (even better) follow up with the CSS theme() function implementation right away? hopefully should be able to reuse most of the implementation.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The change log entry is a bit misleading since I would assume all theme() functions are migrated when it's only theme() function inside candidates.

Do you mean that you expect all the theme() functions to be gone? Because in case you have max-[theme(spacing.4)]:flex then we can't replace the theme() with var because the browser doesn't support this. We could sort of resolve the theme(spacing.4) value, but then we end up with max-[1rem]:flex which isn't "dynamic" anymore if you change your spacing scale.

So in this case, we migrate max-[theme(spacing.4)] to the modern theme function max-[theme(--spacing.4)].

Is this what you mean?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Got it!

It's only about the theme(…) in classes, not about the theme(…) in CSS.

@RobinMalfait RobinMalfait force-pushed the feat/migrate-theme-to-var branch from 6c10608 to 1ab1a01 Compare October 15, 2024 09:46
// This test in itself doesn't make much sense. But we need to make sure
// that this doesn't end up as the modifier in the candidate itself.
['max-[theme(spacing.4/50)]:flex', 'max-[theme(--spacing-4/50)]:flex'],
])('%s => %s', async (candidate, result) => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So there's a class of theme() keypaths that are valid in v3 (and are valid in our interop layer) but don't have a keypath equivalent in v4. Things like: theme(fontWeight.semibold) or theme(cursor.progress). Let's add some tests here to see they are still migrated.

Maybe what we can do is to use the dict in default-theme.ts and resolve these to the actual values?

Copy link
Member Author

@RobinMalfait RobinMalfait Oct 15, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While on call, one thing we noticed is that theme(fontWeight.semibold) is hardcoded in the font-weight utility definition to be 600 and doesn't have a corresponding CSS variable. This means that we can't migrate theme(fontWeight.semibold) to a var(…) or even a theme(--font-weight-semibold) because there is no CSS variable. We could inline it to 600 directly since it's hardcoded.

However, there is a catch... if you define --font-weight-semibold: 100 as a CSS variable inside your @theme { … }, then the value is not hardcoded anymore, but the CSS variable takes precedence.

This means that we will migrate theme(fontWeight.semibold) to theme(--font-weight-semibold) or even var(--font-weight-semibold) but only if you have this CSS variable configured.

Long story short:

  1. If you migrate to var(…) or theme(--…), and remove the CSS variable from your @theme { … } later, then the default fontWeight.semibold won't apply.
  2. If you don't migrate, but inline the value theme(fontWeight.semibold) becomes 600. Then adding the CSS variable to your @theme { … } later, then the variable won't take effect because we already hardcoded the value to 600.
  3. Keep it as-is which is the more correct thing to do I think, but then we have some theme(…) calls with dot notation, some with --… notation and some var(…) notation.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the safer thing to do here is to:

  1. Migrate if we a variable was configured (that's what will happen by default)
  2. Keep the old syntax if we can't instead of inlining the value.

@RobinMalfait RobinMalfait force-pushed the feat/migrate-theme-to-var branch from 1ab1a01 to bb35ca8 Compare October 15, 2024 12:04
Because `Convert.None` is just the default, but feels incorrect naming
wise.
@RobinMalfait RobinMalfait enabled auto-merge (squash) October 16, 2024 14:43
@RobinMalfait RobinMalfait merged commit 8dc343d into next Oct 16, 2024
1 check passed
@RobinMalfait RobinMalfait deleted the feat/migrate-theme-to-var branch October 16, 2024 14:44
RobinMalfait added a commit that referenced this pull request Oct 17, 2024
This PR is a follow up from #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*/
  }
}
```
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants